Android高級動畫(2)

目錄

Android高級動畫(1)http://m.itdecent.cn/p/48554844a2db
Android高級動畫(2)http://m.itdecent.cn/p/89cfd9042b1e
Android高級動畫(3)http://m.itdecent.cn/p/d6cc8d218900
Android高級動畫(4)http://m.itdecent.cn/p/91f8363c3a8c

來點硬貨

前面一篇文章已經(jīng)講了Android中大部分的動畫框架,回顧一下有:Tween動畫,屬性動畫,幀動畫,CircularReveal,Activity轉(zhuǎn)場動畫,5.0新轉(zhuǎn)場動畫,Interpolator插值器,5.0轉(zhuǎn)場動畫分為Explode、Slide、Fade、Share四種模式。合理且充分利用這些動畫,我們已經(jīng)可以做出很多優(yōu)美的效果了。

但是今天這篇文章我們來講講大名鼎鼎的矢量動畫,它顛覆了前面所有的動畫。前面的動畫都是對控件做動畫,而矢量動畫是對圖形做動畫,矢量動畫可以做出前面任何一個動畫框架都做不到的效果。好了,NB就先不吹了,開始我們的學習吧。

從矢量圖形說起

我們平時看到的圖片大多數(shù)都是位圖,英文名叫 bitmap,位圖對應(yīng)的格式就是 .bmp,看過bmp的人都知道,體積那叫一個大啊。。。一張小小的Logo都能2M,于是jpg,png這些壓縮格式就出現(xiàn)了,優(yōu)秀的壓縮算法極大地減少了圖片體積。配合索引位圖、灰度圖等手段,圖片可以壓縮的非常小,世界一下子變得美好~

但是,開心沒多久,問題又來了。不管你的壓縮算法有所優(yōu)秀,位圖有2個天生的缺點無法避免:
(1)圖片放大會失真
(2)圖片尺寸越大,體積越大

不管是做Android開發(fā)還是IOS開發(fā),我們都需要適配不同分辨率的手機,也就意味著同一個ImageView在不同手機上的圖片分辨率是不同的,如果我們只用一套圖片,那必然存在放大失真問題。統(tǒng)一的解決方案就是為每一種分辨屏幕準備一套圖片。這樣失真的問題解決了,但是圖片體積又大了。

似乎兩者是不可兼得的,怎么辦呢?

靚仔

矢量圖登場

矢量圖不同于位圖是用像素描述圖像的,它是用數(shù)學曲線描述圖形。所以一張圖片就是對應(yīng)著一系列的數(shù)學曲線,所以圖片的顯示尺寸和圖片體積無關(guān)。(這里為什么說顯示尺寸,因為矢量圖根本就沒有所謂的尺寸,就看你把它顯示成多大),它的體積就是文本文件的大小。并且矢量圖可以無限拉伸不失真。

先來看一個Android中使用矢量圖的例子:

愛心

哇,這個愛心有點漂亮~

代碼實現(xiàn):

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="256dp"
        android:height="256dp"
        android:viewportHeight="32"
        android:viewportWidth="32">

    <path
        android:fillColor="#e11c00"
        android:pathData="M20.5,9.5 c-1.955,0,-3.83,1.268,-4.5,3
                        c-0.67,-1.732,-2.547,-3,-4.5,-3 C8.957,9.5,7,11.432,7,14
                        c0,3.53,3.793,6.257,9,11.5 c5.207,-5.242,9,-7.97,9,-11.5
                        C25,11.432,23.043,9.5,20.5,9.5z" />
</vector>

PS:這個文件非常小,只有670字節(jié),連1KB都不到。而且我們只需要這一個文件,就可以適配所有分辨率,無限拉伸不失真。)

根節(jié)點是vector,width和height屬性是顯示大小,但是實際上這個大小是可以根據(jù)控件改變的。viewportHeight和viewportWidth也是寬高,它是定義曲線函數(shù)時所參照的寬高。子節(jié)點path就是定義繪制內(nèi)容的,fillColor是填充顏色,pathData是描繪路徑。那么問題來了,pathData中的這一串字母數(shù)字是什么東東?

SVG

說到這,SVG必須得登場了。SVG就是標準的矢量圖格式,Android中使用矢量圖雖然沒有直接使用SVG圖片,但是基本格式是和SVG一樣的。

