今天天分享下如何通过组合模式,在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(),就是继续开始读取。