AnimatedVectorDrawable使用指北

AnimatedVectorDrawable 是干什么用的?

AnimatedVectorDrawable 是干什么用的?看一看官方文檔的解釋。

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

轉(zhuǎn)換成人話大概就是:AnimatedVectorDrawable 就是將用 ObjectAnimator 或者是用 AnimatorSet 定義的動畫運用到 VectorDrawable 上面去。VectorDrawable 是矢量圖片,矢量圖片不會因為縮放而造成失真,并且占用內(nèi)存資源也比傳統(tǒng)的 Drawable要小,如將一組xml 定義的幀動畫設(shè)置到 View 上,不小心是很容易造成 OOM 的。從 API25 開始,AnimatedVectorDrawable 就將動畫放到 RenderThread 去執(zhí)行。這也就是說,即使當我們的主線程任務(wù)很重的時候,AnimatedVectorDrawable 依舊能很流暢的執(zhí)行動畫。

play.gif

這個動畫看起來過度是非常自然的,比直接替換圖片給用戶帶來的體驗提升了很多很多倍。今天就以這個動畫作為例子。

如何構(gòu)建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 繪制的時候相關(guān)。
group 標簽 主要就是控制旋轉(zhuǎn)、縮放、位移等動畫。pivotX,pivotY 設(shè)置作用中心點。
path 標簽就是主要繪制部分了。
其實上面沒寫到的還要一個 clip-path 標簽,用于裁剪,VectorDrawable 相關(guān)的所有標簽及屬性如下圖:

標簽及屬性

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 下定義形變動畫

  • 定義從暫停狀態(tài)到播放狀態(tài)
<?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"
/>
  • 定義從播放狀態(tài)到暫停狀態(tài)
<?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:動畫開始時的形態(tài)
valueTo:動畫結(jié)束時的形態(tài)
注意: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">

    <!-- 變化內(nèi)容 -->
    <target
            android:animation="@animator/path_play_to_pause"
            android:name="play"/>

    <!-- 旋轉(zhuǎn) -->
    <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 關(guān)聯(lián)在一起。

<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 中的使用
示例圖

結(jié)合上圖看下播放按鈕的 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)位置并封閉
對應(yīng)上圖,就是圖中小黑點的位置。

  • pathData 形變時格式要一致?
    起初我也不知道格式一致到底指的是什么,看了很多文章貌似也沒說清楚,指導一步一步實踐才知道。格式一致指的是說 pathData 的點數(shù)要相對應(yīng),比如原始圖是八個點,形變后的圖也需要是八個點。而且,在形變的過程中,前后的點是相對應(yīng)的,也就是說,執(zhí)行動畫時,原始圖的點 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"

原始狀態(tài)就不說了,看下形變后的狀態(tài)。和上面陳述的一樣,形變后的坐標點其實對應(yīng)的是上圖的小交叉點的坐標。
動畫執(zhí)行

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

以此類推,最后就是從播放按鈕形變到暫停按鈕的動畫。

寫在最后

文中若有表述不恰當?shù)牡胤?,請合理指出,共勉?/p>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容