SVG語法
SVG的語法太復(fù)雜了,這里不可能全部講一遍。為了說明問題,我們就講幾個最基礎(chǔ)的命令。
M:新建起點,參數(shù)x,y(M20, 30)
L:連接直線,參數(shù)x,y(L30, 20)
H:縱坐標不變,橫向連線,參數(shù)x(H20)
V:橫坐標不變,縱向連線,參數(shù)y(V30)
Q:二次貝塞爾曲線,參數(shù)x1,y1,x2,y2(Q10,20,30,40)
C:三次貝塞爾曲線,參數(shù)x1,y1,x2,y2,x3,y3(C10,20,30,40,50, 60)
Z:連接首尾,閉合曲線,無參數(shù)

掌握以上這些基本命令之后,我們基本上就可以畫出90%的圖形了。比如上面demo只用到了三個命令:M、C、Z,我們整個系列所有demo用到的命令也就只有M、L、C、Z。

(至于什么是二次貝塞爾,什么是三次貝塞爾,如果不了解的話請自行百度,不能再拓展了,否則這篇文章要突破萬字了。)

讓矢量圖形動起來

雖然我們已經(jīng)可以繪制漂亮的矢量圖形了,但是我們這個系列是Android高級動畫啊,得動起來,Android中怎樣才能讓矢量圖形動起來呢?

animated-vector登場

animated-vector從名字上看就是動起來的vector,先看示例:

animated-vector

初始顯示的是三條橫線,然后從三條橫線的狀態(tài)變化到箭頭,同時整體旋轉(zhuǎn)360度。

代碼如下:
(1)首先是layout文件,一個普通的ImageView,src指向一個drawable

<ImageView
    android:id="@+id/imgBtn"
    android:layout_width="200dp"
    android:layout_height="200dp"
    android:onClick="startAnim"
    android:src="@drawable/animvectordrawable" />

(2)drawable根節(jié)點是一個animated-vector,drawable參數(shù)用于指定初始顯示的樣子,下面兩個target子節(jié)點用于指定動畫,第一個target是指定了旋轉(zhuǎn)動畫,第二個target指定了path轉(zhuǎn)變動畫。下面我們分別來看下初始的drawable和兩個target。

<!-- animvectordrawable.xml -->
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/vectordrawable" >
    <target
        android:name="rotationGroup"
        android:animation="@anim/rotation" />

    <target
        android:name="v"
        android:animation="@anim/path_morph" />
</animated-vector>

(3)初始drawable,這個根節(jié)點vector就是定義了寬高,第三層path節(jié)點就是初始顯示的矢量圖形,它有填充色和path路徑。

<!-- vectordrawable.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:height="300dp"
    android:width="300dp"
    android:viewportHeight="70"
    android:viewportWidth="70" >

    <group
        android:name="rotationGroup"
        android:pivotX="35"
        android:pivotY="35"
        android:rotation="0.0" >
        <path
            android:name="v"
            android:fillColor="#000000"
            android:pathData="M10,10 L60,10 L60,20 L10,20 Z M10,30 L60,30 
                L60,40 L10,40 Z M10,50 L60,50 L60,60 L10,60 Z" />
    </group>
</vector>

但是外面又包了一層group,這個是干什么用的呢?

animated-vector規(guī)定,可以有多個動畫同時進行,但是一個對象上只能加載一個動畫。上面的例子可以看到三條線圖形轉(zhuǎn)變成箭頭圖形,同時旋轉(zhuǎn)360度,那就要有兩個動畫,一個做path變換,一個做旋轉(zhuǎn)。但是兩個動畫不能同時放在一個對象上,所以必須用group包一層,把path變換動畫放在path對象上,把旋轉(zhuǎn)動畫放在group對象上,從而實現(xiàn)整體的效果。

(4)target1,這就是一個簡單的屬性動畫,旋轉(zhuǎn)360度

<!-- rotation.xml -->
<objectAnimator
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1500"
    android:propertyName="rotation"
    android:valueFrom="0"
    android:valueTo="360" />

(5)target2,這個動畫是最神奇的地方,它用于從一個path變換到另一個path,valueFrom指定變換前的path,valueTo指定變換后的path,propertyName和valueType在這個例子中是固定寫法。

