32、矢量图与SVG:VectorDrawable使用、AnimatedVectorDrawable、SVG转Vector

说实话,做Android开发这么多年,我踩过最大的坑之一就是图片适配。早期项目里塞了几百张PNG,不同分辨率各一套,APK体积直接飙到30多M。后来全面转向矢量图,APK瘦身效果立竿见影。今天咱们就聊聊Android里的矢量图方案——VectorDrawable。

为什么Android需要矢量图?

你想想看,一张100x100的图标,放到300dpi的屏幕上,PNG得准备三套图。而矢量图呢?一个XML文件搞定所有分辨率。它不依赖像素,而是通过数学路径来描述图形。说白了,就是一套坐标点+绘制指令。

我在项目中遇到过最典型的场景:App需要支持平板和手机,图标在不同屏幕密度下都得清晰。用PNG的话,光图标资源就得翻三倍。换成VectorDrawable后,一个文件走天下,省心多了。

核心优势:
  • 文件极小:一个复杂图标通常只有1-3KB
  • 无损缩放:任意尺寸都不会模糊
  • 支持动画:配合AnimatedVectorDrawable实现流畅过渡
  • 减少APK体积:尤其适合多语言、多屏幕适配

VectorDrawable基础用法

先看一个最简单的例子。一个红色的圆形,用VectorDrawable怎么写?

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="48dp"
    android:height="48dp"
    android:viewportWidth="48"
    android:viewportHeight="48">
    <path
        android:fillColor="#FF0000"
        android:pathData="M24,24m-20,0a20,20 0,1 1,40 0a20,20 0,1 1,-40 0"/>
</vector>

这里有两个关键属性:viewportWidth/Height定义的是逻辑坐标系,width/height是实际显示尺寸。pathData里的指令跟SVG的d属性几乎一样。M是移动到,m是相对移动,a是弧线。嗯,这里要注意:viewport的数值不一定要跟实际尺寸一致,但比例要匹配,否则图形会变形。

我个人习惯把viewport设成100的整数倍,这样pathData里的坐标好算。比如图标设计在100x100的网格上,直接抄坐标就行。

pathData指令速查表

指令 含义 示例
M / m 移动到(绝对/相对) M10,10
L / l 画直线 L20,30
C / c 三次贝塞尔曲线 C10,20 30,40 50,60
Q / q 二次贝塞尔曲线 Q15,25 40,50
A / a 弧线 A10,10 0 0,1 20,20
Z / z 闭合路径 Z
小技巧:如果你不熟悉pathData,可以用SVG导出工具。Sketch、Figma、Illustrator都支持导出SVG,然后通过工具转成VectorDrawable格式。我常用的是Android Studio自带的Vector Asset工具,直接导入SVG文件。

SVG转VectorDrawable

实际开发中,我们很少手写pathData。设计师给的一般是SVG文件。怎么转成Android能用的VectorDrawable?

我推荐三种方式:

  1. Android Studio内置工具:右键res/drawable → New → Vector Asset → Local SVG。最省事,但有些SVG特性不支持(比如渐变、滤镜)。
  2. 在线转换工具:比如svg2vector.com。适合批量转换,但要注意版权问题,别把敏感设计图传上去。
  3. 命令行工具:用svgsync或自己写脚本。适合CI/CD流程,自动化处理。

我曾经接手过一个老项目,里面混用了PNG和SVG,设计师给的是Sketch源文件。我写了个Python脚本,批量导出SVG再转成VectorDrawable,一天搞定了原来需要一周的适配工作。

注意:不是所有SVG都能完美转换。以下情况容易出问题:
  • 使用了CSS样式而非内联属性
  • 包含位图嵌入(比如base64图片)
  • 使用了复杂的滤镜或蒙版
  • 文本元素(Android不支持SVG文本转路径)
遇到这些情况,要么让设计师简化,要么用PNG兜底。

AnimatedVectorDrawable:让图标动起来

静态矢量图只是开胃菜,真正的硬菜是动画。AnimatedVectorDrawable可以让你对矢量图的路径、颜色、旋转等属性做动画。

它需要三个文件配合:

  1. VectorDrawable静态图
  2. ObjectAnimator动画定义
  3. AnimatedVectorDrawable将它们关联起来

