17、动画与矢量图:VectorDrawable基础、矢量动画(AnimatedVectorDrawable)、路径动画(Path Morphing)
说实话,Android 动画这块,我最早接触的是帧动画——就是一堆 PNG 图片轮流播放。那时候做个小动画,APK 体积直接飙上去。后来接触了 VectorDrawable,才觉得「嗯,这才是正道」。矢量图不占空间,还能随意缩放,配合动画简直绝配。
这一章,我们就来聊聊矢量图动画。我会从最基础的 VectorDrawable 讲起,再到 AnimatedVectorDrawable,最后带你玩转路径变形动画(Path Morphing)。这些都是我实际项目中反复用到的技术,希望能帮你少走弯路。
17.1 VectorDrawable 基础
VectorDrawable 说白了就是一套用 XML 描述的矢量图。它不依赖具体像素,所以适配不同屏幕密度时,你不需要准备多套图。我个人习惯把常用的图标都转成 VectorDrawable,一个文件搞定所有分辨率。
17.1.1 基本语法
一个最简单的 VectorDrawable 长这样:
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M12,2L2,22L22,22Z" />
</vector>
这里有几个关键点:
- viewportWidth / viewportHeight:定义画布坐标系,相当于一个虚拟的绘图空间。
- width / height:实际显示尺寸,单位通常是 dp。
- pathData:路径数据,语法和 SVG 的 d 属性一致。
17.1.2 支持的图形元素
VectorDrawable 支持以下几种元素:
| 元素 | 说明 | 常用属性 |
|---|---|---|
| <path> | 路径,最核心的元素 | pathData, fillColor, strokeColor, strokeWidth |
| <group> | 分组,可以统一做变换 | translateX, translateY, rotation, scaleX, scaleY |
| <clip-path> | 裁剪路径 | pathData |
我在项目中遇到过一个问题:当图标包含多个路径时,最好用 <group> 把它们包起来。这样后续做动画时,可以统一控制旋转或缩放,省去很多麻烦。
17.2 AnimatedVectorDrawable:让矢量图动起来
AnimatedVectorDrawable 是 VectorDrawable 的动画版本。它通过 ObjectAnimator 来控制路径的属性变化。说白了,就是把静态的矢量图变成动态的。
17.2.1 基本结构
一个 AnimatedVectorDrawable 需要三个文件:
- 矢量图文件(vector.xml)
- 动画定义文件(animator.xml)
- 动画矢量图文件(avd.xml)
举个例子,让一个箭头从左边滑到右边:
vector.xml:
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="64"
android:viewportHeight="64">
<group android:name="arrowGroup">
<path
android:name="arrowPath"
android:fillColor="#FF6200EE"
android:pathData="M10,32 L54,32" />
</group>
</vector>
animator_translate.xml:
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:propertyName="translateX"
android:valueFrom="0"
android:valueTo="20"
android:duration="300"
android:interpolator="@android:anim/accelerate_decelerate_interpolator" />
avd.xml:
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/vector">
<target
android:name="arrowGroup"
android:animation="@animator/animator_translate" />
</animated-vector>
17.2.2 在代码中使用
在 Kotlin 中启动动画很简单:
val avd = AppCompatResources.getDrawable(context, R.drawable.avd_arrow) as AnimatedVectorDrawable
imageView.setImageDrawable(avd)
avd.start()
嗯,这里要注意:AnimatedVectorDrawable 的 start() 方法只能在 UI 线程调用。如果你在后台线程调用,它会静默失败,不会报错。我当初就被这个坑过。
17.3 路径动画(Path Morphing)
路径动画是矢量动画里最酷的部分。它能让一个形状平滑地变成另一个形状。比如,一个圆形变成方形,或者一个箭头变成对勾。
17.3.1 核心原理
路径动画的原理其实不复杂:
- 两个路径必须有相同数量的命令和参数
- 动画过程中,系统会插值计算中间帧的路径数据
- 最终实现平滑变形效果
说白了,就是路径数据里的每个坐标点,从起始位置线性移动到结束位置。
17.3.2 实战:箭头变对勾
来看一个我实际做过的例子。一个箭头图标,点击后变成对勾:
vector.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="morphPath"
android:strokeColor="#FF6200EE"
android:strokeWidth="3"
android:strokeLineCap="round"
android:pathData="M12,24 L36,24" />
</vector>
animator_morph.xml:
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:propertyName="pathData"
android:valueFrom="M12,24 L36,24"
android:valueTo="M16,24 L24,32 L36,16"
android:duration="400"
android:interpolator="@android:anim/accelerate_decelerate_interpolator" />
这里的关键是:起始路径 M12,24 L36,24 是一条直线,结束路径 M16,24 L24,32 L36,16 是一个折线。因为两者都有 3 个点(M 和两个 L),所以可以平滑变形。
17.3.3 路径动画的避坑指南
我曾经在项目中做过一个复杂的路径变形动画,结果在低端手机上卡顿明显。后来发现是路径点太多导致的。我的建议是:
- 路径点控制在 10 个以内,性能最好
- 如果必须用复杂路径,考虑用 Lottie 替代
- 动画时长不要超过 500ms,否则用户会觉得拖沓
17.4 知识体系总览
为了让你更直观地理解本章的知识结构,我画了一张图:
17.5 性能与最佳实践
矢量动画虽然好,但也不是万能的。我总结了几条经验:
- 避免过度绘制: 矢量图在渲染时是按路径逐条绘制的,路径越多,性能越差。我一般控制在 10 条路径以内。
- 慎用复杂路径: 如果路径包含大量贝塞尔曲线,建议用 PNG 代替。矢量图在复杂路径下的渲染开销并不低。
- 动画帧率: 路径动画的帧率受路径复杂度影响。如果发现掉帧,可以尝试减少路径点,或者降低动画时长。
- 兼容性: AnimatedVectorDrawable 在 Android 5.0 以上原生支持。如果你需要兼容更低版本,可以用 support 库的
androidx.vectordrawable。
好了,这一章的内容就到这里。矢量图动画是个很实用的技能,尤其是在做 UI 动效时,能让你的 App 看起来更精致。记住,关键是要理解路径数据的结构,以及动画插值的原理。多动手试试,你会发现其实没那么难。
公众号:蓝海资料掘金营,微信deep3321