<!-- path_morph.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <objectAnimator
        android:duration="1500"
        android:propertyName="pathData"
        android:valueFrom="M10,10 L60,10 L60,20 L10,20 Z M10,30 L60,30 L60,40 
            L10,40 Z M10,50 L60,50 L60,60 L10,60 Z"
        android:valueTo="M5,35 L40,0 L47.072,7.072 L12.072,42.072 Z M10,30 L60,30 L60,40 
            L10,40 Z M12.072,27.928 L47.072,62.928 L40,70 L5,35 Z"
        android:valueType="pathType" />
</set>

這里需要重點提下valueFrom和valueTo

valueFrom和valueTo分別指定了變換前的path和變換后的path,它要求前后兩個path必須是同形path
PS:如果兩個path擁有相同的命令數(shù),并且對應(yīng)位置的命令符相同,那么這兩個path我們稱之為同形path。
如:
M10,15 L20,20 L25,15 C10,20,20,20,30,10 L50,50 Z
M20,30 L10,10 L15,25 C25,10,30,30,10,20 L35,35 Z

(6)java代碼啟動動畫

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void startAnim(View view) {
    Drawable drawable = imgBtn.getDrawable();
    ((Animatable) drawable).start();
}

至此,一個簡單的animated-vector就完成了,估計有人要吐槽了。
“我的天,一個動畫要寫這么多代碼?”
是的,這一點木有辦法,矢量動畫本身就比較復(fù)雜。但是別傷心,因為更復(fù)雜的還在后面呢。。。

矢量選擇器

animated-vector已經(jīng)很強大了是吧,但是肯定有人發(fā)現(xiàn)問題了,animated-vector只能從一個path變換到另一個path,不能反向再變回來。如果我需要在兩個path之間來回變換該怎么辦呢?

靚仔2

animated-selector登場。

selector我們大家都很熟悉了,用于一個按鈕的點擊效果。animated-selector類似,也是用于兩個狀態(tài)的切換,只不過animated-selector是在兩個path之間來回切換顯示。

先看演示:

PathMorphing

是不是很酷炫!迫不及待地想知道是怎么實現(xiàn)的。

(1)首先是Layout,一個普通的 ImageView,src指向一個 drawable

<ImageView
    android:id="@+id/iv_2"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:scaleType="fitCenter"
    android:src="@drawable/heart_twitter" />

(2)再看drawable,drawbale是一個animated-selector,子節(jié)點是兩個item和兩個transition。

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

    <item android:id="@+id/state_on"
        android:drawable="@drawable/ic_twitter"
        android:state_checked="true"/>

    <item android:id="@+id/state_off"
        android:drawable="@drawable/ic_heart" />

    <transition
        android:fromId="@id/state_off"
        android:toId="@id/state_on"
        android:drawable="@drawable/avd_heart_to_twitter" />

    <transition
        android:fromId="@id/state_on"
        android:toId="@id/state_off"
        android:drawable="@drawable/avd_twitter_to_heart" />
</animated-selector>

兩個item分別指定了兩種狀態(tài)下要顯示的樣子,兩個transition分別指定了當狀態(tài)切換時所做的動畫。

具體來說:第一個item指定的是state_on時顯示的樣子,第二個item指定的是state_off時顯示的樣子。第一個transition指定的是從off切換到on時所做的動畫,第二個transition指定的是從on切換到off時所做的動畫。

下面來分別看下兩個item和兩個transition

(3)兩個item

