前言:
上一篇文章傳統View動畫與Property動畫基礎及比較簡單對Android動畫系統的基礎做了介紹,本篇文章將對PropertyAnimation進行全面深入的探討,本篇文章可以分為兩大塊,從第六部分可以作為分界點。前五部分著重講解了PropertyAnim的動畫值的計算過程,ValueAnimator與ObjectAnimator以及TimeInterpolation與TypeEvaluator之間的介紹和比較,這幾點是比較重要的,從第六部分開始是通過源碼的角度分析了整個動畫計算以及內部的處理細節,以及引出了對JakeWharton大神的NineOldAndroids 開源庫的分析,如果你覺得太多,可以分開來看,有理解不準確的地方,歡迎大家指正。
Property Animation
官方說了Property Animation是一個很強勁的動畫框架,幾乎可以為所有的事物加上動畫效果。你可以定義一個動畫去改變任何對象的屬性,不管該對象是否在屏幕上,都可以進行繪制。一個屬性動畫在某一個時間段,改變的是一個對象的一個屬性值(一個對象的一個字段)。
屬性動畫系統為動畫供了以下屬性:
cg動畫。Duration:動畫的持續時間
TimeInterpolation: 用于定義動畫變化率的接口,所有插值器都必須實現此接口,如線性,非線性插值器。
TypeEvaluator: 用于定義屬性值計算方式的接口,有int,float,color類型,根據屬性的起始、結束值和插值一起計算出當前時間的屬性值
Animation sets: 動畫集合,即可以同時對一個對象應用多個動畫,這些動畫可以同時播放也可以對不同動畫設置不同的延遲
Frame refreash delay: 多少時間刷新一次,即每隔多少時間計算一次屬性值,默認為10ms,最終刷新時間還受系統進程調度與硬件的影響
Repeat Country and behavoir:重復次數與方式,如播放3次、5次、無限循環,可以讓此動畫一直重復,或播放完時向反向播放
一、Property Animation的工作方式
1.1 示例
示例1:線性動畫
簡單理解為勻速,下面描述了一個物體的X屬性的運動。該對象的X坐標在40ms內從0移動到40 pixel,每10ms刷新一次,移動4次,每次移動40/4=10pixel。
示例2:非線性動畫
安卓 動畫,簡單的理解為非勻速,同樣的40pixel,同樣的時間,但是速率不同,開始和結束的速度要比中間部分慢,即先加速后減速
1.2、屬性動畫的幾個重要組成部分
TimeInterpolator 實現插值器的接口,用于計算插值。
TypeAnimator 計算屬性值的接口。
ValueAnimator 已經實現了TimeInterpolator和TypeAnimator接口,跟蹤了動畫時間的相關屬性,比如一個動畫已完成了多長時間,當前執行動畫的開始、結束或屬性值。
1.3、動畫的計算過程
過程一:計算已完成動畫分數 elapsed fraction
為了執行一個動畫,你需要創建一個ValueAnimator,并且指定目標對象屬性的開始、結束值和持續時間。在調用start后,整個動畫過程中, ValueAnimator會根據已經完成的動畫時間計算得到一個0到1之間的分數,代表該動畫的已完成動畫百分比。0表示0%,1表示100%,比如,示例1中,總時間 t = 40 ms,t = 10 ms 的時候是 0.25。
過程二:計算插值(動畫變化率)interpolated fraction
當ValueAnimator計算完已完成動畫分數后,它會調用當前設置的TimeInterpolator,去計算得到一個interpolated(插值)分數,在計算過程中,已完成動畫百分比會被加入到新的插值計算中。如示例2中,因為動畫的運動是緩慢加速的,它的插值分數大約是 0.15,小于在 t = 10ms 時的已完成動畫分數0.25。而在示例1中,這個插值分數一直和已完成動畫分數是相同的。
國產動畫。關于插值器的詳細介紹,可以看2.3節。
過程三:計算屬性值
當插值分數計算完成后,ValueAnimator 會根據插值分數調用合適的 TypeEvaluator去計算運動中的屬性值。
以上分析引入了兩個概念:已完成動畫分數(elapsed fraction)、插值分數( interpolated fraction )。
在上面的示例2中,TimeInterpolator 使用的是 AccelerateDecelerateInterpolator ,而它的TypeEvaluator使用的是 IntEvaluator。
明白具體的過程后,我們來分析一下它的計算過程,取 t = 10ms:
動畫人物、過程1:計算已完成動畫時間分數:t=10ms/40ms=0.25.
過程2:因為上述例子中用了AccelerateDecelerateInterpolator,其計算公式如下(input即為時間因子),經計算得到的插值大約為0.15:
1 | public float getInterpolation(float input) { |
這里簡單說下,Interpolator接口的直接繼承自TimeInterpolator,內部沒有任何方法,而TimeInterpolator只有一個getInterpolation方法,所以所有的Interpolator只需實現getInterpolation方法即可。下面是AccelerateDecelerateInterpolator的源碼:
1 | public class AccelerateDecelerateInterpolator implements Interpolator { |
過程3:因為它的TypeEvaluator類型為FloatEvaluator,計算公式如下,因為startValue = 0,所以經計算得到屬性值:0.15*(40-0)= 6 pixel:
1 | public Float evaluate(float fraction, Number startValue, Number endValue) { |
參數分別為上一步的插值分數、起始值、結束值。
相信大家看到這里,整個動畫的計算過程應該是非常清楚了。
第六部分的源碼分析詳細的介紹了這3個過程的內部實現。
二、相關對象的API介紹
android三種動畫區別。因為View Animation 系統已經在android.view.animation中定義了很多的插值器,你可以直接應用到你的屬性動畫中。 Animator雖然提供了創建動畫的基本框架,但你不應該直接使用這個類,因為它只提供了很少的功能,需要去擴展才能完全支持動畫。下面介紹的是一些屬性動畫系統中的主要類。
2.1 Animators
2.1.1 ValueAnimator
屬性動畫中的主要的時序引擎,如動畫時間,開始、結束屬性值,相應時間屬性值計算方法等。包含了所有計算動畫值的核心函數。也包含了每一個動畫時間上的細節,信息,一個動畫是否重復,是否監聽更新事件等,并且還可以設置自定義的計算類型。
整個Property Animation動畫有兩個步聚:
1.計算屬性值
2.為目標對象的屬性設置屬性值,即應用和刷新動畫。
動畫,ValueAnimiator只完成了第一步工作,如果要完成第二步,你必須監聽由ValueAnimator計算得到的屬性值,并修改目標對象。需要實現ValueAnimator .onUpdateListener 接口,自己去處理對象的動畫邏輯,比如:
1 | ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f); |
2.1.2 ObjectAnimator
繼承自ValueAnimator,允許你指定要進行動畫的對象以及該對象的一個屬性。該類會根據計算得到的新值自動更新屬性。也就是說上Property Animation的兩個步驟都實現了。大多數的情況,你使用ObjectAnimator就足夠了,因為它使得目標對象動畫值的處理過程變得簡單,不用再向ValueAnimator那樣自己寫動畫更新的邏輯。但ObjectAnimator有一定的限制,比如它需要目標對象的屬性提供指定的處理方法,這個時候你需要根據自己的需求在ObjectAnimator和ValueAnimator中做個選擇了,看哪種實現更簡便。在下面的第三部分有重點介紹。
2.1.3.AnimationSet
動畫集合,提供了一個把多個動畫組合成一個組合的機制,并可設置組中動畫的時序關系,如同時播放、順序播放或延遲播放。Elevator會告訴屬性動畫系統如何計算一個屬性的值,它們會從Animator類中獲取時序數據,比如開始和結束值,并依據這些數據計算動畫的屬性值。
2.1.4 TimeAnimator
它并不能直接實現動畫效果,它是一個對監聽者的簡單回調機制,在TimeListener接口的onTimeUpdate回調方法中返回動畫持續的時間與上次調用的間隔時間,沒有duration、interpolation以及設置值的方法等。主要是在動畫的每一幀的時候Notify其監聽者做相應的處理。
QPropertyAnimation、更詳細的分析和在實際使用中如何選擇,請參考第三部分。
2.2 Evaluators
Evaluators 告訴屬性動畫系統如何去計算一個屬性值。它們通過Animator提供的動畫的起始和結束值去計算一個動畫的屬性值。
屬性系統提供了以下幾種Evaluators:
1.IntEvaluator
2.FloatEvaluator
3.ArgbEvaluator
這三個由系統提供,分別用于計算int,float,color型(十六進制)屬性的計算器
4.TypeEvaluator
一個用于用戶自定義計算器的接口,如果你的對象屬性值類型,不是int,float,或者color類型,你必須實現這個接口,去定義自己的數據類型。
更詳細的介紹,請參考第五部分:使用TypeEvaluator
2.3 Interpolators
插值器:時間的函數,定義了動畫的變化律。
插值器只需實現一個方法:getInterpolation(float input),其作用就是把0到1的elapsed fraction變化映射到另一個interpolated fraction。傳入參數是正常執行動畫的時間點,返回值是用戶真正想要它執行的時間點。傳入參數是{0,1},返回值一般也是{0,1}。{0,1}表示整段動畫的過程。中間的0.2、0.3等小數表示在整個動畫(原本是勻速的)中的位置,其實就是一個比值。如果返回值是負數,會沿著相反的方向執行。如果返回的是大于1,會超出正方向執行。也就是說,動畫可能在你指定的值上下波動,大多數情況下是在指定值的范圍內。
getInterpolation(float input)改變了默認動畫的時間點elapsed fraction,根據時間點interpolated fraction得到的是與默認時間點不同的屬性值,插值器的原理就是通過改變實際執行動畫的時間點,提前或延遲默認動畫的時間點來達到加速/減速的效果。動畫插值器目前都只是對動畫執行過程的時間進行修飾,并沒有對軌跡進行修飾。
簡單點解釋這個方法,就是當要執行input的時間時,通過Interpolator計算返回另外一個時間點,讓系統執行另外一個時間的動畫效果。
經過動畫計算過程的第一步,會獲取一個已完成時間百分比elapsed fraction,也就是getInterpolation方法的參數input。插值器,就是時間的函數,插值就是函數值。Android動畫提供的AccelerateDecelerateInterolator的源碼為:
1 | AccelerateDecelerateInterpolator |
android有哪幾種動畫。在下面的圖中,也可以看到AccelerateDecelerate的Formula(公式)和其getInterpolation(input)方法相對應的函數值。
截圖來自:http://cogitolearning.co.uk/?p=1078?該文章也有關于Android Property Anim的介紹,有興趣的可以看一下。
下面我們再通過AccelerateDecelerate的函數圖來進一步分析。
該曲線圖,表現了動畫計算的兩個過程:X軸是時間因子(正好最大值為1,那么每個X軸上的值就可以看做是百分比),也就是動畫計算過程的第一步所得到的值,Y軸就是相應時間的插值,就是動畫計算過程的第二步。還有一步,這里沒有體現出來,就是通過TypeEvaluator計算最終的屬性值。
下面介紹幾種插值器:
AccelerateDecelerateInterolator 先加速后減速,開始結束時慢,中間加速
AccelerateInterpolator 加速,開始時慢中間加速
DecelerateInterpolator 減速,開始時快然后減速
AnticipateInterpolator 反向 ,先向相反方向改變一段再加速播放
AnticipateOvershootInterpolator 反向加超越,先向相反方向改變,再加速播放,會超出目的值然后緩慢移動至目的值
BounceInterpolator 跳躍,快到目的值時值會跳躍,如目的值100,后面的值可能依次為85,77,70,80,90,100
CycleIinterpolator 循環,動畫循環一定次數,值的改變為一正弦函數:Math.sin(2?mCycles?Math.PI * input)
LinearInterpolator 線性,線性均勻改變
OvershottInterpolator 超越,最后超出目的值然后緩慢改變到目的值
TimeInterpolator 一個接口,允許你自定義interpolator,以上幾個都是實現了這個接口
android動畫實現方式,如果這些插值器不能滿足你的需求,那么你可以通過實現TimeInterpolator接口去創建自己的插值器。下面是 LinearInterpolator計算插值的方法,LinearInterpolator(線性插值器)對于已完成動畫百分比沒有影響。
LinearInterpolator
1 | public float getInterpolation(float input) { |
三、應用動畫
3.1、使用ValueAnimator添加動畫
ValueAnimator類可以為一些動畫指定一系列的int,float,color值。通過調用工廠方法ofInt(),ofFloat().ofObject()來獲取一個ValueAnimator.
1 | ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f); |
上面這段是無效的代碼,因為這里根本就沒有動畫目標的影子,也沒有在ValueAnimator的監聽中獲取計算得到的屬性值去更新目標對象,所以不會有動畫效果。
你需要為動畫指定一個自定義的類型:
1 | ValueAnimator animation = ValueAnimator.ofObject(new MyTypeEvaluator(), startPropertyValue, endPropertyValue); |
ValueAnimator通過MyTypeEvalutor提供的邏輯去計算一個時長為1000ms的動畫在開始和結束之間的屬性值,從start方法開始算起。第一塊代碼,對于對象沒有起到真正的效果,你通常希望通過計算得到的屬性值去修改動畫對象,但這里的ValueAnimator沒有直接操作一個對象或者屬性。你需要在ValueAnimator中實現一個AnimatorUpdateListener監聽去手動更新目標對象的屬性值以及處理動畫生命周期中的其它重要事件,如frame的更新。當你實現了監聽之后,你可以通過getAnimateValue()方法獲取某一幀的動畫值,然后做更新操作。更多關于Listeners的介紹,你可以參考第四部分:Animation Listeners
3.2、使用ObjectAnimator添加動畫
更加簡便,動畫屬性會自動更新,不用再像ValueAnimator那樣自己去實現更新的動畫邏輯,但需要遵循一定的規則。
ObjectAnimator是ValueAnimator的子類,并且同時擁有時序引擎和屬性值計算以及自動更新屬性值的功能,使得為對象添加動畫變得更加簡單。因此你不再需要去實現ValueAnimator.AnimatorUpdateListener去更新動畫的屬性了。
實例化一個ObjectAnimator與實例化一個ValueAnimator是類似的,但是你應該指定對象和對象的某一屬性的名字(String 類型),以及動畫的起始和結束值
1 | ObjectAnimator anim = ObjectAnimator.ofFloat(foo, "alpha", 0f, 1f); |
ObjectAnimator的自動更新功能,依賴于屬性身上的setter和getter方法,所以為了讓ObjectAnimator能夠正確的更新屬性值,你必須遵從以下規范:
- 該對象的屬性必須有get和set方法(方法的格式必須是駝峰式),方法格式為set(),因為ObjectAnimator會自動更新屬性,它必須能夠訪問到屬性的setter方法,比如屬性名為foo,你就需要一個setFoo()方法,如果setter方法不存在,你有三種選擇:
a.添加setter方法
b.使用包裝類。通過該包裝類通過一個有效的setter方法獲取或者改變屬性值的方法,然后應用于原始對象。
c.使用ValueAnimator代替。
(這3點的意思總結起來就是一定要有一個setter方法,讓ObjectAnimator能夠訪問到)
-
如果你為ObjectAnimator的工廠方法的可變參數只傳遞了一個值,那么會被作為動畫的結束值。因此,你的目標對象屬性上必須要有一個getter方法,用于獲取動畫的起始值。這個獲取方法必須使用get()的格式。例如,屬性是foo,就必須有一個getFoo方法。
-
注意,屬性的getter方法和setter方法必須必須是相對應的,比如你構造了一個如下的ObjectAnimator,那么getter和setter方法就應該為:
targetObject.setPropName(float) 和targetObject.getPropName(float) :
ObjectAnimator.ofFloat(targetObject, “propName”, 1f) -
根據動畫的目標屬性或者對象不同,你可能需要調用某個View的invalidate方法,根據新的動畫值去強制屏幕重繪該View。可以在onAnimateonUpdate()回調方法中去做。比如,對一個Drawable的顏色屬性進行動畫,只有當對象重繪自身的時候,才會導致該屬性的更新,(不像平移或者縮放那樣是實時的)。一個VIew的所有setter屬性方法,比如setAlpha()和setTranslationX()都可以適當的更新View。因此你不需要在重繪的時候為這些方法傳遞新的值。更多關于 Listener的信息,可以參考第四部分Animation Listeners。
簡單總結下:
當你不希望向外暴露Setter方法的時候,或者希望獲取到動畫值統一做處理的話,亦或只需要一個簡單的時序機制的話,那么你可以選擇使用ValueAnimator,它更簡單。
如果你就是希望更新動畫,更簡便的,可以使用ObjectAnimator,但你必須有setter和getter方法,并且它們必須都是標準的駝峰式(確保內部能夠調用),必須有結束值。
根據需要,不需實時更新的動畫,需要你自己去強制更新。
3.3、AnimatorSet編排多個動畫
很多時候,你需要在一個動畫的開始或者結束點去播放另一個動畫,Android系統允許你綁定多個動畫到一個AnimatorSet中,因此你可以指定這些動畫是否同時啟動或者有序或者延遲進行。你也可以互相內嵌AnimatorSet。下面的代碼來自Google Sample彈力球Sample,按順序播放了以下動畫:
1、播放 bounceAnim.
2、同時播放 squashAnim1, squashAnim2, stretchAnim1, and stretchAnim2
3、播放 bounceBackAnim.
4、播放 fadeAnim.
1 | AnimatorSet bouncer = new AnimatorSet(); |
更多細節,你可以參考APIDemo,APIDemo在大家的SDK中都有,直接導入即可。
四、Animation Listeners
你可以通過以下監聽器監聽動畫過程中的重要事件:
Animator.AnimatorListener
onAnimationStart() - 動畫啟動時的回調
onAnimationEnd() -動畫結束時的回調
onAnimationRepeat() - 動畫重復自身時候回調
onAnimationCancel() - 動畫被取消的時候回調,一個動畫取消的時候也會調用onAnimationEnd方法,而不考慮動畫是如何結束的。
ValueAnimator.AnimatorUpdateListener
onAnimationUpdate() :動畫的每一幀都會調用該方法,監聽該事件去使用ValueAnimator計算得到的值。通過getAnimatedValue方法可以獲取當前的動畫值。如果你使用 的是ValueAnimator,實現該監聽就是有必要的了。
根據動畫的屬性的實際情況,你可能需要根據新的動畫值去調用某個View身上的invalidate方法去強制刷新某一個區域。這一點和ObjectAnimator中的第4點相同。
如果你不想實現Animator.AnimatorListener接口的所有的方法,你可以繼承AnimatorListenerAdapter類,而不用去實現Animator.AnimatorListener接口。
AnimatorListenerAdapter類提供了一些空的實現,你可以選擇性的覆蓋。比如API中彈力球sample,創建了一個AnimatorListenerAdapter,而只實現了onAnimationEnd方法。
1 | ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f); |
五、使用TypeEvaluator
如果你想添加一種動畫系統中沒有的計算類型,就需要自己通過實現TypeEvaluator接口去創建自己的evaluator。Android系統可以識別的類型是:int,float或者color。對應的java類分別為 IntEvaluator、 FloatEvaluator、 ArgbEvaluator 。
TypeEvaluator接口只有一個方法,就是evaluate()方法,它允許你使用的animator返回一個當前動畫點的屬性值,FloatEvaluator示例:
1 | public class FloatEvaluator implements TypeEvaluator { |
我們再來看一下IntEvaluators的源碼:
1 | /** |
ArgbEvaluator的部分源碼,因為是十六進制顏色值,前部分做了一些位運算的操作,這里貼出的是最后返回值的代碼:
1 | return (int)((startA + (int)(fraction * (endA - startA))) << 24) | |
大家可以看到,三種計算器都是線性的,且形式都為: result = x0 + t * (v1 - v0)。
如果你的數據類型不是float,int,或者color類型,那么你就需要自己實現TypeEvaluator,并實現evaluate方法,根據自己的數據結構計算屬性值。
代碼家的開源動畫庫AnimationEasingFunctions就是根據一個函數庫http://easings.net/zh-cn?做出來的,每個不同的動畫效果就是復寫了evaluate方法,按照不同的函數計算屬性值,從而達到了相應的動畫效果。大家可以自己去看AnimationEasingFunctions的源碼,在理解了1.3動畫的計算過程后,再去看,就非常清晰了,關鍵地方就是這個evaluate方法根據不同的函數做了處理。
TimeInterpolator和TypeEvaluator的區別
不知道大家弄明白TypeEvaluator和TimeInterpolator沒有,反正當時我剛看的時候,有些迷糊,不知道該如何具體的使用。
當時分析了代碼家的AnimationEasingFunctions開源項目,發現它都是在TypeEvaluator中定義函數,而不是在TimeInterpolator中。
我當時很困惑,我的想法是在TimeInterpolator 中定義插值函數,而在Evaluators的evaluate方法只是簡單的處理。比如系統提供的Evaluators那樣,簡單的進行線性運算即可,我當時對Evaluators的理解是:它只是為了擴展一種數據類型,比如系統提供的IntEvaluator、FloatEvaluator,它們內部計算只是簡單的線性計算,只是類型不同而已。后來實在不太明白,就向代碼家請教了下,代碼家的答復:
Interpolator 和 evaluator 都是可以自定義函數的。
前者:只能修改fraction (多數場景可以滿足,將原本線性的運動修改為非線性的)
后者:能拿到所有數據,然后去返回最終的值(終極利器,傳入他的有三個參數 (float >fraction, T startValue, T endValue))
getInterpolation(float input)
evaluate(float fraction, Number startValue, Number endValue)
從上述回復我們可以看到,evaluate方法接收3個參數,第一個參數fraction我們可以實現TimeInterpolator接口復寫 getInterpolation(float input)來控制,但是startValue和endValue我們是拿不到的,這才是關鍵。如果有些動畫值的計算需要startValue和endValue,那么你就只能在evaluate中去定義你的計算函數了。在代碼家的AnimationEasingFunctions 動畫庫有些屬性值計算就是用到了這兩個值,所以他統一在evaluate中定義函數。(這應該就是缺乏實踐吧,只有自己用的時候,才會發現問題。不得不佩服代碼家的經驗,開源了好幾個很棒的庫,要是還不了解的朋友,請立刻關注吧,對你的學習一定會有幫助:https://github.com/daimajia)
以上的分析是基于系統支持的float類型值來分析的,在該前提下,由于我們的計算函數需要startValue或endValue來計算屬性值,所以只能將函數定義在evaluate方法中。我的分析其實是從數據類型的角度考慮的,另外我們知道Property Anim系統的一大擴展就是可以對任何對象進行添加動畫,那么如果你的數據類型不是float、int、color類型,那么你肯定是在TypeEvaluator中定義了,在Interpolator中定義顯然不合適。
所以綜上所述,TypeEvaluator所做的是根據數據結構計算最終的屬性值,允許你定義自己的數據結構,這才是官方對它的真正定義,如果你的計算函數需要startValue和endValue的時候,你也只能在evaluate中定義計算函數,而Interpolator更傾向于你定義一種運動的變化率,比如勻速、加速、減速等,官方對Interpolator的定義也確實是這樣的:
A time interpolator defines the rate of change of an animation. This allows >animations to have non-linear motion, such as acceleration and deceleration.
弄清TimeInterpolator和TypeEvaluator非常重要,如果你希望要自定義自己的動畫,那么這兩個函數肯定是關鍵部分,一個是定義動畫變化率,一個是定義數據結構和屬性值計算方式,兩者共同決定了一個動畫的運動。
TypeEvalutor的evaluate方法接收的fraction究竟來自于哪里?
我覺得這個fraction非常重要,因為它連接了動畫值計算的第二步和第三步,所以弄清楚它到底是什么,對于后續第三步屬性值的計算非常重要。這里也在同一封郵件中向代碼家請教過,代碼家的答復是從第一個參數就是從 getInterpolator得到的。但是自己一直覺得哪里不對,后來經過Debug得出來了一些結果,也就是第六部分的來由,如果當初沒有深入探索下去,就沒有第六部分源碼分析這一塊,而最終收獲良多,并且弄清了NineOldAndroids的實現原理。
首先說明下,在測試的時候,使用的是ObjectAnimator.ofFloat( )工廠方法,值類型為Float,所以內部邏輯使用了FloatKeyframeSet類(關于FloatKeyframeSet后面有詳細的介紹,這里只需知道在該類里確定了傳入Evaluator的fraction)的getFloatValue方法,動畫的每一幀都會執行這個方法,這里也是動畫計算過程的第3步發生的地方,先計算得到一個中間值,然后傳遞到evaluator中的evaluate方法中去計算得到最終的屬性值。該方法中,對不同參數個數的情況進行了不同的處理,具體看源碼:
FloatKeyframeSet.java
1 | /* |
可以看到這里一共處理了4種情況:
mKeyframeNums == 2
fraction <= 0
fraction >= 1
fraction在(0,1)之間且mKeyframeNums != 2
我們先看看keyframe.getFraction()獲取到的是什么值:
1 | PropertyViewHolder |
這里有個從角標1開始的for循環,循環調用Keyframe.ofFloat(fraction,value)工廠方法,創建Keyframe。第一個keyframe的fraction為0,這是默認的。
而其它關鍵幀fraction的計算方式我們可以看到:i / (numKeyframes-1), numKeyframes為用戶傳入到ObjectAnimator.ofFloat(Object target ,String PropertyName,float …values) 方法的可變參數values個數。注意我們這里的value參數是動畫運動的關鍵幀,和之前所說的動畫運動的每一幀是不同的。運動過程中的每一幀是關鍵幀之間的那一部分,這部分是實時的,而關鍵幀就是一個個用戶指定的屬性值,希望在某個時間點(上述已經計算完成),達到的屬性值。
mKeyframeNums = 2
返回的就是直接從參數中獲取到的fraction,而這個fraction就是從通過ValueAnimator的Interpolator獲取到的。所以在這種情況下,正如代碼家的回復一樣。
下面我們看一下源碼中對getInterpolation()方法的注釋:Value可以大于1或者小于0。
1 | /* |
fraction <= 0 和 fraction >= 1的情況相似,都是獲取相鄰兩關鍵幀進行處理,但是它們選擇的兩關鍵幀是固定的,我個人認為這樣的選擇是為了更接近fraction。
假設用戶傳入的values 為 50,100,200,則numKeyframs = 3,那么創建出相應的Keyframe為:
Keyframe(0,50),Keyframe(1/2,100),Keyframe(1,200)
intervalFraction就是要傳入Evaluator的evaluate方法的fraction。
fraction <= 0
final FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0);
final FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(1);
選擇的是第一幀(從上面的賦值來知道,第一幀的fraction為固定值0)和第二幀
prevkeyframeFraction = 0,nextKeyframeFraction = 1 / 2:
fraction >= 1
final FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(mNumKeyframes - 2);
final FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(mNumKeyframes - 1);
由mNumkeyframes-1,mNumkeyframes-2,可以知道,這里獲取的就是倒數第一幀和倒數第二幀。
prevkeyframeFraction = 1/2 ,nextKeyframeFraction = 1:
mKeyframeNums != 2(或者==1,內部已處理為2)且在[0,1]范圍內
上面邏輯中有這么一行代碼: if (fraction < nextKeyframe.getFraction()) {…}
那么我們可以知道,這個elapsed fraction是某兩關鍵幀區間的elapsed fraction,落到了某一關鍵幀和下一關鍵幀區間里。如圖該fraction落在了1/2和1之間的區域:
上面更加清晰的知道,fraction并不一定在{0,1}內,也可能是該區間外的一個值,只是系統為了更接近這個fraction,在做處理的時候,選擇兩個相近的fraction進行計算,得到一個internalFraction傳遞給Evaluator的evaluate方法去計算屬性值。
因此這里可以解決我上面疑問了,evaluate接受的fraction分為兩種:
當用戶傳入的屬性值是2個的時候:是getInterpolator()返回的fraction。
其它情況又分為3種,fraction>=1 和 fraction<=1的取值是固定的兩關鍵幀,0<fraction<1時,為第一幀和大于fraction的那一幀。
兜了一大圈,其實就是為了弄清楚這個fraction到底是個什么值,現在明白了,其實只要知道這個fraction不一定是{0,1}之間的值,就OK了,就沒有什么疑問了。
小結:
TypeEvaluator: 定義了屬性值的計算方式,有int,float,color類型,根據屬性的開始、結束值和插值一起計算出當前時間的屬性值,終極方法,整個計算過程的結尾。
TimeInterpolation: 插值器都必須實現的接口,定義了動畫的變化率,如線性,非線性。
ValueAnimator與ObjectAnimator:兩者都可以進行屬性動畫,但是ObjectAnimator更加簡單,不用去做更新屬性值的計算,但是必須要提供標準的setter和getter方法,讓ObjectAnimator能夠獲取到屬性值。
以上部分為PropertyAnim的核心部分,主要分析已經介紹完了,如果一時消化不了,可以將源碼的分析先放一放,回過頭來再看,也沒問題,如果你希望一氣呵成,一下看完自然是極好的 :)
六、通過源碼的角度來分析整個動畫的全過程
先說明一下整個過程的分析是基于Jake Wharton的NineOldAndroids的,但除了初始化和動畫的更新不同,其它的整體邏輯和思路是一樣的,只是有些細節實現不同,畢竟大神不可能完全copy過來,有自己的代碼習慣。所以大家不用擔心和Android系統的源碼有太大出入,而對于NineOldAndroids的原理分析部分,著重談到了實現原理以及初始化和動畫更新部分與系統動畫的不同之處。
整個動畫值的初始化過程:
初始化過程,就是由ObjectAnimator.ofFloat();方法開始所做的一系列工作
1 | /* |
KeyframeSet.java
大家可以先略過KeyframeSet的介紹,可以直接看下面的代碼邏輯,等整體走了一遍,回過頭來再看也行。
keyframeset只有兩種函數,一種是工廠函數,int float 值作為KeyFrame工廠函數的值,或者直接接收keyFrame類型。 一種是返回value的函數,也就一個:getValue(float fraction),這里就是重頭了,動畫值計算過程的第三步在這里進行。
KeyFrame的Interpolator每個KeyFrame其實也有個Interpolator。如果沒有設置,默認是線性的。我們之前設置的Interpolator是整個動畫的,而系統允許你為每一KeyFrame的單獨定義Interpolator,系統這樣做的目的是允許你在某一個keyFrame做特殊的處理,也就是整體上是按照你的插值函數來計算,但是,如果你希望某個或某些FrameKey會有不同的動畫表現,那么你可以為這個keyFrame設置Interpolator。如果這個KeyFrame的Interpolator設置了,那么由animationFrame(long currentTime)傳遞進來的fraction就需要被重新賦值為當前KeyFrame的Interpolator。
用戶在初始化的時候只傳遞一個屬性值的情況:
如果你在ObjectAnimator中傳入的屬性值,只有一個,那么默認的它會給你創建一個值為0的keyFrame,所以KeyframeSet的大小永遠是大于等于2的。在FloatKeyframeSet中專門對2個屬性值進行了處理,在getValue中計算的時候,就會進入 if (mNumKeyframes == 2)的邏輯,由于你只設置了一個keyFrame,它被作為lastKeyFrame處理,所以在構造函數中,我們可以看到:fraction = mLastKeyframe.getInterpolator()。如果傳遞了兩個值,那么系統就會認為你是明確傳遞了2個值,就不會再為你添加起始值了。這兩種情況都會進入 KeyframeSet getValue 方法的 if (mNumKeyframes == 2)的邏輯。
1 | /** |
以上為賦值部分,最終返回了一個FloatKeyframeSet對象。
ValueAnimator持有一個PropertyValuesHolder的鍵值對:HashMap(numValues),
mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);代表一個或者多個屬性值對象的集合
每個PropertyValuesHolder 持有一個KeyframeSet對象,上層調用下層去賦值,最底層是Keyframe
整個setFloatValues過程:
setFloatValues——————-> setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));————————->
PropertyValuesHolder : ofFloat(String propertyName, float… values) ——————————》setFloatValues(float… values)———————->
KeyframeSet ofFloat(float… values)
執行動畫從start方法開始:
ValueAnimator:start(boolean playBackwards) ------------->
setCurrentPlayTime(getCurrentPlayTime())----------> animationFrame(long currentTime);--------------> animateValue(fraction);----------------->
----------> 父類的animateValue(float fraction) --------> PropertyValuesHolder:calculateValue-------------> FloatKeyframeSet.getFloatValue(fraction);
ValueAnimator.java
1 | /** |
ObjectAnimator復寫了父類的animateValue,所以這里先走ObjectAnimator的animateValue方法:
animateValue插值計算就在這里進行
1 | /* 動畫的每一幀都會調用的方法,該方法的目的是將elapsed fraction轉變為一個 interpolated fraction,用于 |
按照執行順序,先看一下父類ValueAnimator的animateValue(fraction):動畫計算過程的第二步和第三步在這里進行
1 | void animateValue(float fraction) { |
計算的第三步是通過PropertyValuesHolder的getValue方法計算屬性值:
1 | void calculateValue(float fraction) { |
mKeyframsSet.getValue(fraction)方法的實現已經在第五部分:使用TypeEvaluator中為解決evaluate中的fraction的真正來源貼出。
注:因為測試使用的是ofFloat,所以在初始化的時候,PropertyViewHolder已經選擇了FloatKeyFrameSet作為frame信息的處理,因此這里的mKeyframeSet.getValue()就會執行到FloatKeyFrameSet的getValue。
由于最終都是通過KeyFrame來處理這些值的,我們來看一看KeyFrame的定義:
/* 持有動畫time/value的鍵值對。KeyFrame 累用于定義目標動畫對象過程中的屬性值。由于時間是在一幀到另一幀之間盡心個,所有目標對象的
* 動畫值將會在前一幀值和后一幀值之間。每一幀持有了可選的TimeInterpolator對象,為每一幀單獨設置Interpolator.
* Keyframe本身是抽象類,指定類型的工廠函數將會根據保存的值類型返回一明確的Keyfame對象。系統對float和int類型的值,有性能的優化。
* 除非你需要處理一個自定義的類型或者要求直接應用動畫的數據結構(或者實現TypeEvaluator)之外,你應該使用int或者float類型的值。
————————————-super.animateValue 結束 ,華麗的分割線—————————————————-
接著執行,下面的邏輯很關鍵啊,做了更新屬性值的操作,調用的是PropertyValuesHolder的setAnimateValue方法,使用了反射機制去更新屬性值
1 | PropertyValuesHolder[] mValues; |
————-ObjectAnimator.animateValue 結束 ,setCurrentPlayTime(getCurrentPlayTime());結束———————————-
到此AnimateValue方法執行完畢,那么setCurrentPlayTime(getCurrentPlayTime());也就執行完畢了。
那么我們回到 ValueAnimator的start(boolean playBackwards)方法,接著來的事就交給handler處理了。那么我們就看看Handler是如何驅動動畫的。
1 | private static class AnimationHandler extends Handler { |
在ANIMATION_FRAME消息處理的結尾結處:會判斷是否還有活動或者延遲沒執行的動畫,如果有,則post一個ANIMATION_FRAME消息,然后就會再次執行動ANIMATION_FRAME消息的邏輯。如此反復,所有的動畫將會執行完。那么大家肯定很好奇,既然一直在執行動畫,那屬性值的更新一定能從這里跟蹤到,那在哪里呢?不用想,肯定是在startAnimation()方法中,點進去看看:
1 | private void startAnimation() { |
這個是ValueAnimator的startAnimation方法,只是做了初始化動畫和對延遲動畫監聽器回調的處理,(注意,這里initAnimation也被復寫了,對于NineOldAndroids來說是很重要的方法,因為自定義屬性的初始化就在這里進行的,為目標對象重新設置了兼容版本的屬性),并沒有更新值的方法啊,后來在AnimationHandler中,一個一個方法的點進去,跟蹤到了animateFrame方法,該方法結尾處調用了animateValue方法,開始一直認為這個方法只是計算一些屬性值,而且當時打斷點的方式是直接根據Ctrl+B的方式進到方法里面打的斷點,沒有注意到實現類復寫了方法,所以觀察斷點的時候,一走就走到了父類的方法中去。后來也是靈光一閃,感覺是ObjectAnimator是復寫了父類的方法,做了一些自己特有的處理,后來一看,果然是,ObjectAnimator在這個復寫的方法中進行了更新屬性值的操作,真是豁然開朗啊,就此得出結論,整個分析過程就此完成了。在上面分析動畫執行過程中,已經在代碼塊上面寫上了結論,其實也是后來添加上去的。
animationFrame方法是在ANIMATION_FRAME消息中,計算一個動畫是否已經結束的時候調用的:
1 | while (i < numAnims) { |
animateFrame方法,該方法在結尾處調用animateValue方法,這個是子類ObjectAnimator的animateValue方法:
1 | @Override |
這里使用了反射,將屬性值設置到了目標對象上:
1 | void setAnimatedValue(Object target) { |
anim.startAnimation();在AnimationHandler的ANIMATION_FRAME消息處理邏輯中,執行一個動畫,因為這里每次都會去執行監聽方法。所以如果你使用的是ValueAnimator,那么在ValueAnimator的監聽中,你必須手動做一些動畫的操作去更新動畫。
我們簡單總結一下上述的整個分析過程:
start方法進入了setCurrentPlayTime(long)方法,該方法其實是設置了動畫的第一幀操作,但它把整個動畫的計算過程走了一遍。
其中animationFrame方法為計算過程的第一步,animateValue方法為第二步和第三步發生的地方,第二步就是調用getInterpolator()方法獲取一個函數值,第三步則調用PropertyValuesHolder持有的KeyFrameSet.getValue()方法完成。而動畫屬性值的更新則發生在ObjectAnimator的animateValue(fraction) 方法中,也是通過PropertyValuesHolder,setAnimatedValue方法,內部使用了反射機制來調用屬性的setter方法。后續的動畫都在ValueAnimator的Handler中進行,只要還有活動和延遲的動畫,就會一直循環執行。
到此整個動畫的源碼分析過程就結束了。大家可以松口氣了,不過我感覺應該還好吧,上述分析還算連貫,應該能夠順溜的走下來。
NineOldAndroid兼容庫的實現原理
以上的所有分析都是基于NineOldAndroid的,但是和系統的沒什么太大區別,AnimationHandler的實現不同,Android系統的AnimationHandler是一個實現Runnable的子類,而NineOldAndroid的AnimationHandler就是一個繼承Handler的子類,代碼習慣不一樣吧,但是我們要知道核心部分是去更新動畫這部分,其它的部分代碼不同,但是邏輯思路都應該是一樣的。關鍵的地方是動畫的初始化和動畫的刷新。那么我們接下來分析這方面的差異。
首先我們必須得弄清楚的是,之前的View動畫和現在的Property動畫之間的差別:
View Anim,是不支持View自身屬性值的變化的。它的響應事件仍然保留在原始地點,它完全是由其父控件來draw出來的。因此nineOldAndroid的做法就是進行計算出View新的位置。然后自己去處理移動刷新View的邏輯。
還記得之前我們說過的,ValueAnimator的缺點是需要通過實現一個屬性值的方法,自己手動去更新屬性值,這一缺點被它的子類ObjectAnimator所彌補,ObjectAnimator類為用戶實現了自動更新。這個時候ObjectAnimator應該有自動更新屬性值的功能,但問題在哪里呢?就是它只支持3.0以后的系統,那么這一優勢也不再存在了,但是我們希望兼容庫不打破原有的這種繼承關系以及功能范圍(意思就是兼容庫版本的ObjectAnimator應該提供和系統的ObjectAnimator一樣的所有屬性支持),OK,兼容性的ObjectAnimator由此而來了。
nineOldAndroid兼容庫里的做法是正式基于上述理論:在ObjectAnimator中自己定義和3.0 Property Anim系統所支持的相同屬性,并提供setter和getter以及更新屬性值的方法,這樣就可以不依賴系統,而是由我們自己去更新View的屬性。
我們來看一下nineOldAnroid兼容庫:nineOldAndroid的結構,有哪些類:
除了PreHoneycombCompat和AnimatorProx外,我們經過上面的分析,已經都很熟悉了。
ViewHelper則是幫助類,你使用動畫的時候,用這個類就可以了。
另外對于ViewPropertyAnimator提供了不同系統版本的兼容類:
ViewPropertyAnimatorICS、ViewPropertyAnimatorHC、ViewPropertyAnimatorPreHC
1 | public static ViewPropertyAnimator animate(View view) { |
ViewPropertyAnimator是一個幫助類,使得同時創建多個屬性動畫變得更加方便,后面會介紹到。
那么我們看看是如何初始化自定義屬性的:
1 | ObjectAnimator.java |
PreHoneycombCompat類:擁有所有的屬性,向外暴露屬性及屬性方法,PreHoneycombCompat部分代碼:
1 | final class PreHoneycombCompat { |
而內部實現使用了代理類:AnimatorProxy。 關鍵是這個AnimatorProxy類,它是最終實現類。所有的屬性的update都由它來處理,所有的處理都是先計算值然后通過matrix來實現。
1 | |
另外, 在AnimatorProxy類中:每一個應用屬性的方法,都會有以下兩個步驟: prepareForUpdate();invalidateAfterUpdate(); 而動畫的平移是通過Matrix來進行的。
看一下系統的實現,既然之前的系統不支持屬性動畫,那么通過代理來處理initAnimation()方法里設置屬性的時候,都是nineOldAndroid版本的ObjectAnimator提供的: setProperty(PROXY_PROPERTIES.get(mPropertyName));
七、通過ViewPropertyAnimator添加動畫
ViewPropertyAnimator:只使用一個Animator對象就可以為某個View的多個屬性并行的添加動畫。它的行為更像一個ObjectAnimator,因為它修改的是對象的實際屬性值。但它為一次性給多個屬性添加動畫提供了方便,而且使用ViewPropertyAnimator的代碼更連貫更易讀。下面的代碼段分別展示了使用多個ObjectAnimator對象、一個ObjectAnimator對象、 ViewPropertyAnimator同時為一個View的X和Y屬性添加動畫的示例:
多個ObjectAnimator屬性
1 | ObjectAnimator animX = ObjectAnimator.ofFloat(myView, "x", 50f); |
一個ObjectAnimator屬性
1 | PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f); |
ViewPropertyAnimator:
myView.animate().x(50f).y(100f);//只需一行代碼
關于ViewPropertyAnimator更詳細的介紹,可以參考:http://developer.android.com/reference/android/view/ViewPropertyAnimator.html
和相應的官方博客:http://android-developers.blogspot.jp/2011/05/introducing-viewpropertyanimator.html
八、為ViewGroup添加布局動畫
你可以在ViewGroup內,通過LayoutTransition類為布局的變化添加動畫。
當一個ViewGroup中添加或者移除某一個item,或者調用了View的setVisibility方法,使得View 變得VISIBLE或者GONE的時候,在ViewGroup內部的View可以完成出現或者消失的動畫。當你添加或者移除View的時候,那些剩余的View也可以通過動畫的方式移動到自己的新位置。你可以通過setAnimator()方法并傳遞一個Animator對象,在LayoutTransition內部定義以下動畫。以下是幾種事件類型的常量:
APPEARING 為那些添加到父元素中的元素應用動畫
CHANGE_APPEARING 為那些由于父元素添加了新的item而受影響的item應用動畫
DISAPPEARING 為那些從父布局中消失的item應用動畫
CHANGE_DISAPPEARING 為那些由于某個item從父元素中消失而受影響的item應用動畫
你可以為這四種事件定義自己的交互動畫,或者僅僅告訴動畫系統使用默認的動畫。
API Demos中的LayoutAnimations sample向你展示了如何為布局轉換定義一個布局動畫,然后將該動畫設置到目標View對象上。
LayoutAnimationsByDefault和相應的布局文件 layout_animations_by_default.xml展示了如何為ViewGroup啟動默認的轉換動畫,你唯一要做的事就是設置
android:animateLayoutchanges為true
設置該屬性為true后,那些從ViewGroup添加或者移除的View和剩余的View將會擁有動畫的效果。
—————————-這些示例我們可以在Google提供的官方Sample中找到,以下是代碼分析—————————-
九、指定Keyframes
一個keyframs對象由一個time/value的鍵值對組成,可以為動畫定義某一特定時間的特定狀態。
每個keyframe可以擁有自己的插值器,用于控制前一幀和當前幀的時間間隔間內的動畫。
Keyframe.ofFloat(0f,0f);
第一個參數為:要執行該幀動畫的時間節點(elapsed time / duration),第二個參數為屬性值。因此如果你想指定某一特定時間的特定狀態,那么簡單的使用ObjectAnimator就滿足不了你了,因為,ObjectAnimator.ofInt(….)類似的工廠方法,無法指定特定的時間點。
為了實例化一個keyframe對象,你必須使用某一個工廠方法:ofInt(), ofFloat(), or ofObject() 去獲取合適的keyframe類型,然后你調用ofKeyframe工廠方法去獲取一個PropertyValuesHolder對象,一旦你擁有了該對象,你可以將PropertyValuesHolder作為參數獲取一個animator,如下:
Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
Keyframe kf1 = Keyframe.ofFloat(.5f, 360f);
Keyframe kf2 = Keyframe.ofFloat(1f, 0f);
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe(“rotation”, kf0, kf1, kf2);//動畫屬性名,可變參數
ObjectAnimator rotationAnim = ObjectAnimator.ofPropertyValuesHolder(target, pvhRotation)
rotationAnim.setDuration(5000ms);
更復雜的如何使用keyframes的例子,可以參考APIDemos中的 MultiPropertyAnimation。
十、擁有動畫能力的View
View Animation系統是只允許為View添加動畫的,其它的對象是不允許的。這里就介紹property animation 系統為View提供動畫的情況,相對于View Animation,Property Animation更具優勢。
View Animation 系統通過View被draw的方式去轉化View。這是在每一個View的容器中處理的,由于View自身沒有屬性去操作。導致了View雖然實現了動畫效果,但自身并未改變,這樣導致的問題是,即使一個View通過動畫被繪制到了不同的位置,但是它的行為仍然保留在原始的位置。
從Android 3.0開始,添加了新的屬性和相應的setter和getter方法,解決了這種問題。屬性動畫改變的是一個對象的實際屬性值。另外,View也會在屬性值改變的時候會自動調用invalidate方法去更新屏幕。以下是幾種新的屬性:
translationX,translationY: View相對于原始位置的偏移量
rotation,rotationX,rotationY: 旋轉,rotation用于2D旋轉角度,3D中用到后兩個
scaleX,scaleY: 2D縮放支點
pivotX and pivotY: 縮放支點的位置,旋轉和縮放在該支點周圍進行。默認的縮放支點是對象的中心。
x and y: 描述View在其父控件中的最終的位置,是由 left + translationX top + translationY 得來。
alpha: View的透明度,1表示完全不透明,0表示完全透明。
為一個對象的屬性添加動畫,你所需要做的就是創建一個屬性animator并且指定目標屬性:
ObjectAnimator.ofFloat(myView, “rotation”, 0f, 360f);
大家都知道,對于alpha屬性,在View Anim 系統中就有,個人的理解是,由于從3.0開始,動畫機制完全不一樣,一些屬性的setter和getter方法暴露了,alpha也是其中一個,所以只要是具有Property Anim能力的屬性都算是新添加的吧。
在XML中聲明動畫
property animation system 允許你使用XML聲明屬性動畫而不是通過代碼。通過在XML中定義的動畫,可以很方便的在多個Activities中重用而且更容易編輯。為了區分新的屬性動畫,從3.1開始,你應該在res/animator/ 下存放屬性動畫的資源文件,使用animator文件夾是可選的,但是如果你想在Eclipse ADT插件中使用布局編輯工具(ADT 11.0.0+),就必須在res/animator文件夾下存放了,因為ADT只會查找res/animator文件夾下的屬性動畫資源文件。
屬性動畫支持的Tag有:
ValueAnimator -?
ObjectAnimator -?
AnimatorSet -?
下面的示例有序的播放了兩組動畫,外層的那組動畫內嵌了一組動畫。
1 | <set android:ordering="sequentially"> |
當然,為了執行該動畫,你還需要在Java代碼中應用它們:
1 | java |
歡迎大家對本篇文章提出寶貴建議,如果有理解不到位的地方,還望指正。