SVG-Android(gradle插件生成器)源码详解
来源:互联网 发布:蛟龙号发现了什么知乎 编辑:程序博客网 时间:2024/06/15 01:37
Google在android的5.0以后推出了支持显示svg格式的图片,svg是啥?官方解释就是不失真的图片格式,失不失真和像素密度以及图片的像素有关系,如果你自定义一个view,在onDraw方法里画图,你能看出来你所画的图片在不同手机上会产生失真吗?答案肯定是否定的,而Google就在这画图上动了手脚,所有的svg图片都可以用路径画出来,路径当然是path了,也就是说只要能转化为svg路径图片的svg文件,都可以通过Google提供的api来把svg文件解析为path路径,最终通过VectorDrawable将path画出来,所以才没有失真一说。
不积跬步无以至千里不积小流无以成江海,虽然app进入寒冬期,不过做android开发的小伙伴们,也不要放弃最初的梦想,在你还没有足够能力实现经济自由的时候还是不要停止学习。好了,有点扯远了,现在进入正题。
在正式解析之前,首先得具备几项知识点,第一你应该会groovy语言,当然对于学java的我们,学这个语言速度会很快,因为它的语法基本上是仿的java,当然它也是基于jvm虚拟机实现的,这里用它因为Gradle需要它来写构建脚本,就像服务端用ANT实现java的自动构建和部署或用Maven实现,这里是groovy官网,当然你要是觉着看文档太费劲了,也可以参考这一篇groovy语法入门看这一篇就够了,不过我还是建议小伙伴们多去看文档,这样慢慢的能增强我们的阅读能力,毕竟如果你想像大神们一样走在技术前沿的话,先从看文档开始锻炼自己吧。学完了语法基础后,那么你就开始了解怎么配置gradle文件的吧,当然既然咱讲的是android的,那就看android的关于gradle文档的配置吧,android的官方文档gradle配置,熟练android studio的小伙伴这一步可以忽略。
当然这里还涉及到gradle的简单dsl语法,看android的你只需要看android DSL文档,在android{}标签下需要你选择配置的也就文档上写的那么多,如果每个标签你都很熟悉的话,可以做出很不错的小功能,比如多渠道打包、你服务端ip地址的更改打包配置(也就是说你可以通过配置来定义自己生成BuildConfig.java文件的生成规则(BuildConfig是android的android gradle自动为我们生成的类))(打个比方每次发行一个新功能我们需要连接的服务器的时候肯定得先通过测试服务器测试,那么连接ip就是测试服务器的,那么如果测试成功,而你需要重新打包正式的是不是又得去改ip地址,看完这些配置文档你不需要改一行代码了(这个标签是BuildType标签下的buildConfigField(type, name, value)方法))、实现组件化开发(就是我可以建多个module让它的形式在Module和Library形式之间切换,从而在Terminal模板下用命令模式让gradle编译哪一个组件从而实现组件化开发)、自定义打包apk的名字、自定义需要编译文件文件夹的名字等等,android的gradle配置也看完的话,最后就需要看gradle官网,当然这里只需要看这么建一个gradle插件(只要我们创建了这个插件的话,每次编译gradle都会去执行这个插件里面的内容),现在看一看官方文档给我们提供的试例,首先建一个类实现Plugin,如下:
package org.example.greeting;import org.gradle.api.Plugin;import org.gradle.api.Project;public class GreetingPlugin implements Plugin<Project> { public void apply(Project project) { project.getTasks().create("hello", Greeting.class, (task) -> { task.setMessage("Hello"); task.setRecipient("World"); }); }}
接下来声明一个任务类,注意这里的 @TaskAction注解(将要执行任务的方法),注意这里是gradle4.4版本的
package org.example.greeting;import org.gradle.api.DefaultTask;import org.gradle.api.tasks.TaskAction;public class Greeting extends DefaultTask { private String message; private String recipient; public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public String getRecipient() { return recipient; } public void setRecipient(String recipient) { this.recipient = recipient; } @TaskAction void sayGreeting() { System.out.printf("%s, %s!\n", getMessage(), getRecipient()); }}
然后在app文件下build.gradle里加入apply plugin: org.example.greeting.GreetingPlugin,当你点击编译项目的时候,那么将会执行apply方法,以及方法里创建的所有任务,那么这个org.example.greeting.GreetingPlugin名字咋来的,你需要在插件代码的同级目录下创建resources文件夹,如图
这里有一个properties为扩展名的文件,这里就是声明对应的那个Project类,当然在发布到仓库上的时候,可以给起个别名
implementation-class=com.github.megatronking.svg.plugin.SVGPlugin
接触完这些以后,那么接下来你需要下载本次要讲的框架框架源码地址,下载完之后直接进入插件的那个类
public void apply(Project project) { //得到配置文件数据 def svgExtension = project.extensions.create("svg", SVGExtension) //创建任务 def assemble = project.tasks.create("svgAssemble", SVGAssembleTask) assemble.setGroup(SVG_TASK_GROUP) def cleanShape = project.tasks.create("svgCleanShape", SVGShapeCleanTask) cleanShape.setGroup(SVG_TASK_GROUP) def cleanJava = project.tasks.create("svgCleanJava", SVGJavaCleanTask) cleanJava.setGroup(SVG_TASK_GROUP) def cleanVector = project.tasks.create("svgCleanVector", SVGVectorCleanTask) cleanVector.setGroup(SVG_TASK_GROUP) def loadAppColor = project.tasks.create("svgLoadAppColor", SVGAppColorLoadTask) loadAppColor.setGroup(SVG_TASK_GROUP) Task cleanTask = project.tasks.create("svgClean") cleanTask.setGroup(SVG_TASK_GROUP) //得到配置文件svg2vector下的属性 def svg2vectorExtensions = project.container(SVG2VectorExtension) project.extensions.svg2vector = svg2vectorExtensions
当编译文件是会执行这个apply方法,首先将app下的build.gradle里的配置属性通过project.extensions.create("svg", SVGExtension)和project.container方法进行获取,这里你会问为什么配置属性,还记得在android{}配置的那些标签属性吧,gradle是怎么为我们解析的,这里框架源码处有为这个插件提供的属性
svg { // vector resources vectorDirs = ["src/main/svg_debug/drawable"] // shape resources shapeDir = "src/main/svg_release/drawable" // java classpath javaDir = "src/main/java/com/github/megatronking/svg/sample/drawables" svg2vector { test { svgDir = "${rootDir}/test" vectorDir = "src/main/svg_debug/drawable" height = 48 width = 48 } }}
通过我们声明的类名字和配置文件保持一致来获取属性,如下代码
public class SVGExtension { public def vectorDirs = []; public def shapeDir; public def javaDir; public def packageName; public def appColors; public def cleanMode; public def debugMode; public def autoSourceSet = true; public def generateLoader = true;}
public class SVG2VectorExtension { public def name; public def svgDir; public def vectorDir; public def width = 0; public def height = 0; public SVG2VectorExtension(def name) { this.name = name; }
获取完属性后,新创建了很多任务类,当然每个任务类都有响应的要实现的方法,接下来执行afterEvaluate ,官方文档介绍此方法是在插件执行完apply方法后执行执行
project.afterEvaluate { Holder.SVG_HOLDER.clear() // get package name from android plugin def androidPlugin = project.android; //packgeName赋值 if (androidPlugin != null && svgExtension != null && svgExtension.packageName == null) { svgExtension.packageName = androidPlugin.defaultConfig.applicationId } // add vector and shape dirs to sourceSets if (androidPlugin != null && svgExtension != null && svgExtension.autoSourceSet != null) { //shapeDir赋值 def shapeDir = svgExtension.shapeDir // def vectorDirs = svgExtension.vectorDirs != null ? svgExtension.vectorDirs : [] if (svg2vectorExtensions != null) { svg2vectorExtesvg2vectorConfigurationnsions.each { -> if (svg2vectorConfiguration.vectorDir != null && !vectorDirs.contains(svg2vectorConfiguration.vectorDir)) { vectorDirs.add(svg2vectorConfiguration.vectorDir) } } } //添加自己的文件目录到编译文件目录 androidPlugin.sourceSets.each { sourceSet-> if (sourceSet.name.equals('debug')) { vectorDirs.each { it = splitResDir(project, it) def hasDir = false for (dir in sourceSet.res.srcDirs) { if (dir.absolutePath.equals(it)) { hasDir = true break } } if (!hasDir) { println "add debug res dir to sourceSet : ${it}" sourceSet.res.srcDir(new File(it)) } } } if (shapeDir != null && sourceSet.name.equals('release')) { shapeDir = splitResDir(project, shapeDir) def hasDir = false for (dir in sourceSet.res.srcDirs) { if (dir.absolutePath.equals(shapeDir)) { hasDir = true break } } if (!hasDir) { println "add release res dir to sourceSet : ${shapeDir}" sourceSet.res.srcDir(new File(shapeDir)) } } } } // resolve dependencies if(!svg2vectorExtensions.isEmpty()) { def svg2vectorTask = project.tasks.create("svg2vector") svg2vectorTask.setGroup(SVG_TASK_GROUP) svg2vectorExtensions.each { svg2vectorExtension-> def svg2vectorChildTask = project.tasks.create("svg2vector" + firstLettertoUpperCase(svg2vectorExtension.name), SVG2VectorTask) svg2vectorChildTask.setGroup(SVG_TASK_GROUP) svg2vectorChildTask.setExtensionName(svg2vectorExtension.name) svg2vectorTask.dependsOn svg2vectorChildTask } assemble.dependsOn svg2vectorTask } cleanTask.dependsOn cleanShape cleanTask.dependsOn cleanJava cleanTask.dependsOn cleanVector if (svgExtension.cleanMode) { assemble.dependsOn cleanTask } assemble.dependsOn loadAppColor }
这个方法里将要对文件配置的属性进行操作了,这里注意最重要的三个配置点:
配置你需要操作的文件夹的位置,看源码提供的例子原图,通过这几个属性配置,获取svg文件的路径vectorDirs = ["src/main/svg_debug/drawable"] // shape resources shapeDir = "src/main/svg_release/drawable" // java classpath javaDir = "src/main/java/com/github/megatronking/svg/sample/drawables"
那么上面方法的意思已经很明了了,就是将我们自己配置的svg文件路径通过sourceSet设置给android的自带插件,sourceSet是啥?好吧,这个标签是用来修改默认编译文件路径的标签,比如android默认资源编译标签是res文件夹,最后就是设置所有创建任务的依赖了,如下代码:
if(!svg2vectorExtensions.isEmpty()) { def svg2vectorTask = project.tasks.create("svg2vector") svg2vectorTask.setGroup(SVG_TASK_GROUP) svg2vectorExtensions.each { svg2vectorExtension-> def svg2vectorChildTask = project.tasks.create("svg2vector" + firstLettertoUpperCase(svg2vectorExtension.name), SVG2VectorTask) svg2vectorChildTask.setGroup(SVG_TASK_GROUP) svg2vectorChildTask.setExtensionName(svg2vectorExtension.name) svg2vectorTask.dependsOn svg2vectorChildTask } assemble.dependsOn svg2vectorTask } cleanTask.dependsOn cleanShape cleanTask.dependsOn cleanJava cleanTask.dependsOn cleanVector if (svgExtension.cleanMode) { assemble.dependsOn cleanTask } assemble.dependsOn loadAppColor }
这么多的任务,它们之间互相产生依赖,例如A依赖B,那么任务B先执行,然后再执行A。那么接下来就是看看这些任务都干了些什么
所有的任务类都是继承了SVGBaseTask这个基类类,如下实现方法
def SVGExtension configuration def SVG2VectorExtension[] svg2vectorConfigurations; public void run() { configuration = project.svg if (configuration.javaDir) { configuration.javaDir = resolveProjectDir(configuration.javaDir) } if (configuration.shapeDir) { configuration.shapeDir = resolveProjectDir(configuration.shapeDir) } if (configuration.vectorDirs) { def vectorDirs = [] configuration.vectorDirs.each { vectorDir-> vectorDir = resolveProjectDir(vectorDir); vectorDirs.add(vectorDir) } configuration.vectorDirs = vectorDirs } svg2vectorConfigurations = project.extensions.svg2vector if (svg2vectorConfigurations) { svg2vectorConfigurations.each { svg2vectorConfiguration-> if(!configuration.vectorDirs.contains(svg2vectorConfiguration.vectorDir)) { configuration.vectorDirs.add(svg2vectorConfiguration.vectorDir) } } } }
这个方法就是将前面配置的值取出来交给基类的这两个属性,如下
def SVGExtension configurationdef SVG2VectorExtension[] svg2vectorConfigurations
public class SVGAppColorLoadTask extends SVGBaseTask { @TaskAction public void run() { super.run(); if (configuration != null && configuration.appColors != null && !configuration.appColors.isEmpty()) { configuration.appColors.keySet().each { key -> Color.appColorMaps.put(key, (int)(configuration.appColors[key])) } } }}
如上这个颜色的任务类,会将配置的颜色值全部保存到集合中,这个颜色主要是为了默认改变Paint画笔的颜色,默认你也可以不配置,毕竟svg文件中包括了颜色值
public class SVGJavaCleanTask extends SVGBaseTask { @TaskAction public void run() { super.run(); if (configuration != null && configuration.javaDir != null) { def dir = file(configuration.javaDir) dir.deleteDir() } }}如上javaCleanTask类将生成的java文件全部删除,每次重新编译项目的时候当然希望通过gradle生成的文件重新生成,所以先删除
public class SVGShapeCleanTask extends SVGBaseTask { @TaskAction public void run() { super.run(); if (configuration != null && configuration.shapeDir != null) { def dir = file(configuration.shapeDir) dir.deleteDir() } }}如上代码删除配置的产生的shap文件夹(如这种标签<shape xmlns:android="http://schemas.android.com/apk/res/android" />)下的文件
public class SVGVectorCleanTask extends SVGBaseTask { @TaskAction public void run() { super.run(); if (configuration != null && configuration.vectorDirs != null) { configuration.vectorDirs.each { vectorDir-> def dir = file(vectorDir) dir.deleteDir() } } }}
如上SVGVectorCleanTask删除为svg文件产生的java类,这几个任务类的实现确实很通俗易懂哈,好最后就剩下最后一个任务类SVGAssembleTask
public void run() { super.run(); // check arguments if (configuration == null) { return } if (configuration.vectorDirs == null) { return } if (configuration.shapeDir == null || !checkDirExistOrMkdirs(configuration.shapeDir)) { return } if (configuration.javaDir == null || !checkDirExistOrMkdirs(configuration.javaDir)) { return } if (configuration.packageName == null) { return } // check vector files def vectors = collectVectors() if (vectors.size() == 0) { return } // read vector files def vectorModels = []; VectorSAXReader reader = new VectorSAXReader() vectors.each { vector-> def vectorModel = new VectorModel() def vectorFile = file(vector) vectorModel.name = vectorFile.name.substring(0, vectorFile.name.lastIndexOf(".xml")) try { vectorModel.vector = reader.read(vector) } catch (Exception e) { logger.error("Occur an error: " + vector + e.getMessage()); return true } //将解析的文件属性全部存入对象中 vectorModels.add(vectorModel) } // substring the package name like: "com.android.xxx" def javaClassPath = configuration.javaDir.replace("\\", ".").replace("/", ".") def javaClassPackage = javaClassPath.substring(javaClassPath.indexOf("src.main.java.") + 14, javaClassPath.length()) // write renderer writeJavaRendererClass(vectorModels, javaClassPackage) // write loader if (configuration.generateLoader) { writeJavaLoaderClass(vectorModels, javaClassPackage) } // write shape xml writeShapeXml(vectorModels) }
前几个判断就是判断build.gradle里有没有设置上面提到的三个表明路径的属性,如果没有设置的话直接返回,设置了就根据目录创建相应的文件夹,文件夹创建完了,这里主要
创建javaDir的文件夹,接下来就是搜集需要的文件路径信息了如下
private def collectVectors() { def vectors = [] configuration.vectorDirs.each { dir-> dir = file(dir) if (dir.exists() && dir.isDirectory()) { dir.eachFile { file-> def path = file.absolutePath if (file.exists() && file.length() != 0 && !vectors.contains(path) && path.endsWith(".xml")) { vectors.add(file.absolutePath) } } } } return vectors }还记的vectorDirs = ["src/main/res_vector/drawable"]是什么吧,是个数组,所以,我们可以配置多个编译文件夹,这个方法就是搜集这些文件,然后将文件夹路径加入
集合中并返回,那么搜集完了之后这里创建了一个VectorSAXReader(这个类就是用来解析svg.xml的),然后开始遍历上面搜集的文件路径开始解析文件里的属性为VectorModel对
对象,最终加入VectorSAXReader集合中,当一切信息到解析完以后,那么当然是将这些对象信息生成需要的文件,什么文件?当然是开头说的谷歌显示svg的原理,就是将svg.xml里的
pathData提取出来为path,然后画出来,这么看来的话可以就生成svg.xml的渲染器,然后通过渲染器将图形画出来。来,看一下我们配置的编译的svg的文件夹
然后最终给我们生成的类是如下图
最终生成了,随便看一下
final float scaleX = w / 24.0f; final float scaleY = h / 24.0f; mPath.reset(); mRenderPath.reset(); mFinalPathMatrix.setValues(new float[]{1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}); mFinalPathMatrix.postScale(scaleX, scaleY); mPath.moveTo(6.0f, 18.0f); mPath.rCubicTo(0.0f, 0.55f, 0.45f, 1.0f, 1.0f, 1.0f); mPath.rLineTo(1.0f, 0f); mPath.rLineTo(0f, 3.5f); mPath.rCubicTo(0.0f, 0.83f, 0.67f, 1.5f, 1.5f, 1.5f); mPath.rCubicTo(0.8299999f, 0.0f, 1.5f, -0.67f, 1.5f, -1.5f); mPath.lineTo(11.0f, 19.0f); mPath.rLineTo(2.0f, 0f); mPath.rLineTo(0f, 3.5f); mPath.rCubicTo(0.0f, 0.83f, 0.67f, 1.5f, 1.5f, 1.5f); mPath.rCubicTo(0.8299999f, 0.0f, 1.5f, -0.67f, 1.5f, -1.5f); mPath.lineTo(16.0f, 19.0f); mPath.rLineTo(1.0f, 0f); mPath.rCubicTo(0.55f, 0.0f, 1.0f, -0.45f, 1.0f, -1.0f);
看见了什么,到处充斥着path类,ok,这个框架的gradle插件就是帮我们在编译器根据你所编写的svg.xml生成需要的渲染类,从而将svg画出来,从而解决向下兼容的问题。好,
接下来看一下它是怎么解析svg.xml文件的,看之前,首先你得掌握android5.0写svg文件的方式官网地址,一般格式如下
<vector xmlns:android="http://schemas.android.com/apk/res/android" <!-- intrinsic size of the drawable --> android:height="256dp" android:width="256dp" <!-- size of the virtual canvas --> android:viewportWidth="32" android:viewportHeight="32"> <!-- draw a path --> <path android:fillColor="#8fff" android:pathData="M20.5,9.5 c-1.955,0,-3.83,1.268,-4.5,3 c-0.67,-1.732,-2.547,-3,-4.5,-3 C8.957,9.5,7,11.432,7,14 c0,3.53,3.793,6.257,9,11.5 c5.207,-5.242,9,-7.97,9,-11.5 C25,11.432,23.043,9.5,20.5,9.5z" /></vector>
这里最重要的属性就是pathData属性,里面可以看到是一连串的数字和个别字母,不过最终都是xml格式,想一想如果你想要解析文件的话,那么你可以任意定义你的文件格式,但
你定义的格式要有规律可寻,只有这样你才能解析或者让别人解析你的文件,所以官方文档也给我们提供了SVG的格式说明path语法,所有的解析都是建立在规律格式上的,ok,如果掌握了
path格式内容的话,接下来就是进行解析了,下面看一下VectorSAXReader怎么解析的,解析默认实现类为下面这个类的子类
public abstract class SimpleImplementSAXReader<T> implements ObjectXmlSAXReader { private SAXReader mReader;
这里用了SAXReader进行读取xml格式的文件,这里用了阿帕奇的dom4j的jar包,当然解析xml文件格式,android的有好几种方式,这又属于android的基础了,利用阿帕奇的解析工具
解析完以后,将文档书传给下面的方法执行protected Vector parseDocument(Document document) throws DocumentException { Element vectorElement = document.getRootElement(); // simple validate if (!VectorConstants.TAG_VECTOR.equals(vectorElement.getName())) { throw new VectorParseException("The root element must be " + VectorConstants.TAG_VECTOR); } List<?> childElements = vectorElement.elements(); if (childElements == null || childElements.isEmpty()) { throw new VectorParseException("There is no child node in the vector"); } Vector vector = new Vector(); VectorParserImpl.VECTOR_ELEMENT_PARSER.parse(vectorElement, vector); return vector; }}
这里首先判断根标签是不是vector,不满足格式不予解析,如果满足格式又通过VectorParserImpl的工具类进行解析,ok,进去瞧瞧
public void parse(Element element, T t) throws DocumentException { mAttributeParser.parse(element, t); for (Object childElement : element.elements()) { parseChild((Element) childElement, t); } }这里的mAttributeParser.parse是为了解析根属性的值,如下代码:循环取出每个节点进行解析,如下public void parse(Element element, Vector vector) { vector.name = parseString(element, VectorConstants.ATTR_NAME); vector.alpha = parseFloat(element, VectorConstants.ATTR_ALPHA, 1.0f); vector.width = parseString(element, VectorConstants.ATTR_WIDTH); vector.height = parseString(element, VectorConstants.ATTR_HEIGHT); vector.viewportWidth = parseFloat(element, VectorConstants.ATTR_VIEWPORT_WIDTH); vector.viewportHeight = parseFloat(element, VectorConstants.ATTR_VIEWPORT_HEIGHT); vector.autoMirrored = parseBoolean(element, VectorConstants.ATTR_AUTO_MIRRORED); vector.tint = parseColor(element, VectorConstants.ATTR_TINT); vector.tintMode = parseString(element, VectorConstants.ATTR_TINT_MODE); }
protected void parseChild(Element childElement, Vector vector) throws DocumentException { Group rootGroup = new Group(null); if (VectorConstants.TAG_GROUP.equals(childElement.getName())) { Group childGroup = new Group(rootGroup); vector.children.add(childGroup); VectorParserImpl.GROUP_ELEMENT_PARSER.parse(childElement, childGroup); } if (VectorConstants.TAG_PATH.equals(childElement.getName())) { Path childPath = new Path(rootGroup); vector.children.add(childPath); VectorParserImpl.PATH_ATTRIBUTE_PARSER.parse(childElement, childPath); } if (VectorConstants.TAG_CLIP_PATH.equals(childElement.getName())) { Path childPath = new Path(rootGroup); vector.children.add(childPath); VectorParserImpl.CLIP_PATH_ATTRIBUTE_PARSER.parse(childElement, childPath); } }
这里可以看出总共有三种子标签需要解析,group、path、clip-path,其中group可以包括path、clip-path进行循环解析
Root Group / | \ Group Path Group / \ | Path Path Path
判断每个标签的值,将标签值一个取出来设置成相应的类,然后用VectorModel里的children集合将储存不同的类,最后进行赋值,path的解析属性值的方式如下
public void parse(Element element, Path path) { path.name = parseString(element, VectorConstants.ATTR_NAME); path.fillColor = parseColor(element, VectorConstants.ATTR_FILL_COLOR); path.pathData = parseString(element, VectorConstants.ATTR_PATH_DATA); path.fillAlpha = parseFloat(element, VectorConstants.ATTR_FILL_ALPHA, 1.0f); path.fillType = parseString(element, VectorConstants.ATTR_FILL_TYPE); path.strokeLineCap = parseString(element, VectorConstants.ATTR_STROKE_LINE_CAP, "butt"); path.strokeLineJoin = parseString(element, VectorConstants.ATTR_STROKE_LINE_JOIN, "miter"); path.strokeMiterLimit = parseFloat(element, VectorConstants.ATTR_STROKE_MITER_LIMIT, 4); path.strokeColor = parseColor(element, VectorConstants.ATTR_STROKE_COLOR); path.strokeAlpha = parseFloat(element, VectorConstants.ATTR_STROKE_ALPHA, 1.0f); path.strokeWidth = parseFloat(element, VectorConstants.ATTR_STROKE_WIDTH); path.trimPathEnd = parseFloat(element, VectorConstants.ATTR_TRIM_PATH_END, 1); path.trimPathOffset = parseFloat(element, VectorConstants.ATTR_TRIM_PATH_OFFSET); path.trimPathStart = parseFloat(element, VectorConstants.ATTR_TRIM_PATH_START); }
单个属性解析如下方法
protected String parseString(Element element, String name) { Attribute attribute = element.attribute(name); return attribute == null ? null : attribute.getValue(); }好,这样就利用阿帕奇的xml解析工具类完成了xml到对象的映射,接下来就是对象到java文件的映射了,首先通过writeJavaRendererClass(vectorModels, javaClassPackage)
创建所有xml对应的渲染类,如下
rivate void writeJavaRendererClass(def vectorModels, def javaClassPackage) { vectorModels.each { vectorModel-> BufferedWriter bw = new BufferedWriter(new FileWriter(file(configuration.javaDir, vectorModel.name + ".java"))) VectorRenderer renderer = new VectorRenderer() renderer.render(vectorModel.vector) JavaClassWriter writer = new SVGRendererTemplateWriter(renderer, vectorModel.vector) writer.setPackage(javaClassPackage) writer.setClassSimpleName(vectorModel.name) writer.write(bw) } }
这里最终还是用了java的io流进行文件内容的写入,当然所有的信息类都是通过,注意这里自定义了一个VectorRenderer 渲染类,path的解析拼接都是通过它完成的,ok
进入SVGRendererTemplateWriter的写方法public void write(BufferedWriter bw) throws IOException { writePackage(bw); writeImports(bw); writeClassComment(bw); writeClass(bw); writeFields(bw); writeConstructMethods(bw); writeMethods(bw); writeInnerClasses(bw); writeEnd(bw); // The end. bw.flush(); bw.close(); }
随便看看写入导入头,如下,其实就是拼接字符串,然后将它写入文件
protected void writeImports(BufferedWriter bw) throws IOException { if (mImports != null && mImports.length != 0) { for (int i = 0; i < mImports.length; i++) { bw.write("import " + mImports[i] + ";"); bw.newLine(); } bw.newLine(); } }这个方法被子类方法覆盖,如下
protected void writeImports(BufferedWriter bw) throws IOException { super.writeImports(bw); bw.write("import android.content.Context;"); bw.newLine(); bw.write("import android.graphics.Canvas;"); bw.newLine(); bw.write("import android.graphics.ColorFilter;"); bw.newLine(); bw.write("import android.graphics.Paint;"); bw.newLine(); bw.newLine(); bw.write("import com.github.megatronking.svg.support.SVGRenderer;"); bw.newLine(); bw.newLine(); }看到了什么,我们生成java文件的导包
import android.content.Context;import android.graphics.Canvas;import android.graphics.ColorFilter;import android.graphics.Paint;import com.github.megatronking.svg.support.SVGRenderer;
不管是导包写入还是class声明继承,这个都是固定写死的,其实最重要的是把path语句的解析方法的字符串写入,如下两个方法就是将path封装的pathData字符串映射成Path类的
方法也可以叫它渲染。private void drawPathData(String pathData) { PathDataNode[] nodes = PathDataNode.createNodesFromPathData(pathData); float[] current = new float[6]; char previousCommand = 'm'; for (int i = 0; i < nodes.length; i++) { addCommand(current, previousCommand, nodes[i].type, nodes[i].params); previousCommand = nodes[i].type; } }public static PathDataNode[] createNodesFromPathData(String pathData) { if (pathData == null) { return null; } int start = 0; int end = 1; ArrayList<PathDataNode> list = new ArrayList<PathDataNode>(); while (end < pathData.length()) { end = nextStart(pathData, end); String s = pathData.substring(start, end).trim(); if (s.length() > 0) { float[] val = getFloats(s); addNode(list, s.charAt(0), val); } start = end; end++; } if ((end - start) == 1 && start < pathData.length()) { addNode(list, pathData.charAt(start), new float[0]); } return list.toArray(new PathDataNode[list.size()]); }
再看下面这个方法
private static int nextStart(String s, int end) { char c; while (end < s.length()) { c = s.charAt(end); // Note that 'e' or 'E' are not valid path commands, but could be // used for floating point numbers' scientific notation. // Therefore, when searching for next command, we should ignore 'e' // and 'E'. if ((((c - 'A') * (c - 'Z') <= 0) || ((c - 'a') * (c - 'z') <= 0)) && c != 'e' && c != 'E') { return end; } end++; } return end; }看到了什么,pathData字符串中根据特殊字母截取一段一段小的字符串,这就是上面说的svg.xml的path的字符规则,例如"M 100 100 L 300 100 L 200 300 z这段代码
M表示函数moveto,L表示lineTo,z表示闭合
然后将这些分片字符串进行PathNode装配,最后通过如下方法
private void addCommand(float[] current, char previousCmd, char cmd, float[] val) { int incr = 2; float currentX = current[0]; float currentY = current[1]; float ctrlPointX = current[2]; float ctrlPointY = current[3]; float currentSegmentStartX = current[4]; float currentSegmentStartY = current[5]; float reflectiveCtrlPointX; float reflectiveCtrlPointY; switch (cmd) { case 'z': case 'Z': notifyResult("mPath.close();"); // Path is closed here, but we need to move the pen to the // closed position. So we cache the segment's starting position, // and restore it here. currentX = currentSegmentStartX; currentY = currentSegmentStartY; ctrlPointX = currentSegmentStartX; ctrlPointY = currentSegmentStartY; notifyResult("mPath.moveTo(" + currentX + "f, " + ctrlPointY + "f);"); break; case 'm': case 'M': case 'l': case 'L': case 't': case 'T': incr = 2; break; case 'h': case 'H': case 'v': case 'V': incr = 1; break; case 'c': case 'C': incr = 6; break; case 's': case 'S': case 'q': case 'Q': incr = 4; break; case 'a': case 'A': incr = 7; break; } for (int k = 0; k < val.length; k += incr) { switch (cmd) { case 'm': // moveto - Start a new sub-path (relative) currentX += val[k]; currentY += val[k + 1]; if (k > 0) { // According to the spec, if a moveto is followed by multiple // pairs of coordinates, the subsequent pairs are treated as // implicit lineto commands. notifyResult("mPath.rLineTo(" + val[k] + "f, " + val[k + 1] + "f);"); } else { notifyResult("mPath.rMoveTo(" + val[k] + "f, " + val[k + 1] + "f);"); currentSegmentStartX = currentX; currentSegmentStartY = currentY; } break; case 'M': // moveto - Start a new sub-path currentX = val[k]; currentY = val[k + 1]; if (k > 0) { // According to the spec, if a moveto is followed by multiple // pairs of coordinates, the subsequent pairs are treated as // implicit lineto commands. notifyResult("mPath.lineTo(" + val[k] + "f, " + val[k + 1] + "f);"); } else { notifyResult("mPath.moveTo(" + val[k] + "f, " + val[k + 1] + "f);"); currentSegmentStartX = currentX; currentSegmentStartY = currentY; } break; case 'l': // lineto - Draw a line from the current point (relative) notifyResult("mPath.rLineTo(" + val[k] + "f, " + val[k + 1] + "f);"); currentX += val[k]; currentY += val[k + 1]; break; case 'L': // lineto - Draw a line from the current point notifyResult("mPath.lineTo(" + val[k] + "f, " + val[k + 1] + "f);"); currentX = val[k]; currentY = val[k + 1]; break; case 'h': // horizontal lineto - Draws a horizontal line (relative) notifyResult("mPath.rLineTo(" + val[k] + "f, 0f);"); currentX += val[k]; break; case 'H': // horizontal lineto - Draws a horizontal line notifyResult("mPath.lineTo(" + val[k] + "f, " + currentY + "f);"); currentX = val[k]; break; case 'v': // vertical lineto - Draws a vertical line from the current point (r) notifyResult("mPath.rLineTo(0f, " + val[k] + "f);"); currentY += val[k]; break; case 'V': // vertical lineto - Draws a vertical line from the current point notifyResult("mPath.lineTo(" + currentX + "f, " + val[k] + "f);"); currentY = val[k]; break; case 'c': // curveto - Draws a cubic Bézier curve (relative) notifyResult("mPath.rCubicTo(" + val[k] + "f, " + val[k + 1] + "f, " + val[k + 2] + "f, " + val[k + 3] + "f, " + val[k + 4] + "f, " + val[k + 5] + "f);"); ctrlPointX = currentX + val[k + 2]; ctrlPointY = currentY + val[k + 3]; currentX += val[k + 4]; currentY += val[k + 5]; break; case 'C': // curveto - Draws a cubic Bézier curve notifyResult("mPath.cubicTo(" + val[k] + "f, " + val[k + 1] + "f, " + val[k + 2] + "f, " + val[k + 3] + "f, " + val[k + 4] + "f, " + val[k + 5] + "f);"); currentX = val[k + 4]; currentY = val[k + 5]; ctrlPointX = val[k + 2]; ctrlPointY = val[k + 3]; break; case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp) reflectiveCtrlPointX = 0; reflectiveCtrlPointY = 0; if (previousCmd == 'c' || previousCmd == 's' || previousCmd == 'C' || previousCmd == 'S') { reflectiveCtrlPointX = currentX - ctrlPointX; reflectiveCtrlPointY = currentY - ctrlPointY; } notifyResult("mPath.rCubicTo(" + reflectiveCtrlPointX + "f, " + reflectiveCtrlPointY + "f, " + val[k] + "f, " + val[k + 1] + "f, " + val[k + 2] + "f, " + val[k + 3] + "f);"); ctrlPointX = currentX + val[k]; ctrlPointY = currentY + val[k + 1]; currentX += val[k + 2]; currentY += val[k + 3]; break; case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp) reflectiveCtrlPointX = currentX; reflectiveCtrlPointY = currentY; if (previousCmd == 'c' || previousCmd == 's' || previousCmd == 'C' || previousCmd == 'S') { reflectiveCtrlPointX = 2 * currentX - ctrlPointX; reflectiveCtrlPointY = 2 * currentY - ctrlPointY; } notifyResult("mPath.cubicTo(" + reflectiveCtrlPointX + "f, " + reflectiveCtrlPointY + "f, " + val[k] + "f, " + val[k + 1] + "f, " + val[k + 2] + "f, " + val[k + 3] + "f);"); ctrlPointX = val[k]; ctrlPointY = val[k + 1]; currentX = val[k + 2]; currentY = val[k + 3]; break; case 'q': // Draws a quadratic Bézier (relative) notifyResult("mPath.rQuadTo(" + val[k] + "f, " + val[k + 1] + "f, " + val[k + 2] + "f, " + val[k + 3] + "f);"); ctrlPointX = currentX + val[k]; ctrlPointY = currentY + val[k + 1]; currentX += val[k + 2]; currentY += val[k + 3]; break; case 'Q': // Draws a quadratic Bézier notifyResult("mPath.quadTo(" + val[k] + "f, " + val[k + 1] + "f, " + val[k + 2] + "f, " + val[k + 3] + "f);"); ctrlPointX = val[k]; ctrlPointY = val[k + 1]; currentX = val[k + 2]; currentY = val[k + 3]; break; case 't': // Draws a quadratic Bézier curve(reflective control point)(relative) reflectiveCtrlPointX = 0; reflectiveCtrlPointY = 0; if (previousCmd == 'q' || previousCmd == 't' || previousCmd == 'Q' || previousCmd == 'T') { reflectiveCtrlPointX = currentX - ctrlPointX; reflectiveCtrlPointY = currentY - ctrlPointY; } notifyResult("mPath.rQuadTo(" + reflectiveCtrlPointX + "f, " + reflectiveCtrlPointY + "f, " + val[k] + "f, " + val[k + 1] + "f);"); ctrlPointX = currentX + reflectiveCtrlPointX; ctrlPointY = currentY + reflectiveCtrlPointY; currentX += val[k]; currentY += val[k + 1]; break; case 'T': // Draws a quadratic Bézier curve (reflective control point) reflectiveCtrlPointX = currentX; reflectiveCtrlPointY = currentY; if (previousCmd == 'q' || previousCmd == 't' || previousCmd == 'Q' || previousCmd == 'T') { reflectiveCtrlPointX = 2 * currentX - ctrlPointX; reflectiveCtrlPointY = 2 * currentY - ctrlPointY; } notifyResult("mPath.quadTo(" + reflectiveCtrlPointX + "f, " + reflectiveCtrlPointY + "f, " + val[k] + "f, " + val[k + 1] + "f);"); ctrlPointX = reflectiveCtrlPointX; ctrlPointY = reflectiveCtrlPointY; currentX = val[k]; currentY = val[k + 1]; break; case 'a': // Draws an elliptical arc // (rx ry x-axis-rotation large-arc-flag sweep-flag x y) drawArc(currentX, currentY, val[k + 5] + currentX, val[k + 6] + currentY, val[k], val[k + 1], val[k + 2], val[k + 3] != 0, val[k + 4] != 0); currentX += val[k + 5]; currentY += val[k + 6]; ctrlPointX = currentX; ctrlPointY = currentY; break; case 'A': // Draws an elliptical arc drawArc(currentX, currentY, val[k + 5], val[k + 6], val[k], val[k + 1], val[k + 2], val[k + 3] != 0, val[k + 4] != 0); currentX = val[k + 5]; currentY = val[k + 6]; ctrlPointX = currentX; ctrlPointY = currentY; break; } previousCmd = cmd; } current[0] = currentX; current[1] = currentY; current[2] = ctrlPointX; current[3] = ctrlPointY; current[4] = currentSegmentStartX; current[5] = currentSegmentStartY; }将所有封装的命令解析出来,并通过字符串mPath.quadTo(" + reflectiveCtrlPointX + "f, " + reflectiveCtrlPointY + "f, " + val[k] + "f, " + val[k + 1] + "f)拼装,
最后把此字符串写入文件,从而形成渲染代码,当然是不同类型采用不同的方法拼接。渲染类写好了,就该写入口加载类
if (configuration.generateLoader) { writeJavaLoaderClass(vectorModels, javaClassPackage) }
最终生成的文件代码如下
public class SVGLoader { private static LongSparseArray<Drawable.ConstantState> sPreloadedDrawables; public static void load(Context context) { sPreloadedDrawables = SVGHelper.hackPreloadDrawables(context.getResources()); if (sPreloadedDrawables == null) { return; } add(context, R.drawable.ic_android_red, SVGDrawable.SVGDrawableConstantState.create(new ic_android_red(context))); add(context, R.drawable.ic_android_red_01, SVGDrawable.SVGDrawableConstantState.create(new ic_android_red_01(context))); add(context, R.drawable.ic_android_red_02, SVGDrawable.SVGDrawableConstantState.create(new ic_android_red_02(context))); add(context, R.drawable.ic_android_red_03, SVGDrawable.SVGDrawableConstantState.create(new ic_android_red_03(context))); add(context, R.drawable.ic_android_red_04, SVGDrawable.SVGDrawableConstantState.create(new ic_android_red_04(context))); add(context, R.drawable.ic_android_red_05, SVGDrawable.SVGDrawableConstantState.create(new ic_android_red_05(context))); add(context, R.drawable.ic_android_red_06, SVGDrawable.SVGDrawableConstantState.create(new ic_android_red_06(context))); add(context, R.drawable.ic_android_red_07, SVGDrawable.SVGDrawableConstantState.create(new ic_android_red_07(context))); add(context, R.drawable.ic_android_red_08, SVGDrawable.SVGDrawableConstantState.create(new ic_android_red_08(context))); add(context, R.drawable.ic_android_red_09, SVGDrawable.SVGDrawableConstantState.create(new ic_android_red_09(context))); add(context, R.drawable.ic_android_red_10, SVGDrawable.SVGDrawableConstantState.create(new ic_android_red_10(context))); add(context, R.drawable.ic_android_red_11, SVGDrawable.SVGDrawableConstantState.create(new ic_android_red_11(context))); add(context, R.drawable.ic_android_red_12, SVGDrawable.SVGDrawableConstantState.create(new ic_android_red_12(context))); add(context, R.drawable.ic_android_red_13, SVGDrawable.SVGDrawableConstantState.create(new ic_android_red_13(context))); add(context, R.drawable.ic_android_red_14, SVGDrawable.SVGDrawableConstantState.create(new ic_android_red_14(context))); add(context, R.drawable.ic_android_red_15, SVGDrawable.SVGDrawableConstantState.create(new ic_android_red_15(context))); add(context, R.drawable.ic_android_red_16, SVGDrawable.SVGDrawableConstantState.create(new ic_android_red_16(context))); add(context, R.drawable.ic_android_red_17, SVGDrawable.SVGDrawableConstantState.create(new ic_android_red_17(context))); add(context, R.drawable.ic_android_red_18, SVGDrawable.SVGDrawableConstantState.create(new ic_android_red_18(context))); add(context, R.drawable.ic_android_red_19, SVGDrawable.SVGDrawableConstantState.create(new ic_android_red_19(context))); add(context, R.drawable.ic_android_red_20, SVGDrawable.SVGDrawableConstantState.create(new ic_android_red_20(context))); add(context, R.drawable.ic_android_red_rotation_01, SVGDrawable.SVGDrawableConstantState.create(new ic_android_red_rotation_01(context))); add(context, R.drawable.ic_android_red_rotation_02, SVGDrawable.SVGDrawableConstantState.create(new ic_android_red_rotation_02(context))); add(context, R.drawable.ic_android_red_scale_01, SVGDrawable.SVGDrawableConstantState.create(new ic_android_red_scale_01(context))); add(context, R.drawable.ic_android_red_scale_02, SVGDrawable.SVGDrawableConstantState.create(new ic_android_red_scale_02(context))); add(context, R.drawable.ic_android_red_scale_03, SVGDrawable.SVGDrawableConstantState.create(new ic_android_red_scale_03(context))); add(context, R.drawable.ic_android_red_scale_04, SVGDrawable.SVGDrawableConstantState.create(new ic_android_red_scale_04(context))); add(context, R.drawable.ic_android_red_translation_01, SVGDrawable.SVGDrawableConstantState.create(new ic_android_red_translation_01(context))); add(context, R.drawable.ic_android_red_translation_02, SVGDrawable.SVGDrawableConstantState.create(new ic_android_red_translation_02(context))); add(context, R.drawable.ic_android_red_translation_03, SVGDrawable.SVGDrawableConstantState.create(new ic_android_red_translation_03(context))); add(context, R.drawable.ic_sample_01, SVGDrawable.SVGDrawableConstantState.create(new ic_sample_01(context))); add(context, R.drawable.ic_sample_02, SVGDrawable.SVGDrawableConstantState.create(new ic_sample_02(context))); add(context, R.drawable.ic_sample_03, SVGDrawable.SVGDrawableConstantState.create(new ic_sample_03(context))); add(context, R.drawable.ic_sample_04, SVGDrawable.SVGDrawableConstantState.create(new ic_sample_04(context))); add(context, R.drawable.ic_sample_05, SVGDrawable.SVGDrawableConstantState.create(new ic_sample_05(context))); add(context, R.drawable.ic_sample_06, SVGDrawable.SVGDrawableConstantState.create(new ic_sample_06(context))); add(context, R.drawable.ic_sample_07, SVGDrawable.SVGDrawableConstantState.create(new ic_sample_07(context))); add(context, R.drawable.ic_sample_08, SVGDrawable.SVGDrawableConstantState.create(new ic_sample_08(context))); add(context, R.drawable.ic_sample_09, SVGDrawable.SVGDrawableConstantState.create(new ic_sample_09(context))); add(context, R.drawable.ic_sample_10, SVGDrawable.SVGDrawableConstantState.create(new ic_sample_10(context))); add(context, R.drawable.ic_sample_11, SVGDrawable.SVGDrawableConstantState.create(new ic_sample_11(context))); add(context, R.drawable.ic_sample_12, SVGDrawable.SVGDrawableConstantState.create(new ic_sample_12(context))); add(context, R.drawable.ic_sample_13, SVGDrawable.SVGDrawableConstantState.create(new ic_sample_13(context))); add(context, R.drawable.ic_sample_14, SVGDrawable.SVGDrawableConstantState.create(new ic_sample_14(context))); add(context, R.drawable.ic_sample_15, SVGDrawable.SVGDrawableConstantState.create(new ic_sample_15(context))); add(context, R.drawable.ic_sample_16, SVGDrawable.SVGDrawableConstantState.create(new ic_sample_16(context))); add(context, R.drawable.ic_sample_17, SVGDrawable.SVGDrawableConstantState.create(new ic_sample_17(context))); add(context, R.drawable.ic_sample_18, SVGDrawable.SVGDrawableConstantState.create(new ic_sample_18(context))); add(context, R.drawable.ic_sample_19, SVGDrawable.SVGDrawableConstantState.create(new ic_sample_19(context))); add(context, R.drawable.ic_sample_20, SVGDrawable.SVGDrawableConstantState.create(new ic_sample_20(context))); add(context, R.drawable.ic_svg_01, SVGDrawable.SVGDrawableConstantState.create(new ic_svg_01(context))); add(context, R.drawable.ic_svg_02, SVGDrawable.SVGDrawableConstantState.create(new ic_svg_02(context))); add(context, R.drawable.ic_svg_03, SVGDrawable.SVGDrawableConstantState.create(new ic_svg_03(context))); add(context, R.drawable.ic_svg_04, SVGDrawable.SVGDrawableConstantState.create(new ic_svg_04(context))); add(context, R.drawable.ic_svg_05, SVGDrawable.SVGDrawableConstantState.create(new ic_svg_05(context))); add(context, R.drawable.ic_svg_06, SVGDrawable.SVGDrawableConstantState.create(new ic_svg_06(context))); add(context, R.drawable.ic_svg_07, SVGDrawable.SVGDrawableConstantState.create(new ic_svg_07(context))); add(context, R.drawable.ic_svg_08, SVGDrawable.SVGDrawableConstantState.create(new ic_svg_08(context))); add(context, R.drawable.ic_svg_09, SVGDrawable.SVGDrawableConstantState.create(new ic_svg_09(context))); add(context, R.drawable.ic_svg_10, SVGDrawable.SVGDrawableConstantState.create(new ic_svg_10(context))); add(context, R.drawable.ic_svg_11, SVGDrawable.SVGDrawableConstantState.create(new ic_svg_11(context))); add(context, R.drawable.ic_svg_12, SVGDrawable.SVGDrawableConstantState.create(new ic_svg_12(context))); } private static void add(Context context, int resId, SVGDrawable.SVGDrawableConstantState state) { sPreloadedDrawables.put(SVGHelper.resKey(context, resId), state); }
所有文件代码的方式类似,都是采用字符拼接的手法,最终朝文件写入字符串,难点在于pathData的解析,不过你对它的定义规则非常了解的话,这个解析看起来也不在话下。所以
说基础是多么的重要,只有基础好了构造自己的框架才更轻松,文件生成好之后就是代码的使用,这里的使用源码分析请看下回分解。
- SVG-Android(gradle插件生成器)源码详解
- SVG-Android(资源替代详解)源码详解
- Gradle自定义插件详解
- Android热修复 — Nuwa Gradle 插件核心源码分析
- Android Nuwa 热修复原理和的gradle插件详解并怎么修改gradle插件
- Gradle android 插件现况
- Android Gradle插件学习
- Android Gradle插件用户指南
- Android Gradle 插件编写
- Android的Gradle插件(3):Gradle杂谈
- Android Gradle和Gradle插件区别
- Android gradle和gradle插件配置
- mybatis-generator自动生成器插件使用详解
- Android Gradle使用详解
- Android Gradle详解
- ANDROID STUDIO Gradle详解
- Android工程gradle详解
- Android中的Gradle详解
- 约束优化方法之拉格朗日乘子法与KKT条件
- dwz tree组件 取得所选择的值
- 社交网络中各类产品形态的分析
- AI经济发动机:阿里云新一代ECS实例全面解析
- 集群新环境遇到表权限问题不能读表解决方案
- SVG-Android(gradle插件生成器)源码详解
- PostgreSQL配置文件--实时统计
- linux基本操作 1
- python中 @property
- Android项目Tab类型主界面大总结 Fragment+TabPageIndicator+ViewPager
- 最少步数
- java相同数据类型的Arrays.sort()方法排序
- 耳机插头4根线的含义和技巧
- Postman使用方法