AnimatedVectorDrawable使用指北

AnimatedVectorDrawable 是干什么用的?

AnimatedVectorDrawable 是干什么用的?看一看官方文档的解释。

This class animates properties of a VectorDrawable with animations defined using ObjectAnimator or AnimatorSet

转换成人话大概就是:AnimatedVectorDrawable 就是将用 ObjectAnimator 或者是用 AnimatorSet 定义的动画运用到 VectorDrawable 上面去。VectorDrawable 是矢量图片,矢量图片不会因为缩放而造成失真,并且占用内存资源也比传统的 Drawable要小,如将一组xml 定义的帧动画设置到 View 上,不小心是很容易造成 OOM 的。从 API25 开始,AnimatedVectorDrawable 就将动画放到 RenderThread 去执行。这也就是说,即使当我们的主线程任务很重的时候,AnimatedVectorDrawable 依旧能很流畅的执行动画。

play.gif

这个动画看起来过度是非常自然的,比直接替换图片给用户带来的体验提升了很多很多倍。今天就以这个动画作为例子。

如何构建AnimatedVectorDrawable?

1. 在 res/drawable 下定义 VectorDrawable

  • 先定义一个播放按钮的VectorDrawable
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="100dp"
        android:height="100dp"
        android:viewportHeight="10"
        android:viewportWidth="10">

    <group
            android:name="playgroup"
            android:pivotX="5"
            android:pivotY="5">

        <path
                android:name="play"
                android:fillColor="#fff"
                android:pathData="M 3,2 L 7,5 L7,5 L3,5z M 3,8 L7,5 L7,5 L3,5z"/>
    </group>

</vector>
play.png
  • 定义一个暂停的 VectorDrawable
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="100dp"
        android:height="100dp"
        android:viewportHeight="10"
        android:viewportWidth="10">

    <group
            android:name="pausegroup"
            android:pivotX="5"
            android:pivotY="5">

        <path
                android:name="pause"
                android:fillColor="#fff"
                android:pathData="M 2,2 L 2,8 L4,8 L4,2z M 6,2 L6,8 L8,8 L8,2z"/>
   </group>

</vector>
pause.png

这里看到 VectorDrawable 有vector、group、path 三个标签。
vector 标签 的 width 和 heigh 就是宽高。这里解释下 viewportHeight/Widht ,这两个相当于将宽高划分为多少份。例如我这里写的,宽是 100dp,viewportWidth 是 100,意思就是将宽度划分为 100 份,每份 10dp。这个划分挺重要的,会直接和 path 绘制的时候相关。
group 标签 主要就是控制旋转、缩放、位移等动画。pivotX,pivotY 设置作用中心点。
path 标签就是主要绘制部分了。
其实上面没写到的还要一个 clip-path 标签,用于裁剪,VectorDrawable 相关的所有标签及属性如下图:

标签及属性

2. 在 res/animator 下定义属性动画

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
                android:duration="1000"
                android:propertyName="rotation"
                android:valueType="floatType"
                android:valueFrom="0"
                android:valueTo="-90"/>

3. 在 res/animator 下定义形变动画

  • 定义从暂停状态到播放状态
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
                android:duration="500"
                android:valueType="pathType"
                android:propertyName="pathData"
                android:valueFrom="M 2,2 L 2,8 L4,8 L4,2z M 8,2 L8,8 L6,8 L6,2z"
                android:valueTo="M 2,3 L 5,7 L5,7 L5,3z M 8,3 L5,7 L5,7 L5,3z"
/>
  • 定义从播放状态到暂停状态
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
                android:duration="500"
                android:valueType="pathType"
                android:propertyName="pathData"
                android:valueFrom="M 3,2 L 7,5 L7,5 L3,5z M 3,8 L7,5 L7,5 L3,5z"
                android:valueTo="M 2,2 L 8,2 L8,4 L2,4z M 2,8 L8,8 L8,6 L2,6z"
/>

valueFrom:动画开始时的形态
valueTo:动画结束时的形态
注意:valueFrom 和 valueTo 的值的形式要一样,不然运行会报错

4. 在 res/drawable 下定义 AnimatedVectorDrawable

<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
                 android:drawable="@drawable/ic_play_arrow_black_24dp">

    <!-- 变化内容 -->
    <target
            android:animation="@animator/path_play_to_pause"
            android:name="play"/>

    <!-- 旋转 -->
    <target
            android:animation="@animator/rotation_pause"
            android:name="playgroup"/>

</animated-vector>

注意:这里的 name 要和前面定义的 VectorDrawable 定义的 name 一直,不然会报错找不到资源。

