Android屬性動畫(property animation)完全解析

PropertyAnimation

屬性動畫系統是一個健壯的框架,它可以允許你給任何東西加上動畫.你可以定義一個隨時間推移改變任何對象的屬性的動畫,而不用去管這個對象是不是顯示在屏幕上.屬性動畫可以在一段指定的時間內改變一個對象的屬性.你只需要指定你想加動畫的屬性即可,比如一個View在屏幕上的位置.和動畫的時間,以及屬性變化的區間.

屬性動畫讓你可以定義下列動畫的屬性:

1.   持續時間:動畫的持續時間,默認爲300毫秒

2.   時間插值:用一個函數指定相對於動畫已經運行的時間而言,屬性的值應該是多少

3.   重複次數和行爲:指定是否需要重複播放動畫,以及重複次數.也能指定動畫是否要反向播放.將他設置爲反向且重複後,它會來回反覆播放動畫到對應次數.

4.   動畫集合:用一個set集合把幾個動畫封裝成一組,讓他們同時播放,或者在一個指定的延遲後依次播放

5.   刷新率:指定動畫的刷新率.默認是每10毫秒刷新一次,但是最終的刷新率是由CPU的性能和空閒決定的.

How Property Animation Works

首先,讓我們用一個小例子來回顧下一個動畫是如何工作的.下圖描繪了一個物體正在執行和它的X屬性相關的動畫,X屬性代表了它在屏幕上的橫向位置.動畫的持續時間被設置爲40毫秒,動畫的距離爲40像素.使用默認的10ms的刷新率.在40ms後,動畫結束.物體在橫向座標40處.這是一個線性插值動畫例子,物體勻速運動.

你也能給動畫指定一個非線性的插值.下圖展示了一個物體開始時加速,運動結束時減速的例子.這個物體還是在40ms內運動40像素的距離,不過這次不是線性運動.開始時,動畫加速到中點,然後從中點減速到動畫結束.如下圖所示,中點的速度比起始的速度都快.

現在來仔細看看屬性動畫系統有哪些計算動畫的關鍵組件,圖三描繪了最主要的類是如何和其他類一起工作的.

ValueAnimator對象追蹤着動畫運行的時間,和動畫中的對象的屬性.

ValueAnimator封裝了一個定義了動畫插值的TimeInterpolator對象,以及一個TypeEvaluator對象,定義了正在被動畫的對象屬性的計算方式.例如圖2中, TimeInterpolator對象可能就是一個AccelerateTimeInterpolator對象, TypeEvaluator是一個IntEvaluator對象.

那麼怎麼樣去開啓一個動畫呢?創建一個ValueAnimator並且設置起始的屬性值,以及動畫的持續時間.當你調用start()方法時,動畫開始.在動畫執行過程中, ValueAnimator會基於動畫持續時間和動畫運行時間計算一個在0到1之間的值, 叫做Elapsed fraction.這個值代表着動畫完成的比例.0代表着0%,1代表100%.例如在圖一中,t=10的時候, Elapsed fraction的值是0.25,因爲總時間是40ms.

當ValueAnimator計算完成Elapsed fraction以後,他會調用當前設置的TimeInterpolator去計算一個Interpolated fraction.一個插值函數將一個現有的Elapsed fraction映射到一個考慮了當前設置的Interpolated fraction的新的Elapsed fraction上.例如,圖2中,15ms時,因爲動畫緩慢加速,所以Interpolated fraction大約是15,比Elapsed fraction的25少.而在圖一中, Interpolatedfraction總是和Elapsed fraction相同.

當Interpolated fraction被計算完成,ValueAnimator調用相應的TypeEvaluator來計算正在動畫的實際值,這個值得計算基於Interpolated fraction和屬性的起始值.例如圖二中, Interpolated fraction在15ms的值是15,所以這個屬性就是0.15*(40-0)=6.

APIdemo中的com.example.android.apis.animation 包中有許多使用屬性動畫的例子.

 How Property Animation Differs from View Animation


視圖動畫系統只能給一個View做動畫,所以如果你想去給一個不是View的對象做動畫,你只有自己實現代碼.視圖動畫系統同樣也被它那幾個少的可憐的屬性所限制,例如旋轉和縮放,但是像改顏色的之類就沒了.

另一個視圖動畫的缺陷就是視圖動畫系統僅僅改變了View繪製的方式,而不是真正改變了View的屬性.如果你用動畫把一個按鈕移動到屏幕的另一邊,按鈕原來所在的位置還是會響應點擊事件.所以你要自己再寫代碼解決這個問題.

而在屬性動畫系統中,這些缺陷都沒有了.你能給任何對象的任何屬性做動畫,並且對象的屬性是真實被修改了.屬性動畫系統在它執行動畫上來說也更加健壯.你給你想改變的屬性指定animator例如顏色,位置和尺寸.並且能定義動畫的多個方面,例如插值,以及動畫同步.

