AnimatedVectorDrawable使用指北

作者: Wang_Guan | 来源:发表于2019-10-23 11:38 被阅读0次

    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)

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

    写在最后

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

    相关文章

      网友评论

        本文标题:AnimatedVectorDrawable使用指北

        本文链接:https://www.haomeiwen.com/subject/fmoqvctx.html