<?xml version="1.0" encoding="UTF-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="150dp"
        android:height="150dp"
        android:viewportHeight="24.0"
        android:viewportWidth="24.0">

    <group
        android:name="groupTwitter"
        android:pivotX="12"
        android:pivotY="12">

        <path
            android:name="twitter"
            android:fillColor="#C2185B"
            android:pathData="M 22.46,6.0 l 0.0,0.0 C 21.69,6.35,20.86,6.58,20.0,6.69 
            C 20.88,6.16,21.56,5.32,21.88,4.31 c 0.0,0.0,0.0,0.0,0.0,0.0 
            C 21.05,4.81,20.13,5.16,19.16,5.36 C 18.37,4.5,17.26,4.0,16.0,4.0 
            c 0.0,0.0,0.0,0.0,0.0,0.0 L 16.0,4.0 C 13.65,4.0,11.73,5.92,11.73,8.29 
            C 11.73,8.63,11.77,8.96,11.84,9.27 C 8.28,9.09,5.11,7.38,3.0,4.79 
            C 2.63,5.42,2.42,6.16,2.42,6.94 C 2.42,8.43,3.17,9.75,4.33,10.5 
            C 3.62,10.5,2.96,10.3,2.38,10.0 C 2.38,10.0,2.38,10.0,2.38,10.03 
            C 2.38,12.11,3.86,13.85,5.82,14.24 C 5.46,14.34,5.08,14.39,4.69,14.39 
            C 4.42,14.39,4.15,14.36,3.89,14.31 C 4.43,16.0,6.0,17.26,7.89,17.29 
            C 6.43,18.45,4.58,19.13,2.56,19.13 C 2.22,19.13,1.88,19.11,1.54,19.07 
            C 3.44,20.29,5.7,21.0,8.12,21.0 C 16.0,21.0,20.33,14.46,20.33,8.79 
            C 20.33,8.6,20.33,8.42,20.32,8.23 C 21.16,7.63,21.88,6.87,22.46,6.0 L 22.46,6.0"/>
    </group>
</vector>
<?xml version="1.0" encoding="UTF-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="150dp"
        android:height="150dp"
        android:viewportHeight="24.0"
        android:viewportWidth="24.0" >

    <group
        android:name="groupHeart"
        android:pivotX="12"
        android:pivotY="12">

        <path
            android:name="heart"
            android:fillColor="#C2185B"
            android:pathData="M 12.0,21.35 l -1.45,-1.32 C 5.4,15.36,2.0,12.28,2.0,8.5 
            C 2.0,5.42,4.42,3.0,7.5,3.0 c 1.74,0.0,3.41,0.81,4.5,2.09 
            C 13.09,3.81,14.76,3.0,16.5,3.0 C 19.58,3.0,22.0,5.42,22.0,8.5 
            c 0.0,3.78,-3.4,6.86,-8.55,11.54 L 12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 
            C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 
            C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 
            C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 
            C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 
            C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 
            C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 
            C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 
            C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 L 12.0,21.35"/>
    </group>
</vector>

根節(jié)點是一個vector,里面是一個group包著一個path,和前面說的一樣,這個group不是必須的,往往是為了加載動畫而增加的。path是定義路徑。

PS:這里有人可能會有疑問,這些“愛心”、“Twitter”的path是怎么生成的呢?這里先提前簡單地解釋下:對于簡單的圖形,我們可以手動計算,比如上面三條橫線變成箭頭的例子,就是手動計算點坐標的。對于復(fù)雜的圖形,比如Twitter和愛心,手動計算不現(xiàn)實,我們可以找一些輔助軟件來生成。)

(4)兩個transition

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

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

    <target android:name="heart">
        <aapt:attr name="android:animation">
            <objectAnimator
                android:duration="1000"
                android:propertyName="pathData"
                android:valueFrom="M 12.0,21.35 l -1.45,-1.32 C 5.4,15.36,2.0,12.28,2.0,8.5 C 2.0,5.42,4.42,3.0,7.5,3.0 
                c 1.74,0.0,3.41,0.81,4.5,2.09 C 13.09,3.81,14.76,3.0,16.5,3.0 C 19.58,3.0,22.0,5.42,22.0,8.5 
                c 0.0,3.78,-3.4,6.86,-8.55,11.54 L 12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 
                C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 
                C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 
                C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 
                C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 
                C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 
                C 12.0,21.35,12.0,21.35,12.0,21.35 L 12.0,21.35"
                android:valueTo="M 22.46,6.0 l 0.0,0.0 C 21.69,6.35,20.86,6.58,20.0,6.69 C 20.88,6.16,21.56,5.32,21.88,4.31 
                c 0.0,0.0,0.0,0.0,0.0,0.0 C 21.05,4.81,20.13,5.16,19.16,5.36 C 18.37,4.5,17.26,4.0,16.0,4.0 
                c 0.0,0.0,0.0,0.0,0.0,0.0 L 16.0,4.0 C 13.65,4.0,11.73,5.92,11.73,8.29 C 11.73,8.63,11.77,8.96,11.84,9.27 
                C 8.28,9.09,5.11,7.38,3.0,4.79 C 2.63,5.42,2.42,6.16,2.42,6.94 C 2.42,8.43,3.17,9.75,4.33,10.5 
                C 3.62,10.5,2.96,10.3,2.38,10.0 C 2.38,10.0,2.38,10.0,2.38,10.03 C 2.38,12.11,3.86,13.85,5.82,14.24 
                C 5.46,14.34,5.08,14.39,4.69,14.39 C 4.42,14.39,4.15,14.36,3.89,14.31 C 4.43,16.0,6.0,17.26,7.89,17.29 
                C 6.43,18.45,4.58,19.13,2.56,19.13 C 2.22,19.13,1.88,19.11,1.54,19.07 C 3.44,20.29,5.7,21.0,8.12,21.0 
                C 16.0,21.0,20.33,14.46,20.33,8.79 C 20.33,8.6,20.33,8.42,20.32,8.23 C 21.16,7.63,21.88,6.87,22.46,6.0 L 22.46,6.0"
                android:valueType="pathType" />
        </aapt:attr>
    </target>
