Android學習之 屬性動畫

小呂-ICE發表於2014-12-10
    property 動畫系統是相當健壯的框架,它幾乎可以動畫顯示任何物件。 你可以定義一個動畫來定時改變任何物件的屬性值,不論該物件是否在螢幕上顯示。 property 動畫將以一定的時間間隔修改屬性值(物件中的欄位值)。 要實現動畫顯示,你須指定物件的相應屬性(比如物件的螢幕位置),以及動畫時長、動畫時間間隔。

property 動畫系統能讓你設定以下動畫要素:
1.持續時間:指定動畫的持續顯示時間。預設的時長是300毫秒。
2.插值因子:指定屬性值的變化方式,表示為關於動畫已顯示時間的函式。
3.重複次數和方式:指定動畫是否迴圈播放,以及重複的次數。還可以指定動畫是否反向播放。可以設為先正向播放4.再反向回放,如此往復直至達到設定的重複次數。
5.動畫集合:你可以把動畫分為多個邏輯組,以便實現同時播放、順序播放或間隔一段時間再播放。
6.幀重新整理間隔:指定動畫幀的重新整理頻率。預設是每 10 ms重新整理一次,但應用程式實際可執行的重新整理頻率取決於系統整體的繁忙程度,以及系統對定時器的支援程度。

一、Property 動畫的工作方式
    首先,我們通過一個簡單例子來回顧一下動畫的工作原理。圖 1 表明了某物件的 x 屬性變化情況,即在螢幕上水平的位置。 動畫的持續時間設為 40 ms,移動的距離是 40 個畫素點。每隔 10 ms,這是預設的幀重新整理率,此物件橫向移動 10 個畫素點。 在 40 ms 到期後,動畫停止,此物件位置橫移 40 個畫素點。以下是採用線性插值的示例,也即此物件以固定的速度移動。
 
                                          圖 1. 線性動畫的示例
    你還可以把動畫設定為非線性插值方式。圖 2 表示,某物件開始時加速移動,結束時減速移動。 此物件仍然是在 40 ms 內移動 40 個畫素點,但是速度是非線性變化的。在開始時,此動畫加速移動至中間位置,然後再減速移動至終點。 如圖 2 所示,開始和結束階段移動的距離比中間位置要少一些。
 
                                          圖 2. 非線性動畫示例
讓我們來仔細檢視一下 property 動畫系統的關鍵部件在上述動畫中的計算過程。圖 3 展示了主要類的相互工作方式。
 
                                            圖 3. 動畫的計算過程
1、ValueAnimator 物件記錄了動畫自身的一些值,比如已經顯示了多長時間、動畫對應屬性的當前值。
2、ValueAnimator 中封裝了一個定義動畫變化方式的 TimeInterpolator ,以及定義了屬性計算方式的 TypeEvaluator 。比如,圖 2 中的 TimeInterpolator 應該使用 AccelerateDecelerateInterpolator , TypeEvaluator 應該使用 IntEvaluator。要啟動動畫,請建立一個 ValueAnimator 並指定要動畫顯示的屬性的初始值和結束值。呼叫 start() 啟動動畫。在整個動畫過程中, ValueAnimator 根據動畫總時間和已進行的時間和計算出一個時間比例因子(elapsed fraction),大小介於0和1之間。 時間比例因子代表動畫已完成時間的百分比,0 表示 0%,1 表示 100%。例如,在圖 1 中,t = 10 ms 時的時間比例因子應該是 0.25,因為總時間 t = 40 ms。
3、ValueAnimator 算完時間比例因子後,將呼叫已設定好的 TimeInterpolator 計算出一個插值因子(interpolated fraction)。插值因子是一個由時間比例因子換算出來的影像顯示狀態因子。 比如,在圖 2 中,t = 10 ms 時,因為動畫的加速度較小,插值因子約是 0.15,它小於時間比例因子 0.25。 在圖 1 中,插值因子一直保持不變,並與時間比例因子一致。算完插值因子, ValueAnimator 就會呼叫合適的 TypeEvaluator ,根據插值因子、初始值、結束值計算出需要動畫顯示的屬性值。 比如,在圖 2 中,t = 10 ms 時的插值因子是 0.15,所以此時的屬性值應該是 0.15 X (40 - 0),也即為 6。

