背景介绍

我们都知道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好用网站

目前发现网站转换的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方法,慢慢学习