但是不管如何,視圖動畫的執行效率和代碼量還是優於屬性動畫的.如何你使用視圖動畫已經滿足你的需求,那麼就沒必要一定要用屬性動畫.有些情況下同時使用視圖動畫和屬性動畫也是可以的.

API Overview


android.animation.包中有許多屬性動畫的API.因爲視圖動畫已經在android.view.animation定義好了許多常用的插值器,你也能在屬性動畫中使用這些插值器.以下表格描述屬性動畫系統的主要組件.

Animator類提供了一個創建動畫的基礎結構,但你通常不需要直接使用這個類,因爲它只提供了必須被繼承以擴展的最小功能.下列子類繼承自Animator:

Table 1. Animators

Class

Description

ValueAnimator

一個最主要的時間引擎,它計算每個幀的屬性值.並且包含了動畫的所有核心功能,包括每個動畫的時間細節,是否重複,動畫事件的監聽,按屬性值的設置自定義規則.一個動畫需要完成兩大塊內容,計算屬性的值,併爲對象設置屬性值,ValueAnimator不負責第二塊內容.所以你必須監聽ValueAnimator計算出來的值並且修改你要做動畫的對象的值.

ObjectAnimator

ValueAnimator的子類,允許你設置一個目標對象和相應的屬性去做動畫.當它每計算一個屬性值時,他就會更新動畫的一個屬性.ObjectAnimator更加常用,因爲這樣用起來更加簡單.但是有的時候還是需要ValueAnimator.因爲ObjectAnimator有一些限制,比如說對應的屬性必須提供對應的getter和setter方法.(譯者注:這裏應該是使用了反射技術)

AnimatorSet

提供一種將動畫分組的機制讓你能將有關聯關係的動畫打包,可以讓動畫同時執行,也能依次執行,或者以指定的延遲來執行.

Evaluators告訴系統怎麼計算屬性值.它持有系統提供的時間數據,動畫開始和結束的值,並且依據這些計算屬性的值.下列是系統提供的evaluators

Class/Interface

Description

IntEvaluator

The default evaluator to calculate values for int properties.

FloatEvaluator

The default evaluator to calculate values for float properties.

ArgbEvaluator

默認的evaluator,用來計算16進製表示的顏色值

TypeEvaluator

一個evaluator的接口,讓你可以自定義你的計算方式,如果你要改變的值不是int float color的話

一個時間插值器用一個時間函數定義了指定的動畫的值如何計算.例如你能指定動畫從頭到尾線性執行,也就是物體做勻速運動.或者指定一個非線性的插值器,例如開始加速,結尾減速.表三描述了中的插值器.如果現有的插值器不能滿足你的需求,你也可以實現TimeInterpolator 接口來自定義一個插值器.

le 3. Interpolators

Class/Interface

Description

AccelerateDecelerateInterpolator

兩頭慢,中間快

AccelerateInterpolator

勻加速

AnticipateInterpolator

剛開始反向走一點,然後加速向前(譯者注:看起來就像一個剛開始蓄力然後加速運行的過程)

AnticipateOvershootInterpolator

剛開始反向走一點,然後加速向前,會衝過終點線,然後返回

BounceInterpolator

撞到終點會反彈

CycleInterpolator

重複多次的

DecelerateInterpolator

減速

LinearInterpolator

勻速

OvershootInterpolator

超過終點反彈回來

TimeInterpolator

讓你自己實現的一個插值器接口

Animating with ValueAnimator


ValueAnimator 類讓你能用一個int,float等的集合對對象的屬性值做動畫.使用工廠方法ofInt(), ofFloat(),or ofObject()可以得到ValueAnimator的一個實例.例如

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


上面這段代碼中,當start()方法執行時ValueAnimator在1000ms的時間內讓一個浮點數從0變到了1.

你也能指定一個自定義類型做動畫,如下.

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


上面這段代碼中,ValueAnimator startPropertyValueendPropertyValue 之間使用MyTypeEvaluator 提供的邏輯計算了一個持續1000ms的動畫.

但是上述代碼段沒有對任何對象造成影響,因爲它還沒有和某個對象關聯起來.給ValueAnimator定義一個監聽器處理動畫運行時的一些事件,比如說幀刷新.當你實現了監聽器,你能通過調用getAnimatedValue()方法來獲得相應幀刷新的值.

Animating with ObjectAnimator


 ObjectAnimatorValueAnimator 的子類,並且結合了ValueAnimator的能力和關聯對象的能力.這使得你給某個對象做動畫就變得簡單的多了,因爲你不必再去實現ValueAnimator.AnimatorUpdateListener,,因爲這個動畫屬性會自動更新.

