什么是屬性動(dòng)畫
更改一個(gè)對象的屬性值時(shí),值的變化呈現(xiàn)動(dòng)畫效果。如一個(gè)Drawable的alpha值變化,或者一個(gè)Drawable在view上位置的變化。
屬性動(dòng)畫 vs 視圖動(dòng)畫
當(dāng)屬性動(dòng)畫作用于view的屬性時(shí),它呈現(xiàn)的效果和視圖動(dòng)畫相視,但它們還是有區(qū)別的:

主要就是視圖動(dòng)畫僅針對View、Surface等視圖,而屬性動(dòng)畫不只針對視圖;視圖動(dòng)畫改變的僅僅是視覺效果,而屬性動(dòng)畫改變的是值本身,這也就是對一個(gè)view做如位移等動(dòng)畫時(shí),視圖動(dòng)畫結(jié)束后view的點(diǎn)擊區(qū)域還在原區(qū)域,而屬性動(dòng)畫view的點(diǎn)擊區(qū)域就在view呈現(xiàn)的位置。從包名上看也很明顯,屬性動(dòng)畫的包為android.animation,而視圖動(dòng)畫為android.view.animation。

Animator
Animator是屬性動(dòng)畫的基類,給動(dòng)畫提供開始、結(jié)束、動(dòng)畫狀態(tài)回調(diào)等基礎(chǔ)操作。

它主要做的工作就是通過以下步驟來完成動(dòng)畫效果:
- 調(diào)用者提供需要做動(dòng)畫的屬性的初始值startValue和結(jié)束值endValue,及動(dòng)畫時(shí)長;
- 每隔固定的時(shí)間間隔返回這個(gè)屬性的一個(gè)值給調(diào)用者;
- 調(diào)用者把這個(gè)值設(shè)給動(dòng)畫對象的屬性;
如果在每個(gè)時(shí)間間隔到達(dá)時(shí)都通過同一個(gè)公式根據(jù)當(dāng)前動(dòng)畫的完成時(shí)長計(jì)算屬性值的話,我們獲得的動(dòng)畫效果肯定為線性的。
如:我們需要在40ms內(nèi)把一個(gè)view從x=0的位置移動(dòng)到x=40的位置,公式為f(v) = startValue + t * (endValue - startValue),其中 t = elapsedTime / totalTime線性效果如下:

但一般情況下,我們需要這個(gè)動(dòng)畫有一個(gè)變化節(jié)奏,如先慢后快再慢:

這個(gè)時(shí)候我們需要每個(gè)時(shí)間間隔的計(jì)算公式返回的值是非線性的,這樣就需要這個(gè)公式有個(gè)變量,最簡單的就是根據(jù)當(dāng)前時(shí)間完成百分比來控制這個(gè)變量的值,如上面的非線性方程式可以為f(v) = startValue + f’(t) * (endValue - startValue),其中f'(t) = Math.cos(t+ 1) * Math.PI / 2.0f + 0.5f,t = elapsedTime / totalTime。在Animator模型里f(v)的計(jì)算封裝在TypeEvaluator類里,而f'(t)的計(jì)算封裝在TimeInpterpolator里,Animator只需在每個(gè)固定的時(shí)間間隔時(shí)傳入t(當(dāng)前時(shí)間點(diǎn),動(dòng)畫時(shí)間的完成百分比)即可。
TimeInterpolator
如上面的f'(t) = Math.cos(t+ 1) * Math.PI / 2.0f + 0.5f,t = elapsedTime / totalTime。TimeInterpolator提供了一個(gè)公式或匹配方式來獲取當(dāng)前動(dòng)畫完成時(shí)間百分比的插值,確保在計(jì)算屬性值時(shí)有一個(gè)變化率參數(shù)。它的輸入為當(dāng)前動(dòng)畫時(shí)長的完成百分比,輸出為插值。
Android提供的插值器有下面幾種,當(dāng)然你也可以自己定義:

TypeEvaluator
如上面的f(v) = startValue + f’(t) * (endValue - startValue)。TypeEvaluator提供公式根據(jù)當(dāng)前TimeInterpolator提供的帶有變化率的動(dòng)畫時(shí)長完成百分比插值來計(jì)算屬性值。它的輸入為帶變化率的動(dòng)畫完成時(shí)長百分比,輸出為屬性值。
Android提供的TypeEvaluator有下面幾種(可自定義):

TimeInterpolator 和 TypeEvalulator 的關(guān)系

