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 属性一致。
我的小技巧: 如果你不熟悉 pathData 的语法,可以用 Android Studio 自带的 Vector Asset 工具导入 SVG 文件,它会自动帮你生成 XML。我刚开始学的时候,就是靠这个工具入门的。

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 需要三个文件:

  1. 矢量图文件(vector.xml)
  2. 动画定义文件(animator.xml)
  3. 动画矢量图文件(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>
注意: target 的 name 必须和 vector 文件中 group 或 path 的 name 完全一致。我曾经因为大小写写错,调试了半天才发现问题。

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 知识体系总览

为了让你更直观地理解本章的知识结构,我画了一张图:

矢量图动画知识体系 矢量图动画 VectorDrawable 基础 pathData / group / clip-path viewport 坐标系 AnimatedVectorDrawable ObjectAnimator 控制属性 target 绑定机制 路径动画 Path Morphing 路径点插值计算 命令数量必须一致 核心:轻量、可缩放、平滑变形

17.5 性能与最佳实践

矢量动画虽然好,但也不是万能的。我总结了几条经验:

  • 避免过度绘制: 矢量图在渲染时是按路径逐条绘制的,路径越多,性能越差。我一般控制在 10 条路径以内。
  • 慎用复杂路径: 如果路径包含大量贝塞尔曲线,建议用 PNG 代替。矢量图在复杂路径下的渲染开销并不低。
  • 动画帧率: 路径动画的帧率受路径复杂度影响。如果发现掉帧,可以尝试减少路径点,或者降低动画时长。
  • 兼容性: AnimatedVectorDrawable 在 Android 5.0 以上原生支持。如果你需要兼容更低版本,可以用 support 库的 androidx.vectordrawable
我的习惯: 在项目里,我会把常用的图标动画封装成自定义 View。这样在多个页面复用时,只需要改几个参数就行。比如一个加载动画,我可以控制它的颜色、大小和速度,而不需要重复写动画文件。

好了,这一章的内容就到这里。矢量图动画是个很实用的技能,尤其是在做 UI 动效时,能让你的 App 看起来更精致。记住,关键是要理解路径数据的结构,以及动画插值的原理。多动手试试,你会发现其实没那么难。


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