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

Stella981
• 阅读 691

    今天天分享下如何通过组合模式,在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 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("\r\n"); s.append("" + x + "\r\n"); s.append("" + y + "\r\n"); s.append("" + r + "\r\n"); s.append("" + getPaintStyle() + "\r\n"); s.append("" + getWight() + "\r\n"); s.append("" + getPaintAlpha() + "\r\n"); s.append("\r\n"); try { bufferedWriter.write(s.toString()); } catch (IOException e) { e.printStackTrace(); } }

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

@Override public void writer(BufferedWriter bufferedWriter) { String s = "\r\n"; String s1 = "\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 floatList = new ArrayList<>(); try { while ((tempString = bufferedReader.readLine()) != null) { if (tempString.contains("")) { break; } if (tempString.contains("") || tempString.contains("") || tempString.contains("")) { 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("")) { getRectF().top = Float.parseFloat(s); } if (tempString.contains("")) { getRectF().left = Float.parseFloat(s); } if (tempString.contains("")) { getRectF().right = Float.parseFloat(s); } if (tempString.contains("")) { getRectF().bottom = Float.parseFloat(s); } if (tempString.contains("")) { setWight(Float.parseFloat(GetNumbers(tempString))); } if (tempString.contains("")) { 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("")) { 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(),就是继续开始读取。

点赞
收藏
评论区
推荐文章
Easter79 Easter79
3年前
tinyxml的封装与使用
tinyxml是个高效精简的xml解析开源代码.针对tinyxml直接使用对于对xml不是很熟悉的入门新手来说,有些概念难以理解,因此我将其封装后,供大家使用.头文件:include<stringinclude"tinyxml.h"usingnamespacestd;classCXML{public:   
徐小夕 徐小夕
4年前
3分钟教你用原生js实现具有进度监听的文件上传预览组件
本文主要介绍如何使用原生js,通过面向对象的方式实现一个文件上传预览的组件,该组件利用FileReader来实现文件在前端的解析,预览,读取进度等功能,并对外暴露相应api来实现用户自定义的需求,比如文件上传,进度监听,自定义样式,读取成功回调等。组件设计架构如下:(https://imghelloworld.osscnbeijing.
Stella981 Stella981
3年前
MyBatis接口(Bean)与配置信息(Mapper)绑定
目的MyBatis的XML配置文件解析成JAVA类并在内存中存储,但是在程序运行时需要对应的类去调用,而相应的调用类还没有实例化,现在流行的都是使用Spring去管理需要的对象,Spring提供2种方式,分别为XML与注解。下面来分析调用类的实例化及与配置绑定。1XML方式<bean id"menuMapper" cl
Stella981 Stella981
3年前
Android开发之使用pull解析XML文件
Android已经集成进了Pull解析器,所以无需添加任何jar文件。android系统本身使用到的各种xml文件,其内部也是采用Pull解析器进行解析的。Pull解析器的运行方式与SAX解析器相似。它提供了类似的事件,如:开始元素和结束元素事件,使用parser.next()可以进入下一个元素并触发相应事件。跟SAX不同的是,Pull解析器产生的事
Stella981 Stella981
3年前
Android入门:使用Android自带媒体库读取MP3文件
今天研究了下如何在Android读取SD卡中的媒体文件(MP3),开始的思路是遍历SD卡所有目录,相信这也是所有开发者第一会想到的思路,无法就是遍历所有文件,将所有后缀名为.mp3读取出来;但是最后发现,如果你对Android稍有了解,你会发现,其实媒体扫描这个工作,Android设置已经替我们干了,Android系统会在SD卡有更新的时候自动将SD卡文件分
Wesley13 Wesley13
3年前
JavaWeb中的web.xml文件配置解析
一web.xml的文件重要性  web.xml是servlet规定的启动配置文件,凡属基于servlet的javaWeb容器必遵守这个规范,而目前主流的容器都是基于servlet的,因此可以理解web.xml是每个javaweb应用都离不开web.xml配置文件。  web.xml完整的名字应该叫做部署描述符
Stella981 Stella981
3年前
Android开发之使用Pull解析器生成XML文件
有些时候,我们需要生成一个XML文件,生成XML文件的方法有很多,如:可以只使用一个StringBuilder组拼XML内容,然后把内容写入到文件中;或者使用DOMAPI生成XML文件,或者也可以使用pull解析器生成XML文件,这里推荐大家使用Pull解析器。相关代码如下:publicstaticStringwriteXML(List<Pe
Wesley13 Wesley13
3年前
DOM解析XML文件3
1.新建名为domxml的项目!这里写图片描述(http://static.oschina.net/uploads/img/201507/11181054_RvEx.jpg)2.新建user\_item.xml的自定义布局文件<?xmlversion"1.0"encoding"UTF8"?<LinearLay
Stella981 Stella981
3年前
Spring 容器 17 个常用注解总结
传统的Spring做法是使用.xml文件来对bean进行注入或者是配置aop、事物,这么做有两个缺点:如果所有的内容都配置在.xml文件中,那么.xml文件将会十分庞大;如果按需求分开.xml文件,那么.xml文件又会非常多。总之这将导致配置文件的可读性与可维护性变得很低。在开发中在.java文件和.xml文件之间不断切换,是
Stella981 Stella981
3年前
Android学习笔记之AndroidManifest.xml文件解析
一、关于AndroidManifest.xmlAndroidManifest.xml是每个android程序中必须的文件。它位于整个项目的根目录,描述了package中暴露的组件(activities,services,等等),他们各自的实现类,各种能被处理的数据和启动位置。除了能声明程序中的Activities,ContentProvider