如何在固定的時(shí)間間隔開始屬性值計(jì)算?
上面我們提到的是每隔固定的時(shí)間間隔來獲取屬性值,那么怎么做到呢,或者可不可以根據(jù)有變化率的時(shí)間間隔來獲取值呢(這樣不需要TimeInterpolator)?
不管是drawable還是view,在動(dòng)畫更改某件屬性后,都需要更新顯示,所以我們最有效的方法是讓其和view的刷新在同一個(gè)脈沖里進(jìn)行。這樣我們很容易就想到根據(jù)顯示子系統(tǒng)的VSYNC脈沖來更新動(dòng)畫,實(shí)際上android的動(dòng)畫系統(tǒng)也是這么做的。Animator通過AnimationHandler向Choreographer注冊CALLBACK_ANIMATION來獲取固定脈沖即固定時(shí)間間隔,在收到脈沖時(shí)更新動(dòng)畫。
Choreographer從顯示子系統(tǒng)接收定時(shí)脈沖VSYN:

AnimationHandler向Choreographer注冊定時(shí)脈沖回調(diào):

因此我們總結(jié)下我們的思路:

流程總結(jié)下來就是:

ValueAnimator
ValueAnimator就是實(shí)現(xiàn)Animator的一個(gè)類,它在動(dòng)畫時(shí)根據(jù)當(dāng)前脈沖時(shí)間計(jì)算動(dòng)畫屬性值,然后返回給動(dòng)畫調(diào)用方:

其中最重要的就是根據(jù)當(dāng)前脈沖時(shí)間計(jì)算動(dòng)畫屬性值:

其中TypeConverter是在TypeEvaluator計(jì)算出來的值和動(dòng)畫對象要設(shè)的屬性值類型不一致時(shí)使用,如動(dòng)畫對象的屬性值類型為PointF,而TypeEvaluator計(jì)算的值類型為float:

另外它實(shí)現(xiàn)了重復(fù)動(dòng)畫、反向動(dòng)畫、指定延時(shí)后開始動(dòng)畫的功能:

具體的實(shí)現(xiàn)原理如下:

其中我們用到了PropertyValueHolder,它封裝了動(dòng)畫對象的屬性名和在動(dòng)畫過程中這個(gè)屬性的一組值。
我們首先看下Property<T, V>和Keyframe是指什么,Property<T, V>封裝了 屬性名/屬性值,而Keyframe封裝了 時(shí)間點(diǎn)/屬性值。

再看看Property的實(shí)現(xiàn):

Keyframe的實(shí)現(xiàn):

PropertyValuesHolder的實(shí)現(xiàn):

其實(shí)很簡單,就是保存了propertyName和Keyframes,要注意的點(diǎn)就是它獲取保存了這個(gè)屬性名的mSetter和mGetter反射方法,如我們對象View的屬性名propertyName = "alpha",它獲取保存了View#setAlpha(float alpha)和View#getAlpha()的反射Method,這在ObjectAnimator中會(huì)用到。
ObjectAnimator
根據(jù)上面ValueAnimator的內(nèi)容,我們在計(jì)算出屬性值后要返回給動(dòng)畫調(diào)用方讓它自己來重新設(shè)置對象的屬性值。那我們能不能直接幫忙把屬性值設(shè)給動(dòng)畫對象?
答案當(dāng)然是肯定,上面PropertyValuesHolder的實(shí)現(xiàn)原理時(shí)我們看到它獲取了動(dòng)畫屬性的setXXX()反射Method,我們完全可以在計(jì)算結(jié)束后用這個(gè)反射方法來給動(dòng)畫對象更新動(dòng)畫屬性值,只需給Animator多傳入一個(gè)動(dòng)畫對象。
ObjectAnimator vs ValueAnimator

原理

由上面知道,ObjectAnimator的核心思想在于用反射方法來更新動(dòng)畫對象的屬性值,這要求我們的動(dòng)畫屬性在其類里必需有setXXX()的方法,如果這個(gè)set方法有參數(shù)的話還必需有g(shù)etXXX()方法。
ViewPropertyAnimator
如上面ValueAnimator和ObjectAnimator在給View的多個(gè)屬性做動(dòng)畫時(shí),每個(gè)屬性值更改時(shí)調(diào)用一次view.setXXX()方法,而每個(gè)setXXX()方法的實(shí)現(xiàn)里都調(diào)用了invalidate()一次,如果同時(shí)給多個(gè)View的屬性做動(dòng)畫的話,顯然性能不高,那么我們可不可以更新多個(gè)View的屬性值時(shí)只調(diào)用一次invalidate()呢?ViewPropertyAnimator就是通過直接調(diào)用RenderNode.setXXX()來更改屬性值,再統(tǒng)一調(diào)用一次invalidate()來使原布局失效。
ViewPropertyAnimator vs ObjectAnimator

