cg動畫,Android動畫系列 - PropertyAnim 詳解

 2023-11-19 阅读 14 评论 0

摘要:前言:上一篇文章傳統View動畫與Property動畫基礎及比較簡單對Android動畫系統的基礎做了介紹,本篇文章將對PropertyAnimation進行全面深入的探討,本篇文章可以分為兩大塊,從第六部分可以作為分界點。前五部分著重講解了PropertyAnim的動畫值的計算

前言:
上一篇文章傳統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
2
3
public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}

這里簡單說下,Interpolator接口的直接繼承自TimeInterpolator,內部沒有任何方法,而TimeInterpolator只有一個getInterpolation方法,所以所有的Interpolator只需實現getInterpolation方法即可。下面是AccelerateDecelerateInterpolator的源碼:

1
2
3
4
5
6
7
8
9
10
11
12
public class AccelerateDecelerateInterpolator implements Interpolator {
public AccelerateDecelerateInterpolator() {
}

@SuppressWarnings({"UnusedDeclaration"})
public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) {
}

public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}
}

過程3:因為它的TypeEvaluator類型為FloatEvaluator,計算公式如下,因為startValue = 0,所以經計算得到屬性值:0.15*(40-0)= 6 pixel:

1
2
3
4
public Float evaluate(float fraction, Number startValue, Number endValue) {
float startFloat = startValue.floatValue();
return startFloat + fraction * (endValue.floatValue() - startFloat);
}

參數分別為上一步的插值分數、起始值、結束值。
相信大家看到這里,整個動畫的計算過程應該是非常清楚了。
第六部分的源碼分析詳細的介紹了這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
2
3
4
5
6
7
8
9
10
ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f);
animation.setDuration(1000);
animation.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Log.i("update", ((Float) animation.getAnimatedValue()).toString());
}
});
animation.setInterpolator(new CycleInterpolator(3));
animation.start();

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
2
3
4
AccelerateDecelerateInterpolator
public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}

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
2
3
public float getInterpolation(float input) {
return input;
}

三、應用動畫

3.1、使用ValueAnimator添加動畫

ValueAnimator類可以為一些動畫指定一系列的int,float,color值。通過調用工廠方法ofInt(),ofFloat().ofObject()來獲取一個ValueAnimator.

1
2
3
ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f);
animation.setDuration(1000);
animation.start();

上面這段是無效的代碼,因為這里根本就沒有動畫目標的影子,也沒有在ValueAnimator的監聽中獲取計算得到的屬性值去更新目標對象,所以不會有動畫效果。

你需要為動畫指定一個自定義的類型:

1
2
3
ValueAnimator animation = ValueAnimator.ofObject(new MyTypeEvaluator(), startPropertyValue, endPropertyValue);
animation.setDuration(1000);
animation.start();

ValueAnimator通過MyTypeEvalutor提供的邏輯去計算一個時長為1000ms的動畫在開始和結束之間的屬性值,從start方法開始算起。第一塊代碼,對于對象沒有起到真正的效果,你通常希望通過計算得到的屬性值去修改動畫對象,但這里的ValueAnimator沒有直接操作一個對象或者屬性。你需要在ValueAnimator中實現一個AnimatorUpdateListener監聽去手動更新目標對象的屬性值以及處理動畫生命周期中的其它重要事件,如frame的更新。當你實現了監聽之后,你可以通過getAnimateValue()方法獲取某一幀的動畫值,然后做更新操作。更多關于Listeners的介紹,你可以參考第四部分:Animation Listeners

3.2、使用ObjectAnimator添加動畫

更加簡便,動畫屬性會自動更新,不用再像ValueAnimator那樣自己去實現更新的動畫邏輯,但需要遵循一定的規則。

ObjectAnimator是ValueAnimator的子類,并且同時擁有時序引擎和屬性值計算以及自動更新屬性值的功能,使得為對象添加動畫變得更加簡單。因此你不再需要去實現ValueAnimator.AnimatorUpdateListener去更新動畫的屬性了。
實例化一個ObjectAnimator與實例化一個ValueAnimator是類似的,但是你應該指定對象和對象的某一屬性的名字(String 類型),以及動畫的起始和結束值

