Android屬性動畫工作原理

什么是屬性動畫

更改一個對象的屬性值時,值的變化呈現(xiàn)動畫效果。如一個Drawable的alpha值變化,或者一個Drawable在view上位置的變化。

屬性動畫 vs 視圖動畫

當(dāng)屬性動畫作用于view的屬性時,它呈現(xiàn)的效果和視圖動畫相視,但它們還是有區(qū)別的:


屬性動畫 vs 視圖動畫.png

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


動畫分類.png

Animator

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


Animator.png

它主要做的工作就是通過以下步驟來完成動畫效果:

  1. 調(diào)用者提供需要做動畫的屬性的初始值startValue和結(jié)束值endValue,及動畫時長;
  2. 每隔固定的時間間隔返回這個屬性的一個值給調(diào)用者;
  3. 調(diào)用者把這個值設(shè)給動畫對象的屬性;

如果在每個時間間隔到達(dá)時都通過同一個公式根據(jù)當(dāng)前動畫的完成時長計算屬性值的話,我們獲得的動畫效果肯定為線性的。
如:我們需要在40ms內(nèi)把一個view從x=0的位置移動到x=40的位置,公式為f(v) = startValue + t * (endValue - startValue),其中 t = elapsedTime / totalTime線性效果如下:


google官方:animation-linear.png

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


google官方:animation-nonlinear.png

這個時候我們需要每個時間間隔的計算公式返回的值是非線性的,這樣就需要這個公式有個變量,最簡單的就是根據(jù)當(dāng)前時間完成百分比來控制這個變量的值,如上面的非線性方程式可以為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)的計算封裝在TypeEvaluator類里,而f'(t)的計算封裝在TimeInpterpolator里,Animator只需在每個固定的時間間隔時傳入t(當(dāng)前時間點,動畫時間的完成百分比)即可。

TimeInterpolator

如上面的f'(t) = Math.cos(t+ 1) * Math.PI / 2.0f + 0.5f,t = elapsedTime / totalTime。TimeInterpolator提供了一個公式或匹配方式來獲取當(dāng)前動畫完成時間百分比的插值,確保在計算屬性值時有一個變化率參數(shù)。它的輸入為當(dāng)前動畫時長的完成百分比,輸出為插值。

Android提供的插值器有下面幾種,當(dāng)然你也可以自己定義:


時間插值器:TimeInterpolator.png

TypeEvaluator

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

Android提供的TypeEvaluator有下面幾種(可自定義):


類型評估器:TypeEvaluator.png

TimeInterpolator 和 TypeEvalulator 的關(guān)系

TimeInterpolator & TypeEvaluator<T>.png

如何在固定的時間間隔開始屬性值計算?

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

Choreographer從顯示子系統(tǒng)接收定時脈沖VSYN:


Choreographer.png

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


AnimationHandler.png

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


思路.png

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


Animator流程.png

ValueAnimator

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


ValueAnimator實現(xiàn)Animator思路.png

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


根據(jù)當(dāng)前幀時間計算動屬性的動畫值.png

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


TypeConverter<T, V>.png

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


屬性動畫可以定義的特征.png

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


ValueAnimator.png

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


PropertyValuesHolder vs Property vs Keyframe vs Keyframes.png

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


Property<T, V>.png

Keyframe的實現(xiàn):


Keyframe & Keyframes.png

PropertyValuesHolder的實現(xiàn):
PropertyValuesHolder.png

其實很簡單,就是保存了propertyName和Keyframes,要注意的點就是它獲取保存了這個屬性名的mSetter和mGetter反射方法,如我們對象View的屬性名propertyName = "alpha",它獲取保存了View#setAlpha(float alpha)和View#getAlpha()的反射Method,這在ObjectAnimator中會用到。

ObjectAnimator

根據(jù)上面ValueAnimator的內(nèi)容,我們在計算出屬性值后要返回給動畫調(diào)用方讓它自己來重新設(shè)置對象的屬性值。那我們能不能直接幫忙把屬性值設(shè)給動畫對象?
答案當(dāng)然是肯定,上面PropertyValuesHolder的實現(xiàn)原理時我們看到它獲取了動畫屬性的setXXX()反射Method,我們完全可以在計算結(jié)束后用這個反射方法來給動畫對象更新動畫屬性值,只需給Animator多傳入一個動畫對象。

ObjectAnimator vs ValueAnimator

