背景
原生SVG图片仅支持简单线条+填色,网页端的SVG或者说常规的SVG,支持颜色渐变及阴影相关效果,Android不支持。
网上搜索找androidsvg-aar支持SVG全标签解析,抱着学习态度分析实现逻辑。
导入
implementation 'com.caverock:androidsvg-aar:1.4'使用
private void loadSvg() {
InputStream inputStream = null;
try {
inputStream = getAssets().open("loading.svg");
SVG svg = SVG.getFromInputStream(inputStream);
ImageView imageView = findViewById(R.id.svg_test);
imageView.setImageDrawable(new PictureDrawable(svg.renderToPicture()));
} catch (IOException e) {
e.printStackTrace();
} catch (SVGParseException e) {
e.printStackTrace();
}
}源码分析
1. 从最终结果开始
imageView.setImageDrawable(new PictureDrawable(svg.renderToPicture()))
通过库中renderToPicture()方法,传Picture对象,最终转换PictureDrawable对象。
PictureDrawable本质上还Drawable,正如同常规SVG图片,其类型对应的VectorDrawable既然
Drawable对象,那么其使用方法共通,如绘制canvas,设置背景等。
2. PictureDrawable
在Picture中进行绘制(类似于canvas画布,后面会说),最终转换为Drawable对象
3. 读取文件
inputStream = getAssets().open("loading.svg");
SVG svg = SVG.getFromInputStream(inputStream);根据源码,可以看出第一步是打开输出流,读取文件信息。
注:涉及输入输出流,尽量在子线程处理,否则容易出现卡顿。
3.1 读取文件数据
3.1.1 准备输出流
准备构建输出流,解析xml资源
public static SVG getFromInputStream(InputStream is) throws SVGParseException{
SVGParser parser = new SVGParser();
return parser.parse(is, enableInternalEntities);
}
SVG parse(InputStream is, boolean enableInternalEntities) throws SVGParseException{
// 省略构建InputStream相关数据
...
// 解析SVG相关数据
parseUsingXmlPullParser(is, enableInternalEntities);
...
return svgDocument;
}3.1.2 解析xml
省略case中的代码,按照xml正常解析方式解析各种标签
private void parseUsingXmlPullParser(InputStream is, boolean enableInternalEntities) throws SVGParseException{
try{
XmlPullParser parser = Xml.newPullParser();
XPPAttributesWrapper attributes = new XPPAttributesWrapper(parser);
parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, false);
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
parser.setInput(is, null);
int eventType = parser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT){
switch(eventType) {
case XmlPullParser.START_DOCUMENT:
case XmlPullParser.START_TAG:
case XmlPullParser.END_TAG:
case XmlPullParser.TEXT:
case XmlPullParser.CDSECT:
case XmlPullParser.DOCDECL:
case XmlPullParser.PROCESSING_INSTRUCTION:
}
eventType = parser.nextToken();
}
endDocument();
}3.1.3 xml标签解析
下面省略标签类型解析,针对某一circle类型简单分析
这里解析标签数据,基本上按照xml的树结构,照样子构建了java对象中的树结构一样,把xml中的信息解析成对象及属性的模式。
private void startElement(String uri, String localName, String qName, Attributes attributes) throws SVGParseException{
switch (elem){
case circle:
circle(attributes); break;
}
}
private void circle(Attributes attributes) throws SVGParseException{
debug("<circle>");
if (currentElement == null)
throw new SVGParseException("Invalid document. Root element must be <svg>");
SVG.Circle obj = new SVG.Circle();
obj.document = svgDocument;
obj.parent = currentElement;
parseAttributesCore(obj, attributes);
parseAttributesStyle(obj, attributes);
parseAttributesTransform(obj, attributes);
parseAttributesConditional(obj, attributes);
parseAttributesCircle(obj, attributes);
currentElement.addChild(obj);
}到这里,基本可以看出 SVG svg = SVG.getFromInputStream(inputStream);这里只做了解析xml成SVG对象的操作,将SVG文件中的标签转换成JAVA对象存储,并没有实际转换Picture。
3.2 生成Picture对象
3.2.1 解析宽高和option属性
无参方法最终走到该方法,省略部分判断宽高逻辑
public Picture renderToPicture(RenderOptions renderOptions){
Box viewBox = (renderOptions != null && renderOptions.hasViewBox()) ? renderOptions.viewBox: rootElement.viewBox;
if (renderOptions != null && renderOptions.hasViewPort()){
float w = renderOptions.viewPort.maxX();
float h = renderOptions.viewPort.maxY();
return renderToPicture( (int) Math.ceil(w), (int) Math.ceil(h), renderOptions );
}
}3.2.2 生成picture对象
可以看到有2个关键代码
1. 此处new 了一个 Picture对象,并在结束时将该对象传回,该对象包含最终要显示drawable信息
2. Picture中获取canvas对象,最终的绘制都将canvas中进行,类似于onDraw时进行的绘制
public Picture renderToPicture(int widthInPixels, int heightInPixels, RenderOptions renderOptions){
Picture picture = new Picture();
Canvas canvas = picture.beginRecording(widthInPixels, heightInPixels);
if (renderOptions null || renderOptions.viewPort null) {
renderOptions = (renderOptions == null) ? new RenderOptions() : new RenderOptions(renderOptions);
renderOptions.viewPort(0f, 0f, (float) widthInPixels, (float) heightInPixels);
}
SVGAndroidRenderer renderer = new SVGAndroidRenderer(canvas, this.renderDPI);
renderer.renderDocument(this, renderOptions);
picture.endRecording();
return picture;
}3.2.3 遍历,准备绘制canvas
前后逻辑基本都是在做属性校验,关键位置render(rootObj, viewPort, viewBox, preserveAspectRatio)这一行代码。
void renderDocument(SVG document, RenderOptions renderOptions){
...
// Initialise the state
resetState();
checkXMLSpaceAttribute(rootObj);
// Save state
statePush();
Box viewPort = new Box(renderOptions.viewPort);
// If root element specifies a width, then we need to adjust our default viewPort that was based on the canvas size
if (rootObj.width != null)
viewPort.width = rootObj.width.floatValue(this, viewPort.width);
if (rootObj.height != null)
viewPort.height = rootObj.height.floatValue(this, viewPort.height);
// !!重点!!
// Render the document
render(rootObj, viewPort, viewBox, preserveAspectRatio);
// Restore state
statePop();
if (renderOptions.hasCss())
document.clearRenderCSSRules();
}这里开始遍历对象数据,取出xml树状结构中的子元素进行绘制
private void render(SVG.Svg obj, Box viewPort, Box viewBox, PreserveAspectRatio positioning){
// 省略校验,根据属性进行画布平移缩放等逻辑
...
renderChildren(obj, true);
...
}开始根据类型进行真正的绘制,这里可以看到这里支持很多,例如渐变,文字等svg常用类型
private void renderChildren(SvgContainer obj, boolean isContainer) {
if (isContainer) {
parentPush(obj);
}
for (SVG.SvgObject child: obj.getChildren()) {
render(child);
}
if (isContainer) {
parentPop();
}
}
// 绘制子元素
private void render(SVG.SvgObject obj){
if (obj instanceof SVG.Svg) {
render((SVG.Svg) obj);
} else if (obj instanceof SVG.Use) {
render((SVG.Use) obj);
} else if (obj instanceof SVG.Switch) {
render((SVG.Switch) obj);
} else if (obj instanceof SVG.Group) {
render((SVG.Group) obj);
} else if (obj instanceof SVG.Image) {
render((SVG.Image) obj);
} else if (obj instanceof SVG.Path) {
render((SVG.Path) obj);
} else if (obj instanceof SVG.Rect) {
render((SVG.Rect) obj);
} else if (obj instanceof SVG.Circle) {
render((SVG.Circle) obj);
} else if (obj instanceof SVG.Ellipse) {
render((SVG.Ellipse) obj);
} else if (obj instanceof SVG.Line) {
render((SVG.Line) obj);
} else if (obj instanceof SVG.Polygon) {
render((SVG.Polygon) obj);
} else if (obj instanceof SVG.PolyLine) {
render((SVG.PolyLine) obj);
} else if (obj instanceof SVG.Text) {
render((SVG.Text) obj);
}
}3.2.4 绘制canvas
其中一个方法进行举例,可以看到最终调用到canvas.drawPath方法,底层还是使用了canvas进行绘制
private void render(SVG.Circle obj){
//省略
...
Path path = makePathAndBoundingBox(obj);
updateParentBoundingBox(obj);
checkForGradientsAndPatterns(obj);
checkForClipPath(obj);
boolean compositing = pushLayer();
if (state.hasFill)
doFilledPath(obj, path);
if (state.hasStroke)
doStroke(path);
if (compositing)
popLayer(obj);
}
// 其中一个绘制举例
private void doFilledPath(SvgElement obj, Path path){
// First check for pattern fill. It requires special handling.
if (state.style.fill instanceof SVG.PaintReference){
SVG.SvgObject ref = document.resolveIRI(((SVG.PaintReference) state.style.fill).href);
if (ref instanceof SVG.Pattern) {
SVG.Pattern pattern = (SVG.Pattern)ref;
fillWithPattern(obj, path, pattern);
return;
}
}
// Otherwise do a normal fill
canvas.drawPath(path, state.fillPaint);
}3.2.5 完成picture对象生成
Picture picture = new Picture();
Canvas canvas = picture.beginRecording(widthInPixels, heightInPixels);
renderer.renderDocument(this, renderOptions);
picture.endRecording();这是一套完整的picture生成方法,beginRecording到 canvas绘制,endRecording完成picture对象的填充。
总结
使用类原生的方式进行svg解析,支持更多标签,并且仍然可以保证不会失真。
优点:
支持完整svg标签,效果和其他端显示效果一致
缺点:
1. 不支持res资源引入的方式使用,只能java代码解析。
2. 涉及输入输出流,存在性能问题。