如何使用 AnimatedVectorDrawable

  • xml 代码
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:background="@color/colorPrimary"
        android:layout_height="match_parent"
        tools:context=".DrawableActivity">

    <ImageView
            android:id="@+id/imageNormal"
            app:layout_constraintBottom_toTopOf="@id/imageMix"
            app:srcCompat="@drawable/play_pause"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

    <ImageView
            android:id="@+id/imageMix"
            app:layout_constraintTop_toBottomOf="@id/imageNormal"
            app:srcCompat="@drawable/mix_play_pause"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

</android.support.constraint.ConstraintLayout>
  • DrawableActivity 片段
private var play = false
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_vector_drawable)

        val action = {

            if (!play) {
                imageNormal.setImageResource(R.drawable.play_pause)
                imageMix.setImageResource(R.drawable.mix_play_pause)
            } else {
                imageNormal.setImageResource(R.drawable.pause_play)
                imageMix.setImageResource(R.drawable.mix_pause_play)
            }
            play = !play
            (imageNormal.drawable as AnimatedVectorDrawable).start()
            (imageMix.drawable as AnimatedVectorDrawable).start()
        }

        imageNormal.setOnClickListener { action() }
        imageMix.setOnClickListener { action() }

    }

以上就是 AnimatedVectorDrawable 的简单使用。
其实除了上面的方式,我们还可以用一个 xml 文件搞定 AnimatedVectorDrawable。因为 AAPT 工具的支持,我们可以将多个 xml 关联在一起。

<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
                 xmlns:aapt="http://schemas.android.com/aapt">
    <aapt:attr name="android:drawable">
        <vector
                android:height="100dp"
                android:width="100dp"
                android:viewportHeight="10"
                android:viewportWidth="10">
            <group
                    android:name="rotationGroup"
                    android:pivotX="5"
                    android:pivotY="5">
                <path
                        android:name="v"
                        android:fillColor="#fff"
                        android:pathData="M 3,2 L 7,5 L7,5 L3,5z M 3,8 L7,5 L7,5 L3,5z"/>
            </group>
        </vector>
    </aapt:attr>

    <target android:name="rotationGroup">
        <aapt:attr name="android:animation">
            <objectAnimator
                    android:duration="1000"
                    android:propertyName="rotation"
                    android:valueFrom="0"
                    android:valueTo="-90"/>
        </aapt:attr>
    </target>

    <target android:name="v">
        <aapt:attr name="android:animation">
            <set>
                <objectAnimator
                        android:duration="500"
                        android:propertyName="pathData"
                        android:valueFrom="M 3,2 L 7,5 L7,5 L3,5z M 3,8 L7,5 L7,5 L3,5z"
                        android:valueTo="M 2,2 L 8,2 L8,4 L2,4z M 2,8 L8,8 L8,6 L2,6z"
                        android:valueType="pathType"/>
            </set>
        </aapt:attr>
    </target>
</animated-vector>

这样,一个 xml 就搞定了前面好几个 xml,太方便了有木有。

One More Thing

  • 如何定义 pathData?
    M/m:位移指令,移动某个点。
    L/l:划线指令,画直线到某个点。
    Z/z:封闭指令,收尾相连封闭起来。
    大写代表绝对位置,小写代表相对位置。
    (0,0)点在画布的左上角。
    这里有个介绍挺好的SVG 的 PathData 在 Android 中的使用
示例图

结合上图看下播放按钮的 pathData。

M 3,2 L 7,5 L7,5 L3,5z M 3,8 L7,5 L7,5 L3,5z

M 3,2 :移动点到(3,2)位置
L 7,5 :画直线到(7,5)位置
L 7,5 :画直线到(7,5)位置
L 3,5z :画直线到(3,5)位置并封闭
对应上图,就是图中小黑点的位置。

  • pathData 形变时格式要一致?
    起初我也不知道格式一致到底指的是什么,看了很多文章貌似也没说清楚,指导一步一步实践才知道。格式一致指的是说 pathData 的点数要相对应,比如原始图是八个点,形变后的图也需要是八个点。而且,在形变的过程中,前后的点是相对应的,也就是说,执行动画时,原始图的点 1 会移动到形变图的点 1 的位置。
android:valueFrom="M 3,2 L 7,5 L7,5 L3,5z M 3,8 L7,5 L7,5 L3,5z"
android:valueTo="M 2,2 L 8,2 L8,4 L2,4z M 2,8 L8,8 L8,6 L2,6z"

原始状态就不说了,看下形变后的状态。和上面陈述的一样,形变后的坐标点其实对应的是上图的小交叉点的坐标。
动画执行

(3,2) -> (2,2)
(7,5) -> (8,2)
(7,5) -> (8,4)

以此类推,最后就是从播放按钮形变到暂停按钮的动画。

写在最后

文中若有表述不恰当的地方,请合理指出,共勉。

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

推荐阅读更多精彩内容