ObjectAnimator初始化和ValueAnimator類似.但是你要指定一個對象並且用一個字符串去指定你要改變的對象的屬性以及動畫的起始值.

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


爲了使ObjectAnimator 正確的自動更新屬性,你需要這麼做.

A:你想做動畫的對象的屬性必須有個set<propertyName>()形式的setter方法.因爲這裏使用了反射的方法.比如說,如果屬性名是foo,你需要提供一個setFoo()的方法.如果這個setter方法不存在.你有三個選擇:

1.  如果你有權利,那麼就去添加這樣的set方法.

2.  使用一個包裝類將其包裝,給包裝類設置set方法,然後把相應的數值在包裝類裏給原始類的對象送進去.

3.  使用ValueAnimator替代

B:如果你只給 ObjectAnimator的工廠方法的values...參數指定了一個值,那麼這個值就是對象的結束值.所以你就要提供一個get方法讓取得初始值.get方法的形式和上面的set方法類似.

C:getter和setter方法必須操作的是和你給 ObjectAnimator指定的參數類型相同的變量.

ObjectAnimator.ofFloat(targetObject, "propName", 1f)

D:根據你所做動畫的類型,你可能需要調用view的invalidate()方法來強制視圖刷新新的值.你可以在onAnimationUpdate()回調中來做這件事.例如給一個可繪製的對象的顏色屬性做動畫只有在視圖重繪時屏幕纔會更新.View的所有屬性的setter方法都會正確的重繪視圖,例如 setAlpha()setTranslationX() .所以當你調用View的相應方法時就不用刷新了.

Choreographing Multiple Animations withAnimatorSet


很多情況下,一個動畫的依賴着其他動畫的開始或結束.Android系統允許你用一個AnimatorSet將動畫打包.這樣你就可以同步,依次,或延遲之後執行動畫了.你也可以嵌套AnimatorSet 裏的每個其他對象.

請看下面這個來自 Bouncing Balls示範代碼的例子.

1.     Plays bounceAnim.

2.     Plays squashAnim1, squashAnim2, stretchAnim1, and stretchAnim2 at the same time.

3.     Plays bounceBackAnim.

4.     Plays fadeAnim.

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();


Animation Listeners


你能使用下列監聽器監聽一些重要的事件.

·       Animator.AnimatorListener

o  onAnimationStart() 動畫開始時調用

o  onAnimationEnd() 動畫結束時調用

o  onAnimationRepeat() 動畫重新播放時調用

o  onAnimationCancel() 動畫被取消調用,動畫被取消時也會調用onAnimationEnd() 方法

·       ValueAnimator.AnimatorUpdateListener

o  onAnimationUpdate() 動畫的每一幀調用.在這個方法中,你可以調用方法獲得當前計算的值.如果你使用ValueAnimator,那麼實現這個方法是非常重要的.值得注意的是,你可能需要調用 invalidate()方法刷新界面.

如果你不想重寫Animator.AnimatorListener 接口的所有方法,你可以繼承AnimatorListenerAdapter 類.這個類用空方法給接口做了實現,你可以選擇性的重寫.

ValueAnimatorAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
fadeAnim.setDuration(250);
fadeAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
   balls.remove(((ObjectAnimator)animation).getTarget());
}


Animating Layout Changes to ViewGroups


屬性動畫系統給VIewGroup提供了一個像View一樣的簡單的動畫實現方式.

你可以用一個LayoutTransition 類去給佈局做動畫.在VIewGroup裏的View對象在你添加移除或者設置可見度時會經歷一個動畫.剩餘的View也能以動畫的方式移動到它們的新位置.你能調用setAnimator() 方法給LayoutTransition 定義一個動畫,方法中傳入動畫對象和下列常量參數.

·       APPEARING 標記着view出現時的動畫.

·       CHANGE_APPEARING -標記着viewgroup中有新的view加入時改變佈局的動畫.

·       DISAPPEARING -標記着view消失時的動畫.

·       CHANGE_DISAPPEARING 佈局中有View消失而改變佈局的動畫

如果要使用系統默認的佈局動畫,給 android:animateLayoutchanges 設置爲true即可

<LinearLayout
   android:orientation="vertical"
   android:layout_width="wrap_content"
   android:layout_height="match_parent"
   android:id="@+id/verticalContainer"
   android:animateLayoutChanges="true" />


這樣就可以自動播放佈局動畫了

Using a TypeEvaluator


如果你想使用一個對Android系統來說陌生的類型,你可以實現TypeEvaluator 接口來創建一個自己的Evaluator.安卓系統知道的只有int和float以及color,相應的Evaluator有IntEvaluator, FloatEvaluator, ArgbEvaluator 

TypeEvaluate接口只有一個evaluate()方法.用這個在當前動畫的點返回一個合適的值.FloatEvaluate類說明了做法.

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);
   }
}