ValueAnimator vs ObjectAnimator.png

原理

ObjectAnimator.png

由上面知道,ObjectAnimator的核心思想在于用反射方法來更新動畫對象的屬性值,這要求我們的動畫屬性在其類里必需有setXXX()的方法,如果這個set方法有參數(shù)的話還必需有g(shù)etXXX()方法。

ViewPropertyAnimator

如上面ValueAnimator和ObjectAnimator在給View的多個屬性做動畫時,每個屬性值更改時調(diào)用一次view.setXXX()方法,而每個setXXX()方法的實現(xiàn)里都調(diào)用了invalidate()一次,如果同時給多個View的屬性做動畫的話,顯然性能不高,那么我們可不可以更新多個View的屬性值時只調(diào)用一次invalidate()呢?ViewPropertyAnimator就是通過直接調(diào)用RenderNode.setXXX()來更改屬性值,再統(tǒng)一調(diào)用一次invalidate()來使原布局失效。

ViewPropertyAnimator vs ObjectAnimator

ObjectAnimator vs ViewPropertyAnimator.png

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

其流程:


ViewPropertyAnimator流程.png

原理

ViewPropertyAnimator的實現(xiàn)和ValueAnimator不一樣的地方在于:

  1. 不需要直接調(diào)用start(),會根據(jù)View的Choreographer的脈沖回調(diào)自動觸發(fā)。
  2. 并沒有通過ValueEvaluator計算值,而是直接寫死了計算值的公式。


    ViewPropertyAnimator原理.png

作用對象

那么ViewPropertyAnimator是不是實現(xiàn)了View中所有屬性的動畫呢?并不是。其實現(xiàn)的動畫屬性如下:


ViewPropertyAnimator中支持的View的屬性.png

TimeAnimator

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


TimeAnimator.png

它提供一個回調(diào)函數(shù)返回:

  1. 上一幀到這一幀之間的時間增量;
  2. 動畫啟動以來的總時間。

AnimatorSet

AnimatorSet可以按指定順序(如,一起、按順序、延時)播放一組Animator對象。

可以通過

  1. AnimatorSet#playTogether(Animator[])來指定一組Animator同時播放;
  2. AnimatorSet#playSequentially(Animator[])來指定一組Animator依次播放;
  3. Animator#play(Animator)和Builder類中的方法構(gòu)建一組Animator的播放順序。

如果AnimatorSet中的某些Animator的播放順序構(gòu)成了死循環(huán),如a1->a2->a3->a1,將表示這些動畫將永遠(yuǎn)播放下去。

流程

其流程很簡單如下:


AnimatorSet流程圖.png

原理

其原理簡單來說就是:

  1. 根據(jù)動畫是同時播放、還是先后播放構(gòu)建成一個關(guān)系圖,在A動畫之前播放的動畫是其parent,和A動畫一起播放的是其sibling,在A動畫之后播放的是其child。
  2. 定義一個時長為0的root動畫,把沒有parent的動畫設(shè)為其child。
  3. 通過parent的結(jié)束時間計算child的開始時間,再根據(jù)這個開始時間和child本身的動畫總時長計算child的結(jié)束時間。
  4. 把每個動畫和其開始時間、結(jié)束時間封裝成event,然后按照啟動的先后順序加入到一個隊列中,每個時間脈沖到來時,把這個脈沖之前需要執(zhí)行的event拿出來執(zhí)行。
    如下:


    AnimatorSet中Animator的關(guān)系圖.png

    總的實現(xiàn)原理:


    AnimatorSet.png

release版本的ObjectAnimator啟用失???

我們是否會碰到一個ObjectAnimation在debug版本中運行的好好的,但是release版本就不行?
由上面ObjectAnimator的實現(xiàn)原理可以看到,我們在初始化時如果沒有傳入某個屬性的初始值,它會用反射方法去拿這個屬性的當(dāng)前值為其初始值;在屬性值計算完成后,也會用反射方法去把值設(shè)為對象的屬性值。當(dāng)release版本把這個動畫對象的類混淆時,反射方法找不到,于是動畫無法正常運行。

參考

Android官方:屬性動畫
Android官方:ObjectAnimator
Android官方推薦博文:ViewPropertyAnimator
Android官方:View
material: elevation
material: elevation & shadow
Android官方:shadow
Android官方:animation-resource

原創(chuàng)文章,歡迎轉(zhuǎ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)容

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