API 概述
你可以在 android.animation 中找到大部分 property 動畫系統的 API。 由於 view 動畫系統已經在 android.view.animation 中定義了很多 interpolator ,你可以在 property 動畫系統中直接使用它們。 下表對 property 動畫系統中的主要內容進行了說明。
Animator 類只提供了用於建立動畫的基礎構造方法。它只提供了最基本的功能,你必須擴充套件這些功能才能完成對動畫的支援,因此通常你不應該直接使用該類。 以下子類就對 Animator 進行了擴充套件:
表 1. Animator


evaluator 用於告知 property 動畫系統給定屬性值的計算方式。根據 Animator 類給出的計時資料、動畫初始值和結束值,它將計算出需動畫顯示的屬性值。property 動畫系統提供了以下 evaluator:
表 2. Evaluator


時間 interpolator 定義了動畫顯示的計算方式,它是一個關於時間的函式。 比如,你可以指定線性播放動畫,也即全程勻速播放。或者,你也可以指定為非線性播放,比如一開始加速,而臨近結束時減速。 表 3 列出了 android.view.animation 中包含的 Interpolator。 如果已有的 interpolator 無法滿足需求,你可以實現 TimeInterpolator 介面並建立你自己的 interpolator 。 關於編寫自定義 interpolator 的方法。
表 3. Interpolator


二、用ValueAnimator實現動畫
通過指定一組int、float、顏色值, ValueAnimator 類能讓你在動畫過程中修改某些型別的值。通過呼叫其工廠方法(factory method): ofInt()、 ofFloat()、 ofObject(), 你可以獲得一個 ValueAnimator 例項。比如:
ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f);
animation.setDuration(1000);
animation.start();
在這段程式碼中, ValueAnimator 先設定動畫值為0-1之間、持續 1000 ms、並執行start()方法。
你也可以按照以下格式指定自定義型別的動畫值:

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

在這段程式碼中, ValueAnimator 先計算動畫值為startPropertyValue 至 endPropertyValue之間,應用 MyTypeEvaluator,持續時間 1000 ms,並執行 start()。


其實這段程式碼對某物件是無法實現動畫效果的,因為 ValueAnimator 還沒有直接控制物件或其屬性值。你最有可能要做的事就是用計算出來的屬性值修改動畫物件。 通過在 ValueAnimator 中定義偵聽器,你就可以實現這一點。偵聽器可以實現動畫過程中的事件處理,比如幀的重新整理。 在偵聽器的實現程式碼中,你可以呼叫 getAnimatedValue() 獲取為本次畫面重新整理計算出來的屬性值。

三、用ObjectAnimator實現動畫
ObjectAnimator 是 ValueAnimator的子類,它是由計時引擎和計算目標物件的屬性值 ValueAnimator 組合而成的。因為屬性值將會自動更新,你不再需要實現 ValueAnimator.AnimatorUpdateListener了,因此實現任意物件的動畫顯示就更加容易了。
ObjectAnimator 的示例與 ValueAnimator 類似,只是你還需要在動畫區間值之前額外指定物件和屬性名稱(字串格式):
ObjectAnimator anim = ObjectAnimator.ofFloat(foo, "alpha", 0f, 1f);
anim.setDuration(1000);
anim.start();
為了保證 ObjectAnimator 能夠正確地更新屬性值,你必須做到以下幾點:
1、動畫顯示的屬性必須帶有一個 setter 方法(以駱駝拼寫法命名),格式類似 set<propertyName>()。因為ObjectAnimator會在動畫期間自動更新屬性值,它必須能夠用此 setter 方法訪問到該屬性。例如:假設屬性名稱為foo,則需要有一個setFoo()方法。如果此 setter 方法不存在,你有以下三種選擇:
   1>如果你有許可權的話,直接在類中增加此 setter 方法。
   2>用你有權修改的封裝類來增加此 setter 方法,並讓該封裝類來接收屬性值並傳給初始的物件。
   3>換用 ValueAnimator。