1
2
3
ObjectAnimator anim = ObjectAnimator.ofFloat(foo, "alpha", 0f, 1f);
anim.setDuration(1000);
anim.start();

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
2
3
4
5
6
7
8
9
10
11
AnimatorSet bouncer = new AnimatorSet();
bouncer.play(bounceAnim).before(squashAnim1);
bouncer.play(squashAnim1).with(squashAnim2);
bouncer.play(squashAnim1).with(stretchAnim1);
bouncer.play(squashAnim1).with(stretchAnim2);
bouncer.play(bounceBackAnim).after(stretchAnim2);
ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
fadeAnim.setDuration(250);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(bouncer).before(fadeAnim);
animatorSet.start();

更多細節,你可以參考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
2
3
4
5
6
ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
fadeAnim.setDuration(250);
fadeAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
balls.remove(((ObjectAnimator)animation).getTarget());
}

五、使用TypeEvaluator

如果你想添加一種動畫系統中沒有的計算類型,就需要自己通過實現TypeEvaluator接口去創建自己的evaluator。Android系統可以識別的類型是:int,float或者color。對應的java類分別為 IntEvaluator、 FloatEvaluator、 ArgbEvaluator 。

TypeEvaluator接口只有一個方法,就是evaluate()方法,它允許你使用的animator返回一個當前動畫點的屬性值,FloatEvaluator示例:

1
2
3
4
5
6
public class FloatEvaluator implements TypeEvaluator {
public Object evaluate(float fraction, Object startValue, Object endValue) {
float startFloat = ((Number) startValue).floatValue();
return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);
}
}

我們再來看一下IntEvaluators的源碼:

1
2
3
4
5
6
7
8
9
/**
* This evaluator can be used to perform type interpolation between <code>int</code> values.
*/
public class IntEvaluator implements TypeEvaluator<Integer> {
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}
}

ArgbEvaluator的部分源碼,因為是十六進制顏色值,前部分做了一些位運算的操作,這里貼出的是最后返回值的代碼:

1
2
3
4
return (int)((startA + (int)(fraction * (endA - startA))) << 24) |
(int)((startR + (int)(fraction * (endR - startR))) << 16) |
(int)((startG + (int)(fraction * (endG - startG))) << 8) |
(int)((startB + (int)(fraction * (endB - startB))));

大家可以看到,三種計算器都是線性的,且形式都為: 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/*
* 獲取動畫值,通過給定的elapsed fraction 和evaluators去計算中間值。該函數將傳遞的fraction映射到恰當的keyframe和fraction
* 最終計算返回interpolated value.
* 注意:傳入的fraction可能落在[0-1]范圍之外,這樣的情況,我們只使用2個KeyFrameSet。只有2幀的時候,做了特別優化。
* 每一幀還可以擁有自己的Interpolator
*/
public float getFloatValue(float fraction) {
if (mNumKeyframes == 2) {//對于只有兩幀的情況,單獨處理,做了優化
if (firstTime) {
firstTime = false;
firstValue = ((FloatKeyframe) mKeyframes.get(0)).getFloatValue();
lastValue = ((FloatKeyframe) mKeyframes.get(1)).getFloatValue();
deltaValue = lastValue - firstValue;
}
if (mInterpolator != null) {
fraction = mInterpolator.getInterpolation(fraction);
}
if (mEvaluator == null) {
return firstValue + fraction * deltaValue;
} else {
return ((Number)mEvaluator.evaluate(fraction, firstValue, lastValue)).floatValue();
}
}
if (fraction <= 0f) {//fraction<=0,為了更接近獲取第一幀和第二幀做處理
final FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0);//這里的fraction就是0
final FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(1);
float prevValue = prevKeyframe.getFloatValue();
float nextValue = nextKeyframe.getFloatValue();
float prevFraction = prevKeyframe.getFraction();
float nextFraction = nextKeyframe.getFraction();
final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
if (interpolator != null) {
fraction = interpolator.getInterpolation(fraction);
}
float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
return mEvaluator == null ?
prevValue + intervalFraction * (nextValue - prevValue) :
((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
floatValue();
} else if (fraction >= 1f) {//因為fraction>=1,為了更接近fraction,獲取最后的兩幀做處理
final FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(mNumKeyframes - 2);
final FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(mNumKeyframes - 1);
float prevValue = prevKeyframe.getFloatValue();
float nextValue = nextKeyframe.getFloatValue();
float prevFraction = prevKeyframe.getFraction();
float nextFraction = nextKeyframe.getFraction();
final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
if (interpolator != null) {
fraction = interpolator.getInterpolation(fraction);
}
float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
return mEvaluator == null ?
prevValue + intervalFraction * (nextValue - prevValue) :
((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
floatValue();
}
FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0);//
for (int i = 1; i < mNumKeyframes; ++i) {//循環遍歷,
FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(i);
if (fraction < nextKeyframe.getFraction()) {//如果后一幀elapsed fraction 大于 前一個 elapsed fraction,才有效
final TimeInterpolator interpolator = nextKeyframe.getInterpolator();//獲取當前幀的Interpolator
if (interpolator != null) {//當前幀有自己的Interpolator,則重新計算fraction
fraction = interpolator.getInterpolation(fraction);
}
float intervalFraction = (fraction - prevKeyframe.getFraction()) //prevKeyframe.getFraction() = 0
(nextKeyframe.getFraction() - prevKeyframe.getFraction());
float prevValue = prevKeyframe.getFloatValue();//計算前一幀的值
float nextValue = nextKeyframe.getFloatValue();//計算后一幀的值
return mEvaluator == null ?
prevValue + intervalFraction * (nextValue - prevValue) ://未定義Evaluator,則簡單的返回,屬性值計算結束
((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).//按照自定義的Evaluator來計算屬性值
floatValue();
}
prevKeyframe = nextKeyframe;
}
// shouldn't get here
return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).floatValue();
}

可以看到這里一共處理了4種情況:
mKeyframeNums == 2
fraction <= 0
fraction >= 1
fraction在(0,1)之間且mKeyframeNums != 2

我們先看看keyframe.getFraction()獲取到的是什么值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
PropertyViewHolder
public void setFloatValues(float... values) {
mValueType = float.class;
mKeyframeSet = KeyframeSet.ofFloat(values);
}

KeyframeSet.ofFloat(values)
public static KeyframeSet ofFloat(float... values) {
int numKeyframes = values.length;
FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
if (numKeyframes == 1) {
keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f);
keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]);
} else {
keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);
for (int i = 1; i < numKeyframes; ++i) {
keyframes[i] = (FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);//這里是關鍵
}
}
return new FloatKeyframeSet(keyframes);
}