通過上圖可以看到,ViewPropertAnimator在一次給多個(gè)View的屬性做動(dòng)畫時(shí)擁有更高優(yōu)的性能,同時(shí)它的調(diào)用方式也簡單多了。但它的動(dòng)畫對象僅僅是View。
其流程:

原理
ViewPropertyAnimator的實(shí)現(xiàn)和ValueAnimator不一樣的地方在于:
- 不需要直接調(diào)用start(),會(huì)根據(jù)View的Choreographer的脈沖回調(diào)自動(dòng)觸發(fā)。
-
并沒有通過ValueEvaluator計(jì)算值,而是直接寫死了計(jì)算值的公式。
ViewPropertyAnimator原理.png
作用對象
那么ViewPropertyAnimator是不是實(shí)現(xiàn)了View中所有屬性的動(dòng)畫呢?并不是。其實(shí)現(xiàn)的動(dòng)畫屬性如下:

TimeAnimator
如果我僅僅只想知道動(dòng)畫時(shí)長的消耗情況,不需要計(jì)算任何的屬性值,有沒有簡便的方法呢?TimeAnimator就提供了這個(gè)功能。

它提供一個(gè)回調(diào)函數(shù)返回:
- 上一幀到這一幀之間的時(shí)間增量;
- 動(dòng)畫啟動(dòng)以來的總時(shí)間。
AnimatorSet
AnimatorSet可以按指定順序(如,一起、按順序、延時(shí))播放一組Animator對象。
可以通過
- AnimatorSet#playTogether(Animator[])來指定一組Animator同時(shí)播放;
- AnimatorSet#playSequentially(Animator[])來指定一組Animator依次播放;
- Animator#play(Animator)和Builder類中的方法構(gòu)建一組Animator的播放順序。
如果AnimatorSet中的某些Animator的播放順序構(gòu)成了死循環(huán),如a1->a2->a3->a1,將表示這些動(dòng)畫將永遠(yuǎn)播放下去。
流程
其流程很簡單如下:

原理
其原理簡單來說就是:
- 根據(jù)動(dòng)畫是同時(shí)播放、還是先后播放構(gòu)建成一個(gè)關(guān)系圖,在A動(dòng)畫之前播放的動(dòng)畫是其parent,和A動(dòng)畫一起播放的是其sibling,在A動(dòng)畫之后播放的是其child。
- 定義一個(gè)時(shí)長為0的root動(dòng)畫,把沒有parent的動(dòng)畫設(shè)為其child。
- 通過parent的結(jié)束時(shí)間計(jì)算child的開始時(shí)間,再根據(jù)這個(gè)開始時(shí)間和child本身的動(dòng)畫總時(shí)長計(jì)算child的結(jié)束時(shí)間。
-
把每個(gè)動(dòng)畫和其開始時(shí)間、結(jié)束時(shí)間封裝成event,然后按照啟動(dòng)的先后順序加入到一個(gè)隊(duì)列中,每個(gè)時(shí)間脈沖到來時(shí),把這個(gè)脈沖之前需要執(zhí)行的event拿出來執(zhí)行。
如下:
AnimatorSet中Animator的關(guān)系圖.png
總的實(shí)現(xiàn)原理:
AnimatorSet.png
release版本的ObjectAnimator啟用失?。?/h1>
我們是否會(huì)碰到一個(gè)ObjectAnimation在debug版本中運(yùn)行的好好的,但是release版本就不行?
由上面ObjectAnimator的實(shí)現(xiàn)原理可以看到,我們在初始化時(shí)如果沒有傳入某個(gè)屬性的初始值,它會(huì)用反射方法去拿這個(gè)屬性的當(dāng)前值為其初始值;在屬性值計(jì)算完成后,也會(huì)用反射方法去把值設(shè)為對象的屬性值。當(dāng)release版本把這個(gè)動(dòng)畫對象的類混淆時(shí),反射方法找不到,于是動(dòng)畫無法正常運(yùn)行。
參考
Android官方:屬性動(dòng)畫
Android官方:ObjectAnimator
Android官方推薦博文:ViewPropertyAnimator
Android官方:View
material: elevation
material: elevation & shadow
Android官方:shadow
Android官方:animation-resource
原創(chuàng)文章,歡迎轉(zhuǎn)載,但請注明出處