2、如果在呼叫ObjectAnimator的某個工廠方法時,你只為 values... 引數指定了一個值,那此值將被認定為動畫屬性的結束值。這樣的話,動畫顯示的屬性必須帶有一個 getter 方法,用於獲取動畫的起始值。 此 getter 方法必須以get<propertyName>()的格式命名。例如:假設屬性名為foo,則需要有一個getFoo()方法。
3、動畫屬性的 getter 方法(如果必要的話)和 setter 方法所運算元據的型別必須與ObjectAnimator中設定的起始和結束值相同。比如,如果你建立了以下ObjectAnimator ,那麼就必須有targetObject.setPropName(float) 和 targetObject.getPropName(float):

ObjectAnimator.ofFloat(targetObject, "propName", 1f)
4、根據不同的動畫顯示物件和屬性,也許你需要呼叫 View 的invalidate()方法,以便用新的屬性值強制重繪螢幕。你應該在onAnimationUpdate() 回撥方法中進行重新整理操作。比如,要動畫顯示 Drawable 物件的顏色屬性,就只有通過重繪才能在螢幕上顯示出變化。View 中所有的屬性 setter 方法,比如setAlpha()和setTranslationX(),都會適時地禁用 View,因此你不需要在用新屬性值呼叫這些方法時禁用 View。

四、用AnimatorSet編排多個動畫
很多時候,你需要在一個動畫的開始和結束時播放另一個動畫。Android 系統可以讓你把多個動畫組合為一個 AnimatorSet ,這樣你就可以設定同時播放、順序播放或延遲一段時間後再播放。 你還可以巢狀多個 AnimatorSet 物件。
如下面範例,將用以下規則播放 Animator 物件:
1、播放 bounceAnim。
2、同時播放 squashAnim1、squashAnim2、stretchAnim1 和 stretchAnim2。
3、播放 bounceBackAnim。
4、播放 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();

五、動畫偵聽器
在播放過程中,你可以用下列偵聽器來監聽那些重要的動畫事件。
Animator.AnimatorListener
  onAnimationStart() —— 動畫開始時呼叫。
  onAnimationEnd() —— 動畫結束時呼叫。
  onAnimationRepeat() —— 動畫迴圈播放時呼叫。
  onAnimationCancel() —— 動畫被取消時呼叫。不管終止的方式如何,被取消的動畫仍然會呼叫onAnimationEnd()。
ValueAnimator.AnimatorUpdateListener
  onAnimationUpdate() —— 動畫每播放一幀時呼叫。在動畫過程中,可偵聽此事件來獲取並使用ValueAnimator計算出來的屬性值。利用傳入事件的ValueAnimator物件,呼叫其getAnimatedValue()方法即可獲取當前的屬性值。如果你使用ValueAnimator,則必需實現此偵聽器。
根據不同的動畫物件及其屬性,你也許需要呼叫 View 的invalidate()方法,強制用新的屬性值重繪螢幕區域。比如,要動畫顯示 Drawable 物件的顏色屬性,只有重繪才能在螢幕上顯示出變化。View 中所有的屬性 setter 方法,比如setAlpha()和setTranslationX(),都會適時地禁用 View,因此你不需要在用新屬性值呼叫這些方法時禁用 View。
如果你不願意實現 Animator.AnimatorListener 中所有的方法,那你可以不用此偵聽器,而是擴充套件 AnimatorListenerAdapter 類。 AnimatorListenerAdapter 類為所有方法都實現了空方法體,你自行選擇並覆蓋即可。
例如 下面範例 建立了一個只帶有 onAnimationEnd() 回撥方法的 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());
}

