Android 矢量图:加载本地SVG文件并生成Path路径

SVG文件本就是XML格式,因此可以直接进行XML解析,大家的方案都是使用工具(Android Studio 或者在线解析)将SVG转换为Android可识别的Drawable资源文件,但这样就没法去根据需要实时修改了,因此只能从SVG中的path标签入手。

  • 一般SVG文件使用记事本打开后是这些内容:
<?xml version="1.0" encoding="utf-8"?><!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  --><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 48 48" enable-background="new 0 0 48 48" xml:space="preserve">
<path fill="#1565C0" d="M32,5c7.7,0,10,10.6,10,19c0,8.1-3.1,15-8,15s-6-4.4-6-8c0-3.8,1-7.8,1-11c0-4.8-3-6-3-9S28.1,5,32,5z"/>
<path fill="#E39F00" d="M31,16.9l-1.7,1.1c4,5.8,12.1,7.8,12.4,7.9l0.5-1.9C42.1,24,34.5,22.1,31,16.9z"/>
<path fill="#FFC107" d="M41.7,16.3c-0.5-0.3-1.1-0.1-1.4,0.4c0,0.1-3.9,7.3-10.7,8.3c-0.5,0.1-0.9,0.6-0.8,1.1  c0.1,0.5,0.5,0.8,1,0.8c0,0,0.1,0,0.2,0c7.7-1.2,11.9-9.1,12.1-9.4C42.3,17.2,42.2,16.6,41.7,16.3z"/>
<path fill="#90CAF9" d="M32,4c-4.5,0-7,3.6-7,7c0,3,3,5.2,3,9c0,4.8-1,7.3-1,11c0,6.3,2.6,9,7,9c5.8,0,9-7.8,9-16  C43,14.3,40.1,4,32,4z M41,23.1C40.9,28,38.9,30,36.2,30c-6.9,0-4.2-6.8-4.2-8.9c0-5.9-3-8.6-3-11.5c0-1.6,1.1-3.6,3.9-3.6  C40,7,40.9,18.9,41,23.1z"/>
<path fill="#1565C0" d="M22,14c0,3-3,4.2-3,9c0,3.2,1,7.2,1,11c0,3.6-1.1,8-6,8s-8-6.9-8-15C6,18.6,8.3,8,16,8C19.9,8,22,11,22,14z"/>
<path fill="#E39F00" d="M17,19.9l1.7,1.1c-4,5.8-12.1,7.8-12.4,7.9L5.8,27C5.9,27,13.5,25.1,17,19.9z"/>
<path fill="#FFC107" d="M6.3,19.3c0.5-0.3,1.1-0.1,1.4,0.4c0,0.1,3.9,7.3,10.7,8.3c0.5,0.1,0.9,0.6,0.8,1.1c-0.1,0.5-0.5,0.8-1,0.8  c0,0-0.1,0-0.2,0C10.3,28.9,6.1,21,5.9,20.7C5.7,20.2,5.8,19.6,6.3,19.3z"/>
<path fill="#90CAF9" d="M5,27c0,8.2,3.2,16,9,16c4.4,0,7-2.8,7-9c0-3.6-1-6.2-1-11c0-3.8,3-6,3-9c0-3.4-2.5-7-7-7C7.9,7,5,17.3,5,27  z M15.1,9.1c2.7,0,3.9,2,3.9,3.6c0,2.9-3,5.6-3,11.5c0,2,2.7,8.9-4.2,8.9c-2.6,0-4.7-2-4.8-6.9C7.1,21.9,8,10,15.1,9.1z"/>
</svg>
  • 其中path标签的内容就是我们需要的数据,我们使用 DocumentBuilder 和 XPath 来解析此XML,从而得到path对象。
