今天天分享下如何通过组合模式,在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
在这个接口类中,我把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("
在树枝中重写writer方法,遍历自己的子类的writer方法
@Override
public void writer(BufferedWriter bufferedWriter) {
String s = "
我们一行一行的把字符串写进文件,为什么后面是\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\[\] 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("
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(),就是继续开始读取。