這里有個從角標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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    /*
@return The interpolation value. This value can be more than 1.0 for
* interpolators which overshoot their targets, or less than 0 for
* interpolators that undershoot their targets.
*/
float getInterpolation(float input);
```
在mKeyframeNums = 2 的時候,getInterpolation(input)的值會直接傳入到evaluate中,而getInterpolation(input)的值可以是[0,1]之外的值。因此evaluate接收到的fraction就可能大于1,或者小于0,。大于1,說明波動比較大,獲取到的屬性值將大于目標值。

其實當初分析的時候,有一個誤區,就是我所認為的evaluate中的fraction必需是[0,1]范圍內的一個值,這樣才適合作為一個比例值,所以對于getInterpolation(input)方法返回的值,在mKeyframeNums = 2 的時候,直接傳遞給Evaluator的evaluate方法,一直很困惑,最后才明白,getInterpolation(input)的值,其實不受約束的,完全可以由你自定義的插值函數來控制,最終計算得到的屬性值,也不一定就比用戶傳入到ofFloat()中的Value小。事實確實是這樣,動畫的運動軌跡,是可以在你的指定的屬性值上下波動的。

我們再看其它三種情況的處理:
```java
float intervalFraction = (fraction - prevKeyframe.getFraction()) /
(nextKeyframe.getFraction() - prevKeyframe.getFraction());
return mEvaluator == null ? prevValue + intervalFraction * (nextValue - prevValue) ://未定義Evaluators,則簡單的返回,屬性值計算結束
((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).//按照自定義的Evaluators來處理屬性值
floatValue();

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
  /*
* 返回一個ObjectAnimator對象,
* 如果可變參數values:
* 只傳遞1個值:那么該值作為屬性結束值,
* 傳2個值,則為起始和結束值。
* 2個以上,則起始值,過渡值,結束值
*/
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
ObjectAnimator anim = new ObjectAnimator(target, propertyName);
anim.setFloatValues(values);
return anim;
}
```
**ValueAnimator.java**
```java
/**
* 主要是設置values值
* 如果不是通過工廠方法獲取ObjectAnimator對象,而是通過構造函數的方式,并且values為null或者沒有傳值,那么
* 那么就從該屬性身上去獲取并設置值。屬性也沒有的話,那么根據用戶傳遞的屬性名去獲取并設置值。
*/
@Override
public void setFloatValues(float... values) {
if (mValues == null || mValues.length == 0) {
// No values yet - this animator is being constructed piecemeal. Init the values with
// whatever the current propertyName is
if (mProperty != null) {
setValues(PropertyValuesHolder.ofFloat(mProperty, values));
} else {
setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
}
} else {
super.setFloatValues(values);
}
}