</animated-vector>
<?xml version="1.0" encoding="utf-8"?>
<animated-vector
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:aapt="http://schemas.android.com/aapt"
    android:drawable="@drawable/ic_twitter">

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

    <target android:name="twitter">
        <aapt:attr name="android:animation">
            <objectAnimator
                android:duration="1000"
                android:propertyName="pathData"
                android:valueFrom="M 22.46,6.0 l 0.0,0.0 C 21.69,6.35,20.86,6.58,20.0,6.69 C 20.88,6.16,21.56,5.32,21.88,4.31 
                c 0.0,0.0,0.0,0.0,0.0,0.0 C 21.05,4.81,20.13,5.16,19.16,5.36 C 18.37,4.5,17.26,4.0,16.0,4.0 
                c 0.0,0.0,0.0,0.0,0.0,0.0 L 16.0,4.0 C 13.65,4.0,11.73,5.92,11.73,8.29 C 11.73,8.63,11.77,8.96,11.84,9.27 
                C 8.28,9.09,5.11,7.38,3.0,4.79 C 2.63,5.42,2.42,6.16,2.42,6.94 C 2.42,8.43,3.17,9.75,4.33,10.5 
                C 3.62,10.5,2.96,10.3,2.38,10.0 C 2.38,10.0,2.38,10.0,2.38,10.03 C 2.38,12.11,3.86,13.85,5.82,14.24 
                C 5.46,14.34,5.08,14.39,4.69,14.39 C 4.42,14.39,4.15,14.36,3.89,14.31 C 4.43,16.0,6.0,17.26,7.89,17.29 
                C 6.43,18.45,4.58,19.13,2.56,19.13 C 2.22,19.13,1.88,19.11,1.54,19.07 C 3.44,20.29,5.7,21.0,8.12,21.0 
                C 16.0,21.0,20.33,14.46,20.33,8.79 C 20.33,8.6,20.33,8.42,20.32,8.23 C 21.16,7.63,21.88,6.87,22.46,6.0 L 22.46,6.0"
                android:valueTo="M 12.0,21.35 l -1.45,-1.32 C 5.4,15.36,2.0,12.28,2.0,8.5 C 2.0,5.42,4.42,3.0,7.5,3.0 
                c 1.74,0.0,3.41,0.81,4.5,2.09 C 13.09,3.81,14.76,3.0,16.5,3.0 C 19.58,3.0,22.0,5.42,22.0,8.5 
                c 0.0,3.78,-3.4,6.86,-8.55,11.54 L 12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 
                C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 
                C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 
                C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 
                C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 
                C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 C 12.0,21.35,12.0,21.35,12.0,21.35 
                C 12.0,21.35,12.0,21.35,12.0,21.35 L 12.0,21.35"
                android:valueType="pathType"/>
        </aapt:attr>
    </target>
</animated-vector>

兩個transition分別定義了從 off 到 on 和從 on 到 off 時切換動畫。

根節(jié)點是一個animated-vector,它有一個重要的屬性drawable,這個屬性指定動畫前的初始狀態(tài),就是一個普通的vector。

