Android小白的探索:2D绘图之Android简易版Microsoft Visio学习之路 二、 自定义xml生成与解析...

本文介绍了一种不依赖标准接口的XML文件自定义解析与生成方法,利用组合模式实现图形数据的保存与读取。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    今天天分享下如何通过组合模式,在sd卡中,写一个xml文件来保存这个组合类,及如何读取,解析自定义的xml文件,没有使用w3c 组织所推荐的任何一种接口,自己实现对自定义xml文件的解析,因为能力不够,所以实现下基本原理,加深对xml文件解析原理的理解。

    上一篇博客我发现都没写出来我对组合模式的理解,很尴尬,文笔不好呢。

    先简略说一下组合模式,这个组合模式,是我对复杂,嵌套xml文件的解析、生成的基础。

组合模式,其实是对现实生活中树的抽象,一颗树,他有树干,树枝,树叶,树干中又有树枝、树叶,树叶中什么都没有,只是最小的单位,而树干可以说只是大号的树枝,所有,一颗树就两种,树枝和树叶。抽象为一个类的话就是,一个数枝类中,有一个list包含本树枝中所有包含的所有树枝树干,也就是子节点。当画这颗树的时候,我们其实,是去调用总的父节点,让父节点去循环调用他的子节点,有的子节点又是第二级父节点,他又会去调用自己的子节点,第三级、第四级等等,一直到把所有的节点都递归调用出来。如果还不是不清楚,请看我上一篇博客https://my.oschina.net/zhenghaoLi/blog/1519550

    一个xml文件不仅仅只有一层,有根,有叶,有属性等等,我们暂时用不了这么多,我使用xml文件,只是为了,方便查看层级结构,即使不用xml文件,json、Excel、txt等也都是可以的。先看我自定义的xml文件层级结构。

<?xml version="1.0" encoding="utf-8" ?> 
 <Composite>
  <Composite>
   <Ellipse>
    <rectFTop>40.0</rectFTop> 
    <rectFLeft>50.0</rectFLeft> 
    <rectFRight>700.0</rectFRight> 
    <rectFBottom>200.0</rectFBottom> 
    <paintStyle>STROKE</paintStyle> 
    <paintWight>20.0</paintWight> 
    <paintAlpha>100</paintAlpha> 
  </Ellipse>
  <Path>
   <pathX>100.0</pathX> 
   <pathY>100.0</pathY> 
   <pathX>100.0</pathX> 
   <pathY>300.0</pathY> 
   <pathX>200.0</pathX> 
   <pathY>500.0</pathY> 
   <pathX>310.0</pathX> 
   <pathY>265.0</pathY> 
   <pathX>223.0</pathX> 
   <pathY>316.0</pathY> 
   <pathX>265.0</pathX> 
   <pathY>265.0</pathY> 
   <pathX>20.0</pathX> 
   <pathY>100.0</pathY> 
   <paintStyle>STROKE</paintStyle> 
   <paintWight>20.0</paintWight> 
   <paintAlpha>100</paintAlpha> 
  </Path>
 </Composite>
 <Composite>
  <Rectangle>
   <rectFTop>300.0</rectFTop> 
   <rectFLeft>100.0</rectFLeft> 
   <rectFRight>400.0</rectFRight> 
   <rectFBottom>500.0</rectFBottom> 
   <paintStyle>STROKE</paintStyle> 
   <paintWight>20.0</paintWight> 
   <paintAlpha>100</paintAlpha> 
  </Rectangle>
  <Circular>
   <circularX>100.0</circularX> 
   <circularY>100.0</circularY> 
   <circularR>100.0</circularR> 
   <paintStyle>STROKE</paintStyle> 
   <paintWight>20.0</paintWight> 
   <paintAlpha>100</paintAlpha> 
  </Circular>
  <Line>
   <lineX>200.0</lineX> 
   <lineY>100.0</lineY> 
   <lineX>300.0</lineX> 
   <lineY>200.0</lineY> 
   <lineX>100.0</lineX> 
   <lineY>400.0</lineY> 
   <lineX>410.0</lineX> 
   <lineY>165.0</lineY> 
   <lineX>423.0</lineX> 
   <lineY>216.0</lineY> 
   <lineX>665.0</lineX> 
   <lineY>465.0</lineY> 
   <lineX>20.0</lineX> 
   <lineY>100.0</lineY> 
   <paintStyle>STROKE</paintStyle> 
   <paintWight>20.0</paintWight> 
   <paintAlpha>100</paintAlpha> 
  </Line>
 </Composite>
</Composite>

这个xml文件中包含画出一个图形所有需要的信息,我们先看看生成这个文件的,复杂类的结构