六、ViewGroup中Layout的動畫
與 View 動畫一樣簡單, property 動畫系統對 ViewGroup 物件的動畫顯示提供了支援。
你可以用 LayoutTransition 類來動畫顯示 ViewGroup 中的 Layout。 通過把 View 加入或移出 ViewGroup ,或者以 VISIBLE、 INVISIBLE、 GONE 呼叫 View 的 setVisibility() 方法,可以實現某個 View 的顯現和消失效果。 當你新增或刪除 View 時,ViewGroup 中的其它 View 也可以用動畫的方式移到新的位置顯示。 你可以呼叫 LayoutTransition 物件的 setAnimator() 方法來定義下列動畫方式,呼叫引數是 Animator 物件和以下 LayoutTransition 常量:
APPEARING —— 元素在容器中顯現時需要動畫顯示。
CHANGE_APPEARING —— 由於容器中要顯現一個新的元素,其它元素的變化需要動畫顯示。
DISAPPEARING —— 元素在容器中消失時需要動畫顯示。
CHANGE_DISAPPEARING —— 由於容器中某個元素要消失,其它元素的變化需要動畫顯示。
你可以為這四種事件定義自己的動畫方式,以便定製 layout 變化時的外觀,也可以只是通知動畫系統採用預設的動畫方式。
範例程式碼:
1、需要把 ViewGroup 的 android:animateLayoutchanges 屬性設為 true。 比如:

<LinearLayout
    android:orientation="vertical"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:id="@+id/verticalContainer"
    android:animateLayoutChanges="true" />
此屬性設為 true 將會自動把 View 加入和移出 ViewGroup 的過程以動畫方式顯示,ViewGroup 中其它的 View 同時也會以動畫方式進行調整。

2、為ViewGroup設定Layout的改變動畫

LayoutTransition mTransition = new LayoutTransition();
mTransition.setAnimator(  
                LayoutTransition.APPEARING,  
                ObjectAnimator.ofFloat(this, "scaleX", 0, 1)); 
mGridLayout.setLayoutTransition(mTransition);  

PS:更詳細的案例可以參考 鴻洋大神的一篇部落格:

http://blog.csdn.net/lmj623565791/article/details/38092093


七、使用TypeEvaluator
如果 Android 系統無法識別需要動畫顯示的屬性值型別,你可以建立自己的 evaluator,實現 TypeEvaluator 即可。Android 系統可識別的型別包括int、float和顏色,分別由 IntEvaluator、 FloatEvaluator、 ArgbEvaluator 提供支援。
TypeEvaluator介面中只要實現一個方法即可,就是 evaluate()。此方法返回一個當前動畫時刻的屬性值。FloatEvaluator類展示了這一過程:
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 (或 ObjectAnimator )執行時,它會先算出當前已顯示的動畫程式比例(介於 0 和 1 之間),然後再根據所用的 interpolator 計算出插值因子。 TypeEvaluator 接收到的 fraction 引數本身就是此插值因子,因此你在計算動畫屬性值時就不必用到 interpolator 了。

八、使用Interpolator
插值器(interpolator)定義了動畫過程中屬性值的變化規則,它是一個關於時間的函式。 比如,你可以把動畫過程設定為線性變化,這意味著它全程都是勻速變化。 或者,你可以設定動畫為非線性變化,比如在開始或結束時加速或減速。
動畫系統中的 interpolator 會從 Animator 中接收到一個時間比例因子,此因子代表了動畫已顯示的時間。 interpolator 會根據動畫的型別修改此因子。Android 系統在 android.view.animation 包中提供了一組通用的 interpolator。如果這些都不滿足需要,你可以實現 TimeInterpolator 介面來建立自己的 interpolator。
作為示例,預設的 AccelerateDecelerateInterpolator 和 LinearInterpolator interpolator 將會以下列方式計算插值因子:
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;
}

九、設定關鍵幀
Keyframe 物件中包含了一個時間/屬性值的鍵值對,用於定義某個時刻的動畫狀態。 每個關鍵幀還可以擁有自己的 interpolator,用於控制前一關鍵幀至本關鍵幀之間的動畫行為。
要例項化一個 Keyframe 物件,你必須使用它的工廠方法 ofInt()、 ofFloat()、 ofObject() 之一,或者獲得相應型別的 Keyframe 。然後,呼叫工廠方法 ofKeyframe() 獲得一個 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);
rotationAnim.start();