下面兩個target是定義動畫,每一個target都有一個name屬性,指定動畫作用于哪個對象上,這個對象就是上面drawable里定義的名字。<aapt:attr>這一層就是固定寫法,我也不知道為什么要定義這一層~[/尷尬]。再下面就是ObjectAnmator,定義具體的動畫。通過propertyName來區(qū)分動畫類型,rotation是旋轉(zhuǎn),pathData是path轉(zhuǎn)換。旋轉(zhuǎn)動畫就不說了,path動畫轉(zhuǎn)換前面也分析過了。

OK,至此我們已經(jīng)把動畫都定義好了。因為比較復(fù)雜,我們再來捋一遍。
(1)首先定義一個animated-selector,它定義兩個item,對應(yīng)兩種狀態(tài)on、off的顯示,再定義兩個transition用于狀態(tài)變化時啟動動畫。
(2)兩個item是vector類型,定義要顯示的path。
(3)兩個transition是animated-vector類型,定義從一個狀態(tài)到另一個狀態(tài)時的動畫,在指定動畫時要注意,一個對象上只能加載一個動畫,如果動畫個數(shù)比對象個數(shù)多,要用group把對象包裹起來。

可是問題來了,這樣只是定義好了動畫,但是還是動不起來啊。因為animated-selector怎么知道View的狀態(tài)變化了呢?所以還差最后一步,把View的狀態(tài)和animated-selector關(guān)聯(lián)起來。

private boolean isTwitterChecked = false;
public void onTwitterClick(View view) {
        isTwitterChecked = !isTwitterChecked;
        final int[] stateSet = {android.R.attr.state_checked * (isTwitterChecked ? 1 : -1)};
        imageView.setImageState(stateSet, true);
    }

好啦,這樣當我們點擊圖片時,通過調(diào)用imageView.setImageState就可以切換狀態(tài),從而切換 Twitter 和 heart 的顯示。再來欣賞下動畫吧。

PathMorphing

trimPath

trimPath也是一種動畫類型,它是通過對路徑的裁剪實現(xiàn)的動畫。先看示例:

TrimPath

效果還是比較酷炫的,代碼實現(xiàn)和上面Twitter基本類似。直接貼代碼:
(1)animated-selector基本和上面類似,就不分析了

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

    <item android:id="@+id/state_on"
          android:drawable="@drawable/bar"
          android:state_checked="true"/>

    <item android:id="@+id/state_off"
          android:drawable="@drawable/search" />

    <transition
        android:fromId="@id/state_off"
        android:toId="@id/state_on"
        android:drawable="@drawable/avd_search_to_bar" />

    <transition
        android:fromId="@id/state_on"
        android:toId="@id/state_off"
        android:drawable="@drawable/avd_bar_to_search" />
</animated-selector>

(2)兩個item也基本類似,也不分析了

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="300dp"
        android:height="48dp"
        android:viewportWidth="150"
        android:viewportHeight="24">

    <path
        android:name="search"
        android:pathData="M141,17 A9,9 0 1,1 142,16 L149,22"
        android:strokeWidth="2"
        android:strokeColor="#f51035"
        android:strokeAlpha="0.8"
        android:strokeLineCap="round"
        android:trimPathStart="1"/>

    <path
        android:name="bar"
        android:pathData="M10,22 L149,22"
        android:strokeWidth="2"
        android:strokeColor="#f51035"
        android:strokeAlpha="0.8"
        android:strokeLineCap="round" />
</vector>
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="300dp"
        android:height="48dp"
        android:viewportWidth="150"
        android:viewportHeight="24">

    <path
        android:name="search"
        android:pathData="M141,17 A9,9 0 1,1 142,16 L149,22"
        android:strokeWidth="2"
        android:strokeColor="#f51035"
        android:strokeAlpha="0.8"
        android:strokeLineCap="round" />

    <path
        android:name="bar"
        android:pathData="M10,22 L149,22"
        android:strokeWidth="2"
        android:strokeColor="#f51035"
        android:strokeAlpha="0.8"
        android:strokeLineCap="round"
        android:trimPathStart="1"/>
</vector>

