背景介绍
我们都知道android中支持svg图片,但是到底支持到什么程度,哪些支持哪些不支持,这个问题最近让我反思了起来
根据经验来讲,android中是支持path,group等标签的,但是这两天在通过android studio转换svg成xml的时候遇到了一系列的问题,从png转换的svg图片在转换Vector Asset的时候无法转化,报错,不支持image等标签.
之前确实没有遇到过这个问题,于是在论坛上查了很多博客,但是没有一篇文章能说出来到底不支持哪些标签,支持哪些标签的(也有可能是我没找到),于是放弃吃现成的,准备手撕源码解决,不就是一个xml转换成VectorDrawable吧,无外乎就是xml解析转换,只要找到它解析什么标签,那不解析的不是自动就不支持了吗.说撕就撕.
注: 本文仅仅为了解决支持标签问题,所以对于VectorDrawable整体的绘制流程不阐述,代码也很少,如果有兴趣可以自行梳理.
另:VectorDrawable的类注释其实已经把大概的内容都交代的差不多了,java的javadoc做的还是很nice的,如果想要直接看官方解析,可以直接查看类注释.
VectorDrawable简介
说白了就是一个drawable就是展示图片用的,继承自Drawable,Drawable也很简单,就是一个抽象类,里面会实现一些常用的如Bounds,配置,方向,alpha值,tint着色等参数的配置.
使用时通过使用VectorDrawableCompat中保存的代理mDelegateDrawable实现
经验梳理
当前不支持的标签
- image
当前支持的标签
- path
- clip-path
- group
- defs
踩坑
- 能直接使用svg还是使用svg,线上png转换成svg规则不唯一,且有可能存在不支持标签导致无法使用,或者转换的svg图标过大
- png转换svg好用网站
- https://onlineconvertfree.com/zh/convert-format/png-to-svg/
目前发现网站转换的svg能在AS用的几率很低,应该跟转换算法有关,目前大多数网站的转换都是会将png转换成携带标签的svg,而AS中又无法识别,似乎这条路走到头了,很绝望.
发现了一个神器可用的工具,可以解决上边在线转化svg的问题
- 20220314 发现了一个可以将png转换成svg path的工具名为VectorMagic
该软件可以按照配置将png资源转换成svg,其中转换的算法正好符合AS中对SVG中的要求,即path形式.
有绿色版本,目前正在上传审核中,如果有需要也可以私信我,给你发.如果经济允许,还是支持官网下载正版
源码分析
定位过程忽略,直接上分析
Let‘s go~!
我们在Drawable中可以找到这么一个方法createFromXml
// Drawable.java
// Create a drawable from an XML document.
public static Drawable createFromXml(@NonNull Resources r, @NonNull XmlPullParser parser)
throws XmlPullParserException, IOException {
return createFromXml(r, parser, null);
}
可以看到注释的意思就是通过xml文件创建一个drawable对象, 所以这个方法我们也需要看一下
// Drawable.java
// createXml最终会调用到这个方法中
static Drawable createFromXmlInnerForDensity(@NonNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, int density, @Nullable Theme theme) throws XmlPullParserException, IOException {
// r.getDrawableInflater()方法返回的是DrawableInflater.java对象
return r.getDrawableInflater().inflateFromXmlForDensity(parser.getName(), parser, attrs,
density, theme);
}
// DrawableInflater.java
Drawable inflateFromXmlForDensity(@NonNull String name, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs, int density, @Nullable Theme theme)
throws XmlPullParserException, IOException {
...
// 根据tag查找drawable的具体类型
Drawable drawable = inflateFromTag(name);
if (drawable == null) {
drawable = inflateFromClass(name);
}
// 多态调用具体drawable的inflate()方法
drawable.inflate(mRes, parser, attrs, theme);
return drawable;
}
// 这里就是我们希望看到各种drawable的xml解析了
private Drawable inflateFromTag(@NonNull String name) {
switch (name) {
case "selector":
return new StateListDrawable();
case "animated-selector":
return new AnimatedStateListDrawable();
case "level-list":
return new LevelListDrawable();
case "layer-list":
return new LayerDrawable();
case "transition":
return new TransitionDrawable();
case "ripple":
return new RippleDrawable();
case "adaptive-icon":
return new AdaptiveIconDrawable();
case "color":
return new ColorDrawable();
case "shape":
return new GradientDrawable();
case "vector":
return new VectorDrawable();
case "animated-vector":
return new AnimatedVectorDrawable();
case "scale":
return new ScaleDrawable();
case "clip":
return new ClipDrawable();
case "rotate":
return new RotateDrawable();
case "animated-rotate":
return new AnimatedRotateDrawable();
case "animation-list":
return new AnimationDrawable();
case "inset":
return new InsetDrawable();
case "bitmap":
return new BitmapDrawable();
case "nine-patch":
return new NinePatchDrawable();
case "animated-image":
return new AnimatedImageDrawable();
default:
return null;
}
}
// VectorDrawable.java
public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs, @Nullable Theme theme)
throws XmlPullParserException, IOException {
try {
...
inflateChildElements(r, parser, attrs, theme);
...
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
private void inflateChildElements(Resources res, XmlPullParser parser, AttributeSet attrs,
Theme theme) throws XmlPullParserException, IOException {
final VectorDrawableState state = mVectorState;
boolean noPathTag = true;
// Use a stack to help to build the group tree.
// The top of the stack is always the current group.
// 注释说明的很清楚, 栈就是用来构建节点树结构的
final Stack<VGroup> groupStack = new Stack<VGroup>();
groupStack.push(state.mRootGroup);
int eventType = parser.getEventType();
final int innerDepth = parser.getDepth() + 1;
// Parse everything until the end of the vector element.
while (eventType != XmlPullParser.END_DOCUMENT
&& (parser.getDepth() >= innerDepth || eventType != XmlPullParser.END_TAG)) {
if (eventType == XmlPullParser.START_TAG) {
final String tagName = parser.getName();
final VGroup currentGroup = groupStack.peek();
// <path>节点
if (SHAPE_PATH.equals(tagName)) {
final VFullPath path = new VFullPath();
path.inflate(res, attrs, theme);
currentGroup.addChild(path);
if (path.getPathName() != null) {
state.mVGTargetsMap.put(path.getPathName(), path);
}
noPathTag = false;
state.mChangingConfigurations |= path.mChangingConfigurations;
// <clip-path>节点
} else if (SHAPE_CLIP_PATH.equals(tagName)) {
final VClipPath path = new VClipPath();
path.inflate(res, attrs, theme);
currentGroup.addChild(path);
if (path.getPathName() != null) {
state.mVGTargetsMap.put(path.getPathName(), path);
}
state.mChangingConfigurations |= path.mChangingConfigurations;
// <group>节点
} else if (SHAPE_GROUP.equals(tagName)) {
VGroup newChildGroup = new VGroup();
newChildGroup.inflate(res, attrs, theme);
currentGroup.addChild(newChildGroup);
groupStack.push(newChildGroup);
if (newChildGroup.getGroupName() != null) {
state.mVGTargetsMap.put(newChildGroup.getGroupName(),
newChildGroup);
}
state.mChangingConfigurations |= newChildGroup.mChangingConfigurations;
}
} else if (eventType == XmlPullParser.END_TAG) {
final String tagName = parser.getName();
if (SHAPE_GROUP.equals(tagName)) {
groupStack.pop();
}
}
eventType = parser.next();
}
if (noPathTag) {
final StringBuffer tag = new StringBuffer();
if (tag.length() > 0) {
tag.append(" or ");
}
tag.append(SHAPE_PATH);
throw new XmlPullParserException("no " + tag + " defined");
}
}
到这里我们就知道了 VectorDrawable支持path, clip-path和group.当然 这个只是简单的解析,至于xml中的动画 渐变色这些通用标签VectorDrawable应该也是支持的, 如果后续有需要再查找那些标签在哪里调用,在哪里支持的
由于图片处理所以涉及到很多的内部机制以及jni的调用,具体的精髓还是应该在jni中的native方法,慢慢学习