File file = new File("/storage/emulated/0/Tencent/QQfile_recv/more.svg");
        try {
            DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            Document document = builder.parse(file);
            String xpathExpression = "//@*"; // 选择所有节点
            XPathFactory xpf = XPathFactory.newInstance();
            XPath xpath = xpf.newXPath();
            XPathExpression expression = xpath.compile(xpathExpression);
            // 得到整个xml的每个节点
            NodeList svgPaths = (NodeList) expression.evaluate(document, XPathConstants.NODESET);
            ArrayList<Data> pathList = new ArrayList<>();
            for (int i = 0; i < svgPaths.getLength(); i++) {
                // 对应path标签中的d节点
                if (svgPaths.item(i).getNodeName().equals("d")) {
                    Data data = new Data();
                    data.mPath = PathParser.createPathFromPathData(svgPaths.item(i).getTextContent());
                    // 此d节点前面应该是fill节点,fill节点是此路径绘制的颜色
                    if (i > 0 && svgPaths.item(i - 1).getNodeName().equals("fill")) {
                        data.mFillColor = svgPaths.item(i - 1).getTextContent();
                    } else if (i > 0){
                        // d节点的前一个不是fill节点,可能是其他节点,此时往上查找,直到找到fill节点或者再次
                        // 遇到d节点(证明没有fill节点,默认为黑色)为止
                        for (int j = i -1; j >= 0 ; j--) {
                            if (svgPaths.item(j).getNodeName().equals("fill")) {
                                data.mFillColor = svgPaths.item(j).getTextContent();
                                break;
                            } else if (svgPaths.item(j).getNodeName().equals("d")) {
                                // 如果找到了上一个path中的d标签都没有找到fill标签,则代表此path没有fill,为黑色
                                data.mFillColor = "#000000";
                                break;
                            }
                        }
                    }
                    pathList.add(data);
                }
            }
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (XPathExpressionException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        }
    }

    class Data {
         String mFillColor;
         Path mPath;
    }
  • 当然你也可以自己解析d标签的内容,m代表移动,对应path中的moveTo,l代表画线等具体可以查询path的相关方法,但Android自己便可以从Drawable下的XML解析成VectorDrawable,那官方应该有相关的解析方法,全局搜索有关path的信息,果然找到了一个PathParser类。
  • 关于PathParser,这个类原本是在android.support.graphics.drawable下的类,无法访问到,因此直接拷贝出来,使用createPathFromPathData方法便能将d标签下的数据转换为path对象。

通过以上步骤,得到了一个SVG文件的所有path对象,然后根据pathList便可以将矢量图绘制出来。

  • 这里使用一个自定义的IamgeView展示矢量图,在onDraw方法中将path绘制出来即可。
@Override
    public void onDraw(Canvas canvas) {
            canvas.save();
            for (int i = 0; i < pathList.size(); i++) {
                Paint paint = new Paint();
                paint.setColor(Color.parseColor(pathList.get(i).mFillColor));
                canvas.drawPath(pathList.get(i).mPath, paint);
            }
            canvas.restore();
    }
  • 如此便将矢量图绘制到了自定义的IamgeView上,当然也可以直接新建Canvas便可将矢量图按需求进行绘制。
  • 也可以通过此方式将两个或者几个矢量图叠加在一起,每一张SVG最终都生成一个pathList,矢量图的叠加其实也就是path的叠加。

这里对这些path进行一些简单的操作,比如放大,和平移,当然旋转也可以,只要是Matrix拥有的效果均能实现

  • 查看Path这个类,发现有个一addPath(Path src, Matrix matrix)方法,这就完美了,直接可以传入Matrix,再也不用手动计算值了

  • 这里,我重写了ImageView的onTouchEvent方法,当点击时,将SVG图片放大1.1倍,代码如下所示:

@Override
    public boolean onTouchEvent(MotionEvent ev) {
        final int action = ev.getAction();
        switch (action & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN: {
                Matrix matrix = new Matrix();
                matrix.postScale(1.1f,1.1f);
//                matrix.postTranslate(5f,5f);
                for (int i = 0; i < pathList.size(); i++) {
                    Path path = new Path();
                    path.addPath(pathList.get(i).mPath, matrix);
                    pathList.get(i).mPath = path;
                }
                break;
            }
      }
      return true;
}
  • 演示效果如下图:


    加载本地SVG图片并放大.gif

欢迎大家交流指正😁

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容