看个例子:一个箭头从右向左移动的动画。

第一步:定义静态矢量图 arrow.xml

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="48dp"
    android:height="48dp"
    android:viewportWidth="48"
    android:viewportHeight="48">
    <path
        android:name="arrow"
        android:fillColor="#333333"
        android:pathData="M8,24 L40,24 M32,16 L40,24 L32,32"/>
</vector>

第二步:定义动画 animator/arrow_anim.xml

<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:propertyName="translateX"
    android:valueFrom="0"
    android:valueTo="-20"
    android:duration="500"
    android:repeatCount="infinite"
    android:repeatMode="reverse"/>

第三步:关联起来 drawable/animated_arrow.xml

<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/arrow">
    <target
        android:name="arrow"
        android:animation="@animator/arrow_anim"/>
</animated-vector>

然后在代码里这样用:

val iv = findViewById<ImageView>(R.id.iv_arrow)
iv.setImageResource(R.drawable.animated_arrow)
val anim = iv.drawable as AnimatedVectorDrawable
anim.start()

为什么动画要拆成三个文件?说白了,这是Android的设计哲学——关注点分离。静态图归静态图,动画归动画,组合归组合。这样你可以复用同一个矢量图,配上不同的动画效果。

可动画的属性:
  • fillColor / strokeColor:颜色渐变
  • translateX / translateY:平移
  • scaleX / scaleY:缩放
  • rotation:旋转
  • trimPathStart / trimPathEnd:路径裁剪(类似进度条效果)
  • pathData:路径变形(需要起始和结束路径的指令数一致)

知识体系结构图

下面这张图梳理了本章的核心脉络,从矢量图基础到动画实现,再到实际开发中的工具链:

VectorDrawable 知识体系 矢量图核心概念 VectorDrawable AnimatedVectorDrawable SVG 转换工具 pathData 指令 M/L/C/Q/A/Z 等 动画属性 颜色/平移/缩放/路径裁剪 转换方式 AS工具 / 在线 / 命令行 ⚠ 注意:渐变、滤镜、CSS样式等SVG特性可能不兼容

实际项目中的避坑指南

讲几个我踩过的坑,希望能帮你省点时间。

坑一:路径变形动画的指令数必须一致

我曾经做一个加载动画,想让一个圆形渐变成对勾。结果动画跑起来直接崩了。查了半天才发现,圆形的pathData用了4条贝塞尔曲线,对勾只用了3条直线。指令数不一样,AnimatedVectorDrawable直接抛异常。解决办法:让设计师把两个图形的路径拆成相同数量的指令段。

坑二:VectorDrawable在低版本上的兼容性

VectorDrawable从API 21开始原生支持。如果你要兼容API 19以下,得用AppCompat库。但注意,AppCompat的VectorDrawable不支持所有特性,比如android:fillAlpha在某些版本上无效。我的建议是:如果minSdk低于21,尽量用简单的纯色矢量图,别玩透明度渐变。

坑三:过度使用矢量图导致UI卡顿

矢量图虽然文件小,但渲染开销比PNG大。因为每次绘制都要重新计算路径。如果你在列表里用了大量复杂的矢量图,滑动时可能会掉帧。我一般遵循这个原则:图标类(路径少于20条)用矢量图,复杂插画(路径超过50条)用WebP或PNG。

性能建议:
  • 避免在RecyclerView中频繁启动AnimatedVectorDrawable
  • 复杂矢量图(超过100条路径)考虑用位图代替
  • 动画时长控制在300ms-600ms之间,太短看不清,太长显得拖沓
  • 使用android:fillType="evenOdd"处理镂空图形时,注意渲染顺序

总结一下

VectorDrawable是Android开发中处理图标的利器。它让APK瘦身、适配简单、还能做流畅动画。但也不是万能的——复杂图形、低版本兼容、性能开销都是需要权衡的点。

我个人建议:图标、按钮、简单装饰图用矢量图;照片、复杂插画、渐变丰富的图形用位图。两者结合,才是最优解。

好了,矢量图这块就聊到这儿。记住一点:工具是死的,思路是活的。别为了用矢量图而用矢量图,合适才是最好的。


公众号:蓝海资料掘金营,微信deep3321