提示:當ValueAnimator運行時,它計算了當前的elapsed fraction並且計算了一個插值後的版本.這個interpolated fraction是你從fraction參數中獲得的,所以你計算屬性值的時候不必考慮插值.

Using Interpolators


插值器用一個時間函數定義了一個指定的值在動畫過程中如何被計算.

插值器從animator中獲得一個fraction,這個fraction代表着動畫的elapsed time.插值器修改了fraction以覆蓋相應的動畫.系統提供的插值器代碼如下

AccelerateDecelerateInterpolator

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


LinearInterpolator

public float getInterpolation(float input) {
   return input;
}


下面這個表格展示了插值的近似值

ms elapsed

Elapsed fraction/Interpolated fraction (Linear)

Interpolated fraction (Accelerate/Decelerate)

0

0

0

200

.2

.1

400

.4

.345

600

.6

.8

800

.8

.9

1000

1

1

正如表格所顯示的,線性插值器勻速改變值,加減速插值器在200-600比線性快.600-1000ms內比線性慢.

Specifying Keyframes


關鍵幀對象由一組時間/值組成讓你指定某個特定時間的特定狀態.每個關鍵幀也能持有它自己的插值器來控制上一個關鍵幀到當前關鍵幀的值變化.

必須使用一個工廠方法才能初始化一個關鍵幀對象.你之後可以調用ofKeyFrame()工廠方法來獲取aPropertyValuesHolder 對象.一旦你得到了這個對象,你能把它和需要執行動畫的對象一同傳入,這樣你就可以獲得一個ObjectAnimator對象.具體看代碼:

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);


Animating Views


屬性動畫系統提供了流線型的動畫方式,並且比視圖動畫更加有優勢.視圖動畫的變化只是改變了View的繪製方式,這是在每個View的容器中處理的,因爲View沒有可以直接操作的屬性.這回導致View執行了動畫,但是實際上View本身沒有任何變化.Android3.0之後,增加了一些新的屬性以及相應的方法來消除這個缺陷.

屬性動畫系統能通過改變真實屬性的方式給View做動畫.除此以外,當屬性改變時View也能自動調用invalidate()方法刷新屏幕.這些新添加的屬性是:

·       translationX and translationY: 這個屬性控制了View的位置以相對於父控件的上和左座標的方式來表示.

·       rotation, rotationX, and rotationY: 這些屬性控制了View圍繞旋轉軸所做的2D3D旋轉

·       scaleX and scaleY: 這個屬性控制了圍繞樞紐點的2D縮放.

·       pivotX and pivotY: 這兩個屬性控制了發生縮放和旋轉所對應的樞紐點的位置.默認爲View的中心點.

·       x and y: 這是簡單又直接的屬性,描述着translation座標和父容器提供的座標的和,直接決定了View的最終位置.

·       alpha: 透明度.默認1(不透明),0值代表着不可見.

Animating with ViewPropertyAnimator

ViewPropertyAnimator 提供了一種用底層Animator 對象同時給幾個屬性做動畫的方式.看起來很像ObjectAnimator因爲它修改View屬性的真實值,但是在幾個屬性同時動畫時,比ObjectAnimator更有效率.除此之外,使用ViewPropertyAnimator 的代碼更簡潔,更易於閱讀.下列代碼片段比較了使用幾種動畫同時改變XY方式的不同.

Multiple ObjectAnimator objects

ObjectAnimator animX = ObjectAnimator.ofFloat(myView, "x", 50f);
ObjectAnimator animY = ObjectAnimator.ofFloat(myView, "y", 100f);
AnimatorSet animSetXY = new AnimatorSet();
animSetXY.playTogether(animX, animY);
animSetXY.start();

One ObjectAnimator

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);

Declaring Animations in XML


屬性動畫系統讓你能用XML文件取代硬編碼的方式來定義動畫.使用XML文件定義動畫使得你的動畫可以重用,並且更改也會很方便.

爲了和之前的動畫系統區分,你需要把屬性動畫放到res/animator/目錄下.使用animator 目錄是無所謂的.但是如果你想使用ADT的佈局編輯工具的話,你就必須這麼做了.因爲ADT只會搜索這個目錄.

下列屬性動畫有相應的XML標記可以使用.

·       ValueAnimator - <animator>

·       ObjectAnimator - <objectAnimator>

·       AnimatorSet - <set>

下面的例子展示了兩個依次執行的動畫集合.其中第一個是一個重疊的動畫.

<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>


爲了執行這段動畫,在執行動畫之前,你必須將XML文件inflate成一個對象,然後給所有動畫設置目標對象.調用setTarget()方法給所有動畫設置一個共同目標對象.如下代碼所示:

AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,
   R.anim.property_animator);
set.setTarget(myObject);
set.start();


 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章