dataRoot.addChild(dataroot1);
dataRoot.addChild(dataroot2);
dataroot1.addChild(dataLeaf1);
dataroot1.addChild(dataLeaf2);
dataroot2.addChild(dataLeaf3);
dataroot2.addChild(dataLeaf4);
dataroot2.addChild(dataLeaf5);

写文件,写文件,我是用java io去写的,一行一行的追加进去,所以,我们需要在定义一个接口,让数据类去实现这个接口,我们就能在外部进行调用,而不用管是谁执行了这个接口,

public interface DataInterface {
    public void addChild(BaseData baseData);
    public void removeChild(int index);
    public void move(float moveX ,float moveY);
    public void draw(Canvas canvas);
    public List<BaseData> getChild();
    public DataType[] pointIsInside(float moveX , float moveY);
    public void writer(BufferedWriter bufferedWriter);
    public BaseData read(BufferedReader bufferedReader);
}

在这个接口类中,我把io流的传进去,io流是这样的,这就像一个管道,你开了口子,你不关闭,这个口子就一直存在,可以随意的对文件的内容进行操作,在Android中,除了基本数据类型传递的时候是copy,其他的任何数据类型都是指针的引用,所以不用考虑,在子类中我当前所操作的这个对象是不是我想要那个对象。

写xml:

数据的父类中的wrier方法

@Override
public void writer(BufferedWriter bufferedWriter) {

}

在叶子中把自己的属性写进去,重写writer方法

@Override
public void writer(BufferedWriter bufferedWriter) {
    float x = getFloats()[0];
    float y = getFloats()[1];
    float r = getFloats()[2];
    StringBuilder s = new StringBuilder();
    s.append("<Circular>\r\n");
    s.append("<circularX>" + x + "</circularX>\r\n");
    s.append("<circularY>" + y + "</circularY>\r\n");
    s.append("<circularR>" + r + "</circularR>\r\n");
    s.append("<paintStyle>" + getPaintStyle() + "</paintStyle>\r\n");
    s.append("<paintWight>" + getWight() + "</paintWight>\r\n");
    s.append("<paintAlpha>" + getPaintAlpha() + "</paintAlpha>\r\n");
    s.append("</Circular>\r\n");
    try {
        bufferedWriter.write(s.toString());
    } catch (IOException e) {
        e.printStackTrace();
    }
}

在树枝中重写writer方法,遍历自己的子类的writer方法