/**
* 接下來看一下有屬性值的情況,會走父類ValueAnimator的setFloatValues,
* 這個方法里面會執行到,如果你已經通過多個PropertyValuesHolder為多個對象定義了多對動畫值。那么會為第一個對象賦值。
* (每一個對象的值集合對應一個PropertyValuesHolder)
* 說明:對于ValueAnimator來說,通常應該傳遞2個或2個以上的屬性值,因為它沒法像ObjectAnimator那樣去根據屬性去
* 獲取起始值。
*
* @param values
*/
public void setFloatValues(float... values) {
if (values == null || values.length == 0) {
return;
}
if (mValues == null || mValues.length == 0) {
setValues(PropertyValuesHolder.ofFloat("", values));
} else {
PropertyValuesHolder valuesHolder = mValues[0];
valuesHolder.setFloatValues(values);
}
// New property/values/target should cause re-initialization prior to starting
mInitialized = false;
}
/**
*
* PropertyValuesHolder持有Property屬性信息和動畫過程中的值,它可以通過ValueAnimator 或者 ObjectAnimator創建多個并行的動畫。除了一些工
* 廠方法,還擁有設置和獲取屬性get、set方法的函數:getPropertyFunction,setupSetterOrGetter,還有更新屬性值的方法setAnimatedValue
*/
PropertyValuesHolder setFloatValues
public void setFloatValues(float... values) {
mValueType = float.class;
mKeyframeSet = KeyframeSet.ofFloat(values);
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* KeyFrame的集合,被ValueAnimator調用去計算keyframe之間的動畫值,因為它是封裝了KeyFrame的存儲、使用細節,所以也放在animation包下
* 類型明確的KeyframeSet子類,相對于那些自定義的TypeEvaluators來說在getValue方法上有速度的提升。因為它不需要對那些原始類型進行自動包裝
*/

public static KeyframeSet ofFloat(float... values) {
boolean badValue = false;
int numKeyframes = values.length;
FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
if (numKeyframes == 1) {
keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f);
keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]);
if (Float.isNaN(values[0])) {
badValue = true;
}
} else {
keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);
for (int i = 1; i < numKeyframes; ++i) {
keyframes[i] =
(FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
if (Float.isNaN(values[i])) {
badValue = true;
}
}
}
if (badValue) {
Log.w("Animator", "Bad value (NaN) in float animator");
}
return new FloatKeyframeSet(keyframes);
}
//返回一個相應的FloatKeyframeSet
public FloatKeyframeSet(FloatKeyframe... keyframes) {
super(keyframes);
}

KeyframeSet.java
public KeyframeSet(Keyframe... keyframes) {
mNumKeyframes = keyframes.length;
mKeyframes = new ArrayList<Keyframe>();
mKeyframes.addAll(Arrays.asList(keyframes));
mFirstKeyframe = mKeyframes.get(0);
mLastKeyframe = mKeyframes.get(mNumKeyframes - 1);
mInterpolator = mLastKeyframe.getInterpolator();
}

以上為賦值部分,最終返回了一個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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
/**
* 參數為Boolean值,表示動畫是否需要reverse,默認為false.
* 調用該方法會在調用者所在的線程中啟動動畫,該線程應該擁有一個輪詢器,如果對象的屬性動畫是View,那么應該
* 在UI線程中調用。
*/

private void start(boolean playBackwards) {
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
mPlayingBackwards = playBackwards;
mCurrentIteration = 0;
mPlayingState = STOPPED;
mStarted = true;
mStartedDelay = false;
sPendingAnimations.get().add(this);
if (mStartDelay == 0) {
// This sets the initial value of the animation, prior to actually starting it running
setCurrentPlayTime(getCurrentPlayTime());
mPlayingState = STOPPED;
mRunning = true;

if (mListeners != null) {
ArrayList<Animator.AnimatorListener> tmpListeners =
(ArrayList<Animator.AnimatorListener>) mListeners.clone();
int numListeners = tmpListeners.size();
for (int i = 0; i < numListeners; ++i) {
tmpListeners.get(i).onAnimationStart(this);
}
}
}
AnimationHandler animationHandler = sAnimationHandler.get();
if (animationHandler == null) {
animationHandler = new AnimationHandler();
sAnimationHandler.set(animationHandler);
}
animationHandler.sendEmptyMessage(ANIMATION_START);
}