十、動畫顯示View
property 動畫系統可以讓 View 物件進行一系列的動畫顯示,相比 view 動畫系統具有更多優勢。 view 動畫系統通過改變 View 物件的繪製方式來實現動畫效果。 因為 View 本身沒有給出屬性以供控制,所以這是由 View 所在容器來完成處理的。 雖然這樣能實現 View 的動畫效果,但 View 物件本身並沒有變化。 因此會出現這種情況:雖然螢幕上的顯示位置已經移動過了,但物件實際仍然停留在原來的位置。 為了消除這一弊病,在 Android 3.0 中給 View 增加了一些新的屬性以及相應的 getter、setter 方法。
1> property 動畫系統可以通過修改 View 物件實際的屬性值來實現螢幕上的動畫效果。此外,當屬性值發生變化時,Views 也會自動呼叫     invalidate()方法來重新整理螢幕。 View 類中新增的便於實現 property 動畫的屬性包括:
2> translationX 和 translationY:這兩個屬性控制著 View 的螢幕位置座標變化量,以 layout 容器的左上角為座標原點。
3> rotation、rotationX 和 rotationY:這三個屬性控制著 2D 旋轉角度(rotation屬性)和圍繞某樞軸點的 3D 旋轉角度。
4> scaleX、scaleY:這兩個屬性控制著 View 圍繞某樞軸點的 2D 縮放比例。
5> pivotX 和 pivotY: 這兩個屬性控制著樞軸點的位置,前述的旋轉和縮放都是以此點為中心展開的。預設的樞軸點是 View 物件的中心點。
   注意: 1、通過呼叫mView.setPivotX() 和 mView.setPivotY()來設定某View的旋轉/縮放樞軸點座標。
            2、此時的原點座標(0,0)為該旋轉/縮放View的左上角,而不再以手機螢幕左上角為x、y軸座標原點。

6> x 和 y:這是指 View 在容器內的最終位置,等於 View 左上角相對於容器的座標加上 translationX 和 translationY 後的值。
7> alpha:表示 View 的 alpha 透明度。預設值為 1 (不透明),為 0 則表示完全透明(看不見)。
要動畫顯示 View 物件的某個屬性,比如顏色或旋轉值,你所有要做的事情就是建立一個 property animator,並設定對應的 View 屬性。比如:

ObjectAnimator.ofFloat(myView, "rotation", 0f, 360f);

十一、用ViewPropertyAnimator實現動畫
利用一個基礎的 Animator 物件, ViewPropertyAnimator 為同時動畫顯示 View 的多個屬性提供了一種捷徑。這種方式與一個 ObjectAnimator 很類似,因為也會修改 View 的實際屬性值,但在需要一次動畫顯示多個屬性時效率會更高一些。 此外,使用 ViewPropertyAnimator 的程式碼會簡潔很多,可讀性更好。以下程式碼段展示了分別使用多個 ObjectAnimator 物件、單個 ObjectAnimator 物件、 ViewPropertyAnimator 物件實現 view 的 x 和 y 屬性的同時變化。
多個 ObjectAnimator 物件

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
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvyY).start();
ViewPropertyAnimator
ViewPropertyAnimator
myView.animate().x(50f).y(100f);

十二、在XML中宣告動畫
property 動畫系統允許你用 XML 宣告 property 動畫,而不需要編寫程式碼來實現。 通過 XML 定義的方式,你可以方便地在多個 activity 中複用動畫資源,並且更容易編排動畫順序。
為了把採用新增 property 動畫 API與採用以前 view animation 框架的動畫檔案區分開來,自 Android 3.1 開始,你應該把 property 動畫 XML 檔案儲存到res/animator/目錄下(而不是res/anim/目錄)。 目錄名animator是可以修改的,但如果你要用 Eclipse ADT(ADT 11.0.0+) 外掛作為 layout 編輯工具,那就不能動了。 因為 ADT 只會搜尋res/animator/目錄下的動畫資源。
以下列出了可用 XML 標記宣告的 property 動畫類:
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 資源置入 AnimatorSet 中,然後設定動畫的所有目標物件,再開始動畫。用 setTarget() 可以很方便地為 AnimatorSet 下所有子元素設定一個目標物件。以下程式碼展示了這種設定方法:
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,
    R.anim.property_animator);
set.setTarget(myObject);
set.start();


備註:該篇內容轉載於:http://blog.sina.com.cn/s/blog_48d4913001011a1u.html [呆呆大蝦:API翻譯系列]

英文原文:http://developer.android.com/guide/topics/graphics/prop-animation.html




相關文章