@Override
public void writer(BufferedWriter bufferedWriter) {
    String s = "<Composite>\r\n";
    String s1 = "</Composite>\r\n";
    try {
        bufferedWriter.write(s);
        if (childComponents != null) {
            for (BaseData baseData : childComponents) {
                baseData.writer(bufferedWriter);
            }
        }
        bufferedWriter.write(s1);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

我们一行一行的把字符串写进文件,为什么后面是\r\n,这是因为,我解析xml文件时需要一行一行的读取字符串,好判断我需要生成的是树枝还是树叶,\r\n因为在Windows与Linux对换行的编码不同,Linux为\n,Windows为\r\n ,为了有换行的效果,最好使用\r\n。childComponents 是一个list ,其中包含了这个树枝中包含的所有子类,他是如何加进去的,参考我的上一篇博客。

main

public class MainActivity extends AppCompatActivity {
    RelativeLayout relativeLayout;
    DrawView drawView;
    BaseData dataRoot;
    String FILE_PATH = "/sdcard/ClockIn/";
    String FILE_NAME = "1234567.xml";

    List<BaseData> dataList;
    String szie = null;
    float x = 0;
    float y = 0;
    List<Float> floatList = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        relativeLayout = (RelativeLayout) findViewById(R.id.main_relativeLayout);
        drawView = new DrawView(this);
        relativeLayout.addView(drawView);

        dataRoot = new CompositeData();
        BaseData dataroot1 = new CompositeData();
        BaseData dataroot2 = new CompositeData();

        RectF rectF = new RectF();
        rectF.top = 40;
        rectF.right = 700;
        rectF.left = 50;
        rectF.bottom = 200;
//        float left, float top, float right, float bottom
        BaseData dataLeaf1 = new EllipseData();
        dataLeaf1.setRectF(rectF);
        dataLeaf1.setPaintStyle(Paint.Style.STROKE);
        dataLeaf1.setWight(20);
        dataLeaf1.setPaintAlpha(100);


        float[] leaf2Float = {100, 100, 100, 300, 200, 500, 310, 265, 223, 316, 265, 265};
        BaseData dataLeaf2 = new PathData();
        dataLeaf2.setPaintStyle(Paint.Style.STROKE);
        dataLeaf2.setWight(20);
        dataLeaf2.setPaintAlpha(100);
        dataLeaf2.setFloats(leaf2Float);


        RectF rectF1 = new RectF(100, 300, 400, 500);
        BaseData dataLeaf3 = new RectangleData();
        dataLeaf3.setRectF(rectF1);
        dataLeaf3.setPaintStyle(Paint.Style.STROKE);
        dataLeaf3.setWight(20);
        dataLeaf3.setPaintAlpha(100);


        float[] leaf4Float = {100, 100, 100, 100, 300, 600, 410, 165, 423, 216, 665, 465};
        BaseData dataLeaf4 = new CircularData();
        dataLeaf4.setFloats(leaf4Float);
        dataLeaf4.setPaintStyle(Paint.Style.STROKE);
        dataLeaf4.setWight(20);
        dataLeaf4.setPaintAlpha(100);


        float[] leaf5Float = {200, 100, 300, 200, 100, 400, 410, 165, 423, 216, 665, 465};
        BaseData dataLeaf5 = new LineData();
        dataLeaf5.setFloats(leaf5Float);
        dataLeaf5.setPaintStyle(Paint.Style.STROKE);
        dataLeaf5.setWight(20);
        dataLeaf5.setPaintAlpha(100);


        dataRoot.addChild(dataroot1);
        dataRoot.addChild(dataroot2);
        dataroot1.addChild(dataLeaf1);
        dataroot1.addChild(dataLeaf2);
        dataroot2.addChild(dataLeaf3);
        dataroot2.addChild(dataLeaf4);
        dataroot2.addChild(dataLeaf5);

        drawView.upData(dataRoot);
//
        generatedFile(dataRoot, FILE_PATH, FILE_NAME);
//        relativeLayout.setOnTouchListener(new TouchListenerImp());
        readFile(FILE_PATH, FILE_NAME);
    }
/**
 * 数据写入文件
 */
public void generatedFile(BaseData baseData, String filePath, String fileName) {
    BufferedOutputStream bufferedOutputStream = null;
    OutputStreamWriter outputStreamWriter = null;
    BufferedWriter bufferedWriter = null;
    try {
        File file = new File(String.valueOf(makeFilePath(filePath, fileName)));
        // 如果文件存在就删除它
        if (file.exists()) {
            file.delete();
        } else {
            file.mkdir();
        }

        OutputStream outputStream = new FileOutputStream(file);
        bufferedOutputStream = new BufferedOutputStream(outputStream);
        outputStreamWriter = new OutputStreamWriter(bufferedOutputStream);
        bufferedWriter = new BufferedWriter(outputStreamWriter);
        bufferedWriter.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n");
        baseData.writer(bufferedWriter);
        bufferedWriter.flush();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            bufferedWriter.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

// 生成文件夹
public static void makeRootDirectory(String filePath) {
    File file = null;
    try {
        file = new File(filePath);
        if (!file.exists()) {
            file.mkdir();
        }
    } catch (Exception e) {
        Log.i("error:", e + "");
    }
}

// 生成文件
public File makeFilePath(String filePath, String fileName) {
    File file = null;
    makeRootDirectory(filePath);
    try {
        file = new File(filePath + fileName);

        if (!file.exists()) {
            file.createNewFile();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return file;
}

在main中,写文件时,我是把bufferedWriter传给了父节点,让父节点写出所有的东西,文件写好后,记得关闭流。

读:

xml的解析,有几种方式,总的来说就两种,一是流式解析,一是整体加载,整体加载我不是很明白,先来流式解析吧,xml解析,是在xml文档中,一行一行的解析字符串,比如,遇到代表树叶的“<树叶>”,马上通过工厂方法new 一个树叶对象出来,然后,在通过字符串,解析其中的属性,比如“<长>”,然后,把“<长>123</长>”中的123取出来,赋值给这个对象的 长属性,遇到“</树叶>”是,停止对bufferedWriter 的读取,因为只要一个地方读取了一行,bufferedWriter内的指针就会指向下一个,

树叶对文件的读取及返回自身

@Override
public BaseData read(BufferedReader bufferedReader) {
    String tempString = "";
    List<Float> floatList = new ArrayList<>();
    try {
        while ((tempString = bufferedReader.readLine()) != null) {
            if (tempString.contains("</Circular>")) {
                break;
            }
            if (tempString.contains("<circularX>") || tempString.contains("<circularY>") || tempString.contains("<circularR>")) {
                String s = GetNumbers(tempString);
                if (s.length() != 0) {
                    floatList.add(Float.parseFloat(s));
                }
            }
            CommonAttributeAssignment(tempString);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }

    float[] floats = new float[floatList.size()];
    for (int i = 0; i < floatList.size(); i++) {
        floats[i] = floatList.get(i);
    }
    setFloats(floats);
    return this;
}

在自定义的xml文件中,有一些是所有树叶所共有的,所以抽出来,写在父类中

//公共属性赋值
void CommonAttributeAssignment(String tempString) {
    String s = GetNumbers(tempString);
    if (s.length() != 0) {
        if (tempString.contains("<rectFTop>")) {
            getRectF().top = Float.parseFloat(s);
        }
        if (tempString.contains("<rectFLeft>")) {
            getRectF().left = Float.parseFloat(s);
        }
        if (tempString.contains("<rectFRight>")) {
            getRectF().right = Float.parseFloat(s);
        }
        if (tempString.contains("<rectFBottom>")) {
            getRectF().bottom = Float.parseFloat(s);
        }
        if (tempString.contains("<paintWight>")) {
            setWight(Float.parseFloat(GetNumbers(tempString)));
        }
        if (tempString.contains("<paintAlpha>")) {
            setPaintAlpha(Integer.parseInt(GetNumbers(tempString)));
        }
    }

    if (tempString.contains("<paintStyle>")) {
        if (tempString.contains("FILL")) {
            setPaintStyle(Paint.Style.FILL);
        }
        if (tempString.contains("FILL_AND_STROKE")) {
            setPaintStyle(Paint.Style.FILL_AND_STROKE);
        }
        if (tempString.contains("STROKE")) {
            setPaintStyle(Paint.Style.STROKE);
        }
    }
}

取出xml文件中,取出的一行字符串取出其中数字

//截取数字
String GetNumbers(String tempString) {

    Pattern p = Pattern.compile("[0-9\\.]+");
    Matcher m = p.matcher(tempString.trim());
    while (m.find()) {
        return m.group();
    }
    return "";
}

也写在父类里边,为什么不写静态方法,是因为,我在那个类里进行的赋值,只会赋给当前类

树枝对文件的读取及返回自身

@Override
public BaseData read(BufferedReader bufferedReader) {
    String tempString = "";
    try {
        while ((tempString = bufferedReader.readLine()) != null) {
            if (tempString.contains("</Composite>")) {
                break;
            }
            addChild(StyleView.getBaseData(tempString).read(bufferedReader));
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    return this;
}

StyleView.getBaseData(tempString).read(bufferedReader) 是使用解析出来的的字符串,通过getBaseData工厂方法来生成对应对象的方法,然后调用生成对象的read方法,返回这个属性已经被赋值了的树叶、树枝类,然后添加到树枝的list中去

StyleView.getBaseData(tempString) 生成xml对应类的工厂方法

public static BaseData getBaseData(String typeEnum) {
    BaseData baseData = new BaseData();
    if (typeEnum.contains(TypeEnum.Circular.toString())){
           return new CircularData();
    }
    if (typeEnum.contains(TypeEnum.Ellipse.toString())){
        return new EllipseData();
    }
    if (typeEnum.contains(TypeEnum.Line.toString())){
        return new LineData();
    }
    if (typeEnum.contains(TypeEnum.Path.toString())){
        return new PathData();
    }
    if (typeEnum.contains(TypeEnum.Rectangle.toString())){
        return new RectangleData();
    }

    if (typeEnum.contains(TypeEnum.Composite.toString())){
        return new CompositeData();
    }

    return baseData;
}

TypeEnum 对类的判断

public enum TypeEnum {
    Circular, Ellipse, Line, Path, Rectangle, Composite;
}

main的read方法,xml格式化,以行为单位解析xml,是解析xml的核心,通过一行一行的解析,我们可以根据这行字符串中内容进行:对应类的生成、进行类的属性赋值。

/**
 * 以行为单位读取文件,常用于读面向行的格式化文件
 */
public void readFile(String filePath, String fileName) {
    File file = new File(filePath + fileName);
    BufferedReader reader = null;
    try {
        reader = new BufferedReader(new FileReader(file));
        String tempString = null;

        BaseData baseData = new BaseData();
        while ((tempString = reader.readLine()) != null) {
            if (tempString.length() == 0 || tempString.contains("<?xml version=\"1.0\" encoding=\"utf-8\"?>")) {
                continue;
            }
            baseData = StyleView.getBaseData(tempString).read(reader);
        }
        reader.close();
        drawView.upData(baseData);
        generatedFile(baseData, filePath, fileName);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (reader != null) {
            try {
                reader.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }
}

过滤掉头文件,与所有的空行,然后画出这个复杂类,在生成一下这个类的xml文件,对比两个xml是否相同,反正我对比的时候是相同的xml结构与属性。自定义xml的生成与解析到这儿就完了。有点长。

    如果不是格式化的xml文件怎么办,其实,所谓的格式化xml,所谓的换行,只是在你想要换行的后面加一个 \r\n 或 \n 而已,BufferedReader 其实是java io一个字节一个字节的读取,然后放到字节buffer中,当往字节buffer中加数据是,判断读取到\n 或 \n 时,暂停读取,字节buffer转换为BufferedReader。BufferedReader.readLine(),就是继续开始读取。

 

转载于:https://my.oschina.net/zhenghaoLi/blog/1531808

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值