以下全是setCurrentPlayTime(getCurrentPlayTime())方法內部的調用邏輯
/**
* 設置動畫的指定起始時間點,如果動畫沒開始,只有設置了該時間點以后,動畫才會開始執行。
* 如果動畫已經在運行了,那么會為動畫的時間重新賦值,并從該點繼續執行,
* 參數:動畫起始點單位,單位:毫秒
*/
public void setCurrentPlayTime(long playTime) {
initAnimation();
long currentTime = AnimationUtils.currentAnimationTimeMillis();
if (mPlayingState != RUNNING) {
mSeekTime = playTime;
mPlayingState = SEEKED;
}
mStartTime = currentTime - playTime;
animationFrame(currentTime);
}
animationFrame很重要的方法,這里是設置啟動時間值調用到的,以后動畫的每一幀都會調用該方法,rgb(255, 255, 255);">在這個方法里計算得到了elapsed fraction,傳遞到
這里是動畫計算過程的第一步(計算已完成動畫時間比),也是每一幀動畫都會執行的地方、后續幀的起始點
/* 處理某一幀的動畫。該參數currentTime用于計算elapsed duration(已完成動畫比)。
* 返回值用于判斷動畫是否已結束。(如果動畫是repeat,那么根據repeatCount來加入計算)
*/
boolean animationFrame(long currentTime) {
boolean done = false;

if (mPlayingState == STOPPED) {
mPlayingState = RUNNING;
if (mSeekTime < 0) {
mStartTime = currentTime;
} else {
mStartTime = currentTime - mSeekTime;
// Now that we're playing, reset the seek time
mSeekTime = -1;
}
}
switch (mPlayingState) {
case RUNNING:
case SEEKED:
float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f;//計算動畫執行的進度,0-1間的小數
if (fraction >= 1f) {
if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) {
// Time to repeat
if (mListeners != null) {
int numListeners = mListeners.size();
for (int i = 0; i < numListeners; ++i) {
mListeners.get(i).onAnimationRepeat(this);
}
}
if (mRepeatMode == REVERSE) {
mPlayingBackwards = mPlayingBackwards ? false : true;
}
mCurrentIteration += (int)fraction;
fraction = fraction % 1f;
mStartTime += mDuration;
} else {
done = true;
fraction = Math.min(fraction, 1.0f);
}
}
if (mPlayingBackwards) {
fraction = 1f - fraction;
}
animateValue(fraction);
break;
}

return done;
}

ObjectAnimator復寫了父類的animateValue,所以這里先走ObjectAnimator的animateValue方法:
animateValue插值計算就在這里進行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  /* 動畫的每一幀都會調用的方法,該方法的目的是將elapsed fraction轉變為一個 interpolated fraction,用于
* 動畫的屬性值(由evaluators計算得到)。該函數通常在動畫更新的時候被調用,但end()方法執行的時候,也會被
* 調用,用于設置屬性的最終值。
* 復寫此方法的時候必須調用父類去計算動畫屬的性值。
* 參數為elapsed fraction
*/
@Override
void animateValue(float fraction) {
super.animateValue(fraction);
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].setAnimatedValue(mTarget);
}
}

按照執行順序,先看一下父類ValueAnimator的animateValue(fraction):動畫計算過程的第二步和第三步在這里進行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void animateValue(float fraction) {
fraction = mInterpolator.getInterpolation(fraction);//計算的第二步:根據elapsed fraction使用Interpolation重新計算一個fraction
mCurrentFraction = fraction;
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].calculateValue(fraction);//計算的第三步:根據fraction,為每一個屬性計算屬性值,調用的是PropertyValuesHolder的calculate方法
}
if (mUpdateListeners != null) {
int numListeners = mUpdateListeners.size();
for (int i = 0; i < numListeners; ++i) {
mUpdateListeners.get(i).onAnimationUpdate(this);//實現監聽,更新動畫的屬性值
}
}
}

計算的第三步是通過PropertyValuesHolder的getValue方法計算屬性值:

1
2
3
void calculateValue(float fraction) {
mAnimatedValue = mKeyframeSet.getValue(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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
PropertyValuesHolder[]  mValues;
for (int i = 0; i < numValues; ++i) {
mValues[i].setAnimatedValue(mTarget);
}
PropertyValuesHolder的setAnimatedValue:重要方法,更新屬性值
void setAnimatedValue(Object target) {
if (mProperty != null) {
mProperty.set(target, getAnimatedValue());
}
if (mSetter != null) {
try {
mTmpValueArray[0] = getAnimatedValue();
mSetter.invoke(target, mTmpValueArray);//這里就是設置屬性值的方法了
} catch (InvocationTargetException e) {
Log.e("PropertyValuesHolder", e.toString());
} catch (IllegalAccessException e) {
Log.e("PropertyValuesHolder", e.toString());
}
}
}

————-ObjectAnimator.animateValue 結束 ,setCurrentPlayTime(getCurrentPlayTime());結束———————————-

到此AnimateValue方法執行完畢,那么setCurrentPlayTime(getCurrentPlayTime());也就執行完畢了。
那么我們回到 ValueAnimator的start(boolean playBackwards)方法,接著來的事就交給handler處理了。那么我們就看看Handler是如何驅動動畫的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
private static class AnimationHandler extends Handler {
/**
* 有兩種消息我們需要關心:ANIMATION_START 和 ANIMATION_FRAME
* START消息:當start()方法被調用的時候,會發送一個START消息。當start()方法被調用的時候,動畫的啟動并不是同步的。
* 因為它可能是在錯誤的線程中被調用,并且由于每個動畫的時序是不同的,所以也不可能與其它的動畫同步。
* 每一個動畫向Handler發送START消息的時候,就會觸發Handler把該動畫放到處于活動的動畫隊列中去,并開始
* 該動畫的frame。
* FRAME消息:只要有活動狀態的動畫需要去處理,就會一直發送。
*/
@Override
public void handleMessage(Message msg) {
boolean callAgain = true;
ArrayList<ValueAnimator> animations = sAnimations.get(); //準備開始執行的動畫
ArrayList<ValueAnimator> delayedAnims = sDelayedAnims.get();//需要延遲執行的動畫
switch (msg.what) {
// TODO: should we avoid sending frame message when starting if we
// were already running?
case ANIMATION_START:
ArrayList<ValueAnimator> pendingAnimations = sPendingAnimations.get();
if (animations.size() > 0 || delayedAnims.size() > 0) {
callAgain = false;
}
// pendingAnims holds any animations that have requested to be started
// We're going to clear sPendingAnimations, but starting animation may
// cause more to be added to the pending list (for example, if one animation
// starting triggers another starting). So we loop until sPendingAnimations
// is empty.
while (pendingAnimations.size() > 0) {
ArrayList<ValueAnimator> pendingCopy =
(ArrayList<ValueAnimator>) pendingAnimations.clone();
pendingAnimations.clear();
int count = pendingCopy.size();
for (int i = 0; i < count; ++i) {
ValueAnimator anim = pendingCopy.get(i);
// If the animation has a startDelay, place it on the delayed list
if (anim.mStartDelay == 0) {
anim.startAnimation();
} else {
delayedAnims.add(anim);
}
}
}
// 注意,這里沒有break,ANIMATION_FRAME中代碼將繼續執行
case ANIMATION_FRAME:
// 在當前幀內,currentTime對于所有的動畫處理,持有相同的時間
long currentTime = AnimationUtils.currentAnimationTimeMillis();
ArrayList<ValueAnimator> readyAnims = sReadyAnims.get();
ArrayList<ValueAnimator> endingAnims = sEndingAnims.get();

int numDelayedAnims = delayedAnims.size();//如果延遲隊列中的動畫到了該啟動的時候,那么它們加入到活動動畫隊列中去
for (int i = 0; i < numDelayedAnims; ++i) {
ValueAnimator anim = delayedAnims.get(i);
if (anim.delayedAnimationFrame(currentTime)) {
readyAnims.add(anim);
}
}
int numReadyAnims = readyAnims.size();
if (numReadyAnims > 0) {
for (int i = 0; i < numReadyAnims; ++i) {
ValueAnimator anim = readyAnims.get(i);
anim.startAnimation();//該方法將將要執行的動畫加入到活動動畫隊列中,這里其實并沒有刷新屬性值的方法
anim.mRunning = true;
delayedAnims.remove(anim);//動畫開始,將其從延遲動畫中干掉
}
readyAnims.clear();
}

// 處理所有活動中的動畫,animationFrame()的返回值決定該當動畫是否執行完畢
int numAnims = animations.size();
int i = 0;
while (i < numAnims) {
ValueAnimator anim = animations.get(i);
if (anim.animationFrame(currentTime)) {//關鍵代碼,循環的去調用animationFrame方法,去進行一些列的計算,并更新屬性值
endingAnims.add(anim);//如果該動畫執行完畢,則加入到結束隊列中去
}
if (animations.size() == numAnims) {//動畫沒被取消,++i
++i;
} else {//當前正在執行的動畫被cancle的情況
// An animation might be canceled or ended by client code
// during the animation frame. Check to see if this happened by
// seeing whether the current index is the same as it was before
// calling animationFrame(). Another approach would be to copy
// animations to a temporary list and process that list instead,
// but that entails garbage and processing overhead that would
// be nice to avoid.
--numAnims;
endingAnims.remove(anim);
}
}
if (endingAnims.size() > 0) {
for (i = 0; i < endingAnims.size(); ++i) {
endingAnims.get(i).endAnimation();
}
endingAnims.clear();
}

if (callAgain && (!animations.isEmpty() || !delayedAnims.isEmpty())) {//如果有活動或者延遲狀態的動畫,那么就繼續發送FRAME消息,循環
sendEmptyMessageDelayed(ANIMATION_FRAME, Math.max(0, sFrameDelay -
(AnimationUtils.currentAnimationTimeMillis() - currentTime)));
}
break;
}
}
}

在ANIMATION_FRAME消息處理的結尾結處:會判斷是否還有活動或者延遲沒執行的動畫,如果有,則post一個ANIMATION_FRAME消息,然后就會再次執行動ANIMATION_FRAME消息的邏輯。如此反復,所有的動畫將會執行完。那么大家肯定很好奇,既然一直在執行動畫,那屬性值的更新一定能從這里跟蹤到,那在哪里呢?不用想,肯定是在startAnimation()方法中,點進去看看:

1
2
3
4
5
6
7
8
9
10
11
12
private void startAnimation() {
initAnimation();
sAnimations.get().add(this);
if (mStartDelay > 0 && mListeners != null) {<span style="font-family: Arial, Helvetica, sans-serif;">// startDelay == 0 的Listeners 已經在start()中notify了,這里只對延遲動畫做處理</span>
ArrayList<AnimatorListener> tmpListeners =
(ArrayList<AnimatorListener>) mListeners.clone();
int numListeners = tmpListeners.size();
for (int i = 0; i < numListeners; ++i) {
tmpListeners.get(i).onAnimationStart(this);
}
}
}

這個是ValueAnimator的startAnimation方法,只是做了初始化動畫和對延遲動畫監聽器回調的處理,(注意,這里initAnimation也被復寫了,對于NineOldAndroids來說是很重要的方法,因為自定義屬性的初始化就在這里進行的,為目標對象重新設置了兼容版本的屬性),并沒有更新值的方法啊,后來在AnimationHandler中,一個一個方法的點進去,跟蹤到了animateFrame方法,該方法結尾處調用了animateValue方法,開始一直認為這個方法只是計算一些屬性值,而且當時打斷點的方式是直接根據Ctrl+B的方式進到方法里面打的斷點,沒有注意到實現類復寫了方法,所以觀察斷點的時候,一走就走到了父類的方法中去。后來也是靈光一閃,感覺是ObjectAnimator是復寫了父類的方法,做了一些自己特有的處理,后來一看,果然是,ObjectAnimator在這個復寫的方法中進行了更新屬性值的操作,真是豁然開朗啊,就此得出結論,整個分析過程就此完成了。在上面分析動畫執行過程中,已經在代碼塊上面寫上了結論,其實也是后來添加上去的。
animationFrame方法是在ANIMATION_FRAME消息中,計算一個動畫是否已經結束的時候調用的:

1
2
3
4
5
6
while (i < numAnims) {
ValueAnimator anim = animations.get(i);
if (anim.animationFrame(currentTime)) {//大家可以看這里,循環的去animationFrame方法,去進行一些列的計算,如果該動畫執行完畢,則加入到結束隊列中去
endingAnims.add(anim);
}
}

animateFrame方法,該方法在結尾處調用animateValue方法,這個是子類ObjectAnimator的animateValue方法:

1
2
3
4
5
6
7
8
@Override
void animateValue(float fraction) {
super.animateValue(fraction);//做一些屬性值的計算
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].setAnimatedValue(mTarget);//這里將屬性值更新到了Target上去
}
}