(3)兩個transition,這里和前面稍有不同。我們拿最后一個分析下。

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

    <target android:name="search">
        <aapt:attr name="android:animation">
            <objectAnimator
                android:propertyName="trimPathStart"
                android:valueFrom="1"
                android:valueTo="0"
                android:valueType="floatType"
                android:duration="1000"  />
        </aapt:attr>
    </target>

    <target android:name="bar">
        <aapt:attr name="android:animation">
            <objectAnimator
                android:propertyName="trimPathStart"
                android:valueFrom="0"
                android:valueTo="1"
                android:valueType="floatType"
                android:duration="1000" />
        </aapt:attr>
    </target>
</animated-vector>
<?xml version="1.0" encoding="utf-8"?>
<animated-vector
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:aapt="http://schemas.android.com/aapt"
    android:drawable="@drawable/search">

    <target android:name="search">
        <aapt:attr name="android:animation">
            <objectAnimator
                android:propertyName="trimPathStart"
                android:valueFrom="0"
                android:valueTo="1"
                android:valueType="floatType"
                android:duration="1000"  />
        </aapt:attr>
    </target>

    <target android:name="bar">
        <aapt:attr name="android:animation">
            <objectAnimator
                android:propertyName="trimPathStart"
                android:valueFrom="1"
                android:valueTo="0"
                android:valueType="floatType"
                android:duration="1000" />
        </aapt:attr>
    </target>
</animated-vector>

首先根節(jié)點是一個animated-vector,有一個drawable屬性,下面包含兩個target子節(jié)點,整體結(jié)構(gòu)上沒有變化。區(qū)別就在target中的ObjectAnimator。

propertyName是trimPathStart,表示這是一個trimPath類型的動畫(還有trimPathEnd,方向相反)。trimPath的原理是從一段path上裁剪出一小部分顯示,通過改變裁剪的長度,形成一個漸變的動畫。

上面的demo中,其實是有兩段path,一段是放大鏡,一段是橫線。就像這樣:

image

初始狀態(tài),橫線顯示的長度是0,所以我們只能看到一個放大鏡:

image

動畫開始后,放大鏡裁剪的部分逐漸變小,橫線裁剪的部分逐漸變大,直至放大鏡消失,只剩橫線。

image

trimPath動畫的寫法也基本是固定的

<objectAnimator
    android:propertyName="trimPathStart"
    android:valueFrom="1"
    android:valueTo="0"
    android:valueType="floatType"
    android:duration="1000" />

valueFrom和valueTo表示裁剪的起始點和結(jié)束點,valueType是float類型,duration是1000毫秒。這樣就實現(xiàn)了放大鏡和橫線切換顯示的動畫啦。

總結(jié)

這一篇我們基本講完了Android中的矢量動畫,這塊知識點都不難,就是太亂。我盡量把思路捋的順一點了,用問題引出問題的方式把所有知識點串起來,這樣更容易理清關(guān)系。如果完整看到這里的話你一定會發(fā)現(xiàn)還是有問題,Android系統(tǒng)提供的vector、animated-vector、animated-selector雖然很強大,但是有一個致命的缺點,就是只能在xml中寫死,不能通過java代碼動態(tài)構(gòu)建,并且我們不能控制動畫的過程。所以這又是個頭疼的問題。怎么辦呢,下一個靚仔在哪里?

下一篇

下一篇應(yīng)該是這個系列總結(jié)篇,我們會在系統(tǒng)矢量動畫的基礎(chǔ)上封裝一些自己的庫,實現(xiàn)一些額外的功能。最后我們還會封裝一個通用動畫庫,簡化動畫的使用。

最后編輯于
?著作權(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)容

  • Android Vector曲折的兼容之路 兩年前寫書的時候,就在研究Android L提出的Vector,可研究...
    eclipse_xu閱讀 35,421評論 30 263
  • 目錄 Android高級動畫(1)http://m.itdecent.cn/p/48554844a2dbAnd...
    大公爵閱讀 9,392評論 20 55
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,351評論 25 708
  • 感恩一早同事幫我買好的物品! 感恩清潔工一早將樓道打掃的非常干凈,讓我們有舒適的環(huán)境! 感恩客戶的來電,對我充分的...
    我不叫許仲斌閱讀 309評論 0 3
  • [JVM] 雖然大部分情況下不需要我們直接對Java內(nèi)存方面進行直接操作,但是了解其中的原理和調(diào)優(yōu)還是比較重要的,...
    ChaosCoffee閱讀 399評論 0 0

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