這里使用了反射,將屬性值設置到了目標對象上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void setAnimatedValue(Object target) {
if (mProperty != null) {
mProperty.set(target, getAnimatedValue());
}
if (mSetter != null) {
try {
mTmpValueArray[0] = getAnimatedValue();
mSetter.invoke(target, mTmpValueArray);
} catch (InvocationTargetException e) {
Log.e("PropertyValuesHolder", e.toString());
} catch (IllegalAccessException e) {
Log.e("PropertyValuesHolder", e.toString());
}
}
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static ViewPropertyAnimator animate(View view) {
ViewPropertyAnimator animator = ANIMATORS.get(view);
if (animator == null) {
final int version = Integer.valueOf(Build.VERSION.SDK);
if (version >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
animator = new ViewPropertyAnimatorICS(view);
} else if (version >= Build.VERSION_CODES.HONEYCOMB) {
animator = new ViewPropertyAnimatorHC(view);
} else {
animator = new ViewPropertyAnimatorPreHC(view);
}
ANIMATORS.put(view, animator);
}
return animator;
}

ViewPropertyAnimator是一個幫助類,使得同時創建多個屬性動畫變得更加方便,后面會介紹到。
那么我們看看是如何初始化自定義屬性的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ObjectAnimator.java
@Override
void initAnimation() {
if (!mInitialized) {
// mValueType may change due to setter/getter setup; do this before calling super.init(),
// which uses mValueType to set up the default type evaluator.
if ((mProperty == null) && AnimatorProxy.NEEDS_PROXY && (mTarget instanceof View) && PROXY_PROPERTIES.containsKey(mPropertyName)) {
setProperty(PROXY_PROPERTIES.get(mPropertyName));
}
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].setupSetterAndGetter(mTarget);
}
super.initAnimation();
}
}

PreHoneycombCompat類:擁有所有的屬性,向外暴露屬性及屬性方法,PreHoneycombCompat部分代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
final class PreHoneycombCompat {
static Property<View, Float> ALPHA = new FloatProperty<View>("alpha") {
@Override
public void setValue(View object, float value) {
AnimatorProxy.wrap(object).setAlpha(value);
}

@Override
public Float get(View object) {
return AnimatorProxy.wrap(object).getAlpha();
}
};
static Property<View, Float> PIVOT_X = new FloatProperty<View>("pivotX") {
@Override
public void setValue(View object, float value) {
AnimatorProxy.wrap(object).setPivotX(value);
}

@Override
public Float get(View object) {
return AnimatorProxy.wrap(object).getPivotX();
}
};
}
..........

而內部實現使用了代理類:AnimatorProxy。 關鍵是這個AnimatorProxy類,它是最終實現類。所有的屬性的update都由它來處理,所有的處理都是先計算值然后通過matrix來實現。

1
2
3
4
5
6
7
8
9
10
11
12
13
 
private void computeRect(final RectF r, View view) {
..............
}

private void transformMatrix(Matrix m, View view) {
...............
}

@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
.............
}

另外, 在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
2
3
4
5
ObjectAnimator animX = ObjectAnimator.ofFloat(myView, "x", 50f);
ObjectAnimator animY = ObjectAnimator.ofFloat(myView, "y", 100f);
AnimatorSet animSetXY = new AnimatorSet();
animSetXY.playTogether(animX, animY);
animSetXY.start();

一個ObjectAnimator屬性

1
2
3
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvyY).start();

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<set android:ordering="sequentially">
<set>
<objectAnimator
android:propertyName="x"
android:duration="500"
android:valueTo="400"
android:valueType="intType"/>
<objectAnimator
android:propertyName="y"
android:duration="500"
android:valueTo="300"
android:valueType="intType"/>
</set>
<objectAnimator
android:propertyName="alpha"
android:duration="500"
android:valueTo="1f"/>
</set>

當然,為了執行該動畫,你還需要在Java代碼中應用它們:

1
2
3
4
5
java
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,
R.anim.property_animator);
set.setTarget(myObject);
set.start();

歡迎大家對本篇文章提出寶貴建議,如果有理解不到位的地方,還望指正。

轉載于:https://www.cnblogs.com/android-blogs/p/5735573.html

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/3/180405.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息