一、概述

提到屬性動畫,發現自己之前只是限於簡單的使用,對它只有很淺的認識。都知道,屬性動畫是補間動畫的增強版,具有更強大的功能。我對它可是“傾慕”已久,發誓一定要“好好把握”!之前只有簡單認識,現在開始深入瞭解。

說到Android的動畫,Google Api Guides中分了三種,分別是:Property AnimationView Animation,和Drawable Animation。見下圖:
這裡寫圖片描述

其中Property Animation就是屬性動畫,View Animation是補間動畫(Tween Animation),而Drawable Animation就是幀動畫(Frame Animation)。

屬性動畫是Android 3.0(API11)才出現的。

現在是屬性動畫頻段,所以先重溫一下屬性動畫的基礎知識。

二、屬性動畫基礎

下面一段是Api Guides關於屬性動畫的描述。

1. 屬性動畫

prop_anim

獻醜簡單翻譯一下:
屬性動畫體系是一個強大的框架,允許你對幾乎任何東西(物件)進行動畫。你可以定義一個動畫,隨著時間改變任何物件的屬性,不管它是否在螢幕中繪製。屬性動畫在指定長度的時間內改變一個屬性(物件的一個全域性屬性)的值。要對一些屬性執行動畫,你(需要)指定你想要對其進行動畫的物件屬性,例如,物件在螢幕中的位置,你想讓動畫執行多長時間,和你想在什麼值之間執行動畫。

屬性動畫可以設定以下引數:
Duration:動畫持續時間。預設300毫秒。
Time interpolation:時間插值。可以指定插值器,確定屬性值的計算方法。
Repeat count and behavior:重複次數和行為。可設定重複次數和重複方式(重新開始或翻轉)。
Frame refresh delay:幀重新整理時間間隔。指定每隔多長時間重新整理一幀,預設10毫秒重新整理一幀,但還是要取決於系統的佔用和執行速度。
下面是屬性動畫的計算過程:
屬性動畫計算過程

屬性動畫內部封裝了一個TimeInterpolator用於定義動畫插值,一個TypeEvaluator用於確定如何計算屬性值。再給定開始和結束的屬性值,以及持續時間duration,呼叫start()方法,就可以啟動屬性動畫了。在整個動畫執行過程中,ValueAnimator根據動畫總時長duration和動畫已經執行的時間,計算出一個在0到1之間的時間流逝小數(elapsed fraction),這個小數(elapsed fraction)表示動畫已經執行的時間百分比,0表示0%,1表示100%。

當ValueAnimator開始計算時間流逝小數(elapsed fraction),它就會呼叫時間插值器TimeInterpolator,計算出一個插值小數(interpolated fraction)。插值小數(interpolated fraction)是將時間插值(time interpolation)計算在內與時間流逝小數流(elapsed fraction)對應生成的一個小數。

計算了插值小數(interpolated fraction),ValueAnimator就會呼叫合適的TypeEvaluator,根據插值小數(interpolated fraction)、開始值和結束值計算出當前的屬性值。
以上這些均是翻譯自Api Guiders。

2. 屬性動畫(Property Animation)和補間動畫(View Animation)的不同

屬性動畫與補間動畫的不同之處在於以下幾個方面:

·> 補間動畫只能定義兩個關鍵幀在“透明度”、“旋轉”、“縮放”、“位移”4個方面的變化,但屬性動畫可以定義任何屬性變化。

·> 補間動畫只能對UI元件(View)執行動畫,但屬性動畫幾乎可以對任何物件執行動畫(不管它是否會繪製(顯示)在螢幕上)。

·> 補間動畫還有一個缺點:它只改變繪圖位置,不改變控制元件自身。比如,給一個按鈕添加了一個劃過螢幕的動畫,動畫會正確執行動畫,但是實際可以點選的按鈕位置沒有變還在原來位置。而屬性動畫可以動態改變任何物件(View或非View)的屬性值,而且物件是作了真正的改變。

·> 但是,補間動畫的程式碼量少,寫程式碼更省時間。

3. 屬性動畫API概述

在android.animation包下,可以找到大部分屬性動畫體系的API。因為在補間動畫體系已經在 android.view.animation包下定義了許多插值器(interpolator ),這些插值器也可以用於屬性動畫。下面以表格表示屬性動畫體系的構成。

表1:Animator

描述
Animator 提供建立動畫的最基本結構,一般不直接使用。以下都是它的子類。
ValueAnimator 屬性動畫主要的時間引擎,負責計算各個幀的屬性值。它定義了計算屬性動畫值的所有核心功能,包括計算每一個動畫的時間細節、動畫是否重複的資訊、接收更新事件的監聽,以及設定自定義計算型別的能力。 動態計算屬性由兩部分組成:計算: 一,計算動畫各個幀的相關屬性值;二,設定這些值給執行動畫的物件和屬性。 ValueAnimator不執行第二部分,所以你必須監聽由ValueAnimator計算的屬性值的更新,並且用你自己的邏輯改變你想要執行動畫的物件。
ObjectAnimator ValueAnimator的子類,允許你設定目標物件和物件的屬性來執行動畫,每當它計算出一個新的動畫值,就會更新一次屬性值。多數情況下,只需要ObjectAnimator,因為它比較簡單。但有時候你需要使用ValueAnimator,因為ObjectAnimator會有一些侷限。
AnimatorSet 是Animator的子類。可以將多個動畫組合到使它們一起執行。你可以設定這些動畫一起播放或者按次序播放或者延遲指定時間後播放。

 
計算器告訴屬性動畫體系如何計算給定屬性的值。它們接收由Animator類提供的定時資料、動畫的開始結束值,並根據這些資料計算屬性的動畫值。屬性動畫體系提供了以下幾種計算器:

表2:Evaluator

類/介面 描述
IntEvaluator 用於計算int型別屬性值的預設計算器
FloatEvaluator 用於計算float型別屬性值的預設計算器
ArgbEvaluator 用於計算以十六進位制表示的顏色屬性值的預設計算器
TypeEvaluator 一個你可以用來(實現這個介面)建立自己的計算器的介面。如果你執行動畫的物件屬性不是一個int、float或color,你必須實現TypeEvaluator介面,指定如果計算物件屬性的動畫值。如果你想讓處理int、float和color值的過程與預設(估值器的處理)方式不同,你也可以指定一個自定義的估值器(TypeEvaluator)來計算處理這些值。

 
時間插值器定義了動畫中具體的值是如何作為一個時間函式被計算的。例如,你可以讓動畫在整個動畫執行過程勻速執行,你也可以讓動畫隨時間非線性執行,比如,在開始的時候加速結束的時候減速。表3列出了android.view.animation包下的插值器(interpolator),如果提供的插值器不能滿足你的需求,就實現TimeInterpolator建立你自己的插值器。
 
表3:Interpolator

類/介面 描述
AccelerateDecelerateInterpolator 加速減速插值器。開始和結束的變化速度慢中間加速的。先慢後快再慢。
AccelerateInterpolator 加速插值器。開始的變化速度慢然後加速。先慢後快。
AnticipateInterpolator 後退插值器。開始向後然後快速向前。先向後再快速向前。
AnticipateOvershootInterpolator 後退過沖插值器。變化開始先後,然後快速向前,超過目標值,最終回到終值。向後,前衝,超出,返回。
BounceInterpolator 彈跳插值器。彈跳幾次後回到最終位置。終點彈跳。
CycleInterpolator 正弦週期插值器。速率按正弦曲線週期性變化。正弦週期。
DecelerateInterpolator 減速插值器。變化速度開始快然後減速。先快後慢。
LinearInterpolator 線性插值器。勻速變化。勻速。
OvershootInterpolator 過沖插值器。快速向前,超過終值,再返回。前衝,超出,返回。
TimeInterpolator 時間插值器。一個介面,允許你實現自己的插值器。

4. 使用ValueAnimator進行動畫

ValueAnimator通過不斷控制 的變化,再不斷 手動 賦給物件的屬性,從而實現動畫效果。

ValueAnimator類允許你在一個動畫的持續過程中通過指定一組intfloat,或者color值對某種型別的值進行動畫計算。獲取一個ValueAnimator可以通過呼叫它的其中一個工廠方法:ofInt()ofFloat() 或者ofObject()。例如:

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

在這段程式碼總,當start()方法執行時,ValueAnimator開始在1000ms時間內,從0帶100計算動畫值。
你也可以制定一個自定義型別來動畫計算,如下

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

上面程式碼中,當start()方法開始執行時,ValueAnimator開始在1000ms動畫持續時間內,通過MyTypeEvaluator提供的邏輯在開始屬性值和結束屬性值之間計算動畫值。

你可以通過給ValueAnimator新增一個AnimatorUpdateListener來使用動畫值,如下面程式碼所示:

animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator updatedAnimation) {
        // You can use the animated value in a property that uses the
        // same type as the animation. In this case, you can use the
        // float value in the translationX property.
        float animatedValue = (float)updatedAnimation.getAnimatedValue();
        textView.setTranslationX(animatedValue);
    }
});

在onAnimationUpdate()方法中你可以訪問更新後的動畫值並且把它用於你的一個控制元件的屬性上。

5. 使用ObjectAnimator進行動畫

ObjectAnimatorValueAnimator的一個子類,結合ValueAnimator 的時間引擎和值計算,能夠動畫計算一個目標物件的屬性。這使得對一些物件進行動畫計算更簡單,你不需要再實現ValueAnimator.AnimatorUpdateListener,因為進行動畫計算的屬性會自動更新。
例項化ObjectAnimator與ValueAnimator類似,但是,你也要指定物件和物件的屬性名,以及開始值和結束值。如下

ObjectAnimator animation = ObjectAnimator.ofFloat(textView, "translationX", 100f);
animation.setDuration(1000);
animation.start();

為了使ObjectAnimator能夠正確的更新屬性值,你需要做到以下幾個方面:

  • 你要進行動畫計算的物件屬性必須有以set<PropertyName>()為格式的setter方法(駝峰式)。因為ObjectAnimator在動畫中要自動更新屬性,它必須能夠使用setter方法訪問這個屬性。例如,屬性名為foo,你需要有一個setFoo()方法。如果setter方法不存在,你有三種選擇:
        · 如果你有許可權,新增一個setter方法到類中。
        · 使用一個包裝類,這個包裝類有許可權改變屬性值,並且使這個包裝有一個有效的setter方法接收屬性值並把它交給原來的物件。
        · 使用ValueAnimator代替。

  • 如果你使用ObjectAnimator的一個工廠方法建立例項時,只給 values..指定一個值,它會被當做動畫的結束值。因此你要進行動畫計算的物件屬性必須要有一個getter方法用於獲取動畫的開始值。這個getter方法必須是 get<PropertyName>()格式。例如,屬性名是foo,那麼就需要一個getFoo()

  • 進行動畫計算的屬性的getter(如果需要)和setter方法的型別必須和你指定給ObjectAnimator的開始值和結束值的型別相同。例如,要構造下面的ObjectAnimator,你必須要有targetObject.getPropName(float)targetObject.getPropName(float) 兩個方法。

    ObjectAnimator.ofFloat(targetObject, "propName", 1f)
  • 根據你要進行動畫計算的屬性或物件,你可能需要隨著屬性值的更新對一個View呼叫invalidate()方法強制螢幕繪製它。你可以在onAnimationUpdate()回撥中呼叫invalidate()方法。例如,對Drawable物件的顏色屬性進行動畫計算,當物件重繪自己時就會引起螢幕更新。View所有的屬性的setter方法,例如setAlpha()setTranslationX()都對View作了適當的重繪,所以當你呼叫這些方法賦新值的時候,你不需要對View進行重繪。

6. 用AnimatorSet編排多種動畫

很多情況下,我們想根據另一個動畫的開始或者結束播放一個動畫。安卓系統允許你將多個動畫一起裝進AnimatorSet,以便你能夠指定是否同時,或者按順序,或者經過一個指定的延時來啟動這些動畫。你也可以讓AnimatorSet物件相互巢狀。

下面的示例程式碼取自 Bouncing Balls 案例,它以以下方式執行下面的Animator物件:
1. 執行彈跳動畫(bounceAnim)。
2. 同時執行壓扁動畫1(squashAnim1),壓扁動畫。2(squashAnim2),伸展動畫1(stretchAnim1),和伸展動畫2(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();

7. 動畫監聽

你可以用以下監聽器監聽動畫執行期間的重要事件。

  • Animator.AnimatorListener

    • onAnimationStart():當動畫開始時呼叫。
    • onAnimationEnd():當動畫結束時呼叫。
    • onAnimationRepeat() :當動畫重複時呼叫。
    • onAnimationCancel():當動畫取消時呼叫。一個被取消的動畫也會呼叫onAnimationEnd(),不管它們如何結束。
  • ValueAnimator.AnimatorUpdateListener

    • onAnimationUpdate():在動畫的每一幀呼叫。我們可以監聽這個事件來使用動畫執行期間ValueAnimator產生的計算值。要使用這個值,拿到傳進事件的ValueAnimator物件使用getAnimatedValue()方法來獲得當前的動畫值。如果你是用ValueAnimator,需要實現這個監聽。
      根據正在進行動畫的屬性或物件,你可能需要使用新的動畫值,呼叫控制元件(View)的invalidate()強制螢幕去重繪這個控制元件(View)。例如,對Drawable物件的顏色屬性進行動畫計算,當物件重繪自己時就會引起螢幕更新。View所有的屬性的setter方法,例如setAlpha()setTranslationX()都對View作了適當的重繪,所以當你呼叫這些方法賦新值的時候,你不需要對View進行重繪。

如果你不想實現Animator.AnimatorListener介面的所有方法,你可以繼承AnimatorListenerAdapter類代替實現Animator.AnimatorListener介面,AnimatorListenerAdapter類提供了介面所有方法的空實現,你可以有選擇的去重寫。
例如,API demos的Bouncing Balls案例中建立了一個AnimatorListenerAdapter只作了onAnimationEnd() 的回撥:

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

7. 對ViewGroup的佈局改變執行動畫

屬性動畫系統提供了動畫改變ViewGroup物件的功能,還提供了一個簡便方式讓View物件執行動畫。

你可以使用LayoutTransition(佈局切換)類對ViewGroup內的佈局改變執行動畫。ViewGroup內的控制元件(Views)會經過一個出現動畫和消失動畫,當你把它們新增到ViewGroup或從中移除,或者當你呼叫控制元件的setVisibility()方法設定VISIBLEINVISIBLEGONE。當你新增或移除控制元件時,ViewGroup中其餘的控制元件也會動畫到它們新的位置。你可以呼叫setAnimator()方法傳入一個Animator物件和一個以下LayoutTransition的常量,給LayoutTransition物件指定下面的動畫。

  • APPEARING:一個標示,表示子控制元件上正在容器內出現時執行的動畫。
  • CHANGE_APPEARING:一個標示,表示子控制元件由於一個新的子控制元件正在容器中的出現而變化時執行的動畫。
  • DISAPPEARING:一個標示,表示子控制元件上正從容器內消失時執行的動畫。
  • CHANGE_DISAPPEARING:一個標示,表示子控制元件由於一個子控制元件正在容器中的消失而變化時執行的動畫。

你可以為這四種類型的事件自定義你自己的動畫,或者使用系統預設的動畫。

為ViewGroup設定預設的佈局轉換非常簡單,你只需要在xml佈局檔案中設定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,就會自動為容器中新增的,移除的,以及剩下的子控制元件執行動畫。

8. 使用一個型別計算器

我們知道,Android系統的計算器型別有int,float,和color三種類型,分別由IntEvaluatorFloatEvaluator,和ArgbEvaluator三種類型的計算器支援。如果想使用一個Android系統不知道的型別的計算器來進行動畫,你可以通過實現TypeEvaluator來建立自己的計算器。

你只需要實現TypeEvaluator的evaluate()方法。這使你使用的屬性動畫能夠在當前動畫點為你的動畫屬性返回一個合適的值。

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之間),然後根據你使用的是什麼插值器計算出一個插值器版本的小數。這個插值小數是你的TypeEvaluator接收的浮點引數,所以當計算動畫值時,你不必把插值器計算在內。

9. 使用一個插值器

插值器定義了動畫中的具體值是如何作為一個時間函式被計算出來的。例如,你可以指定動畫在整個過程中以線性執行,這就意味著在整個執行時間內,動畫均勻地進行,或者你也可以指定動畫使用非線性插值器,例如,動畫的開始加速,結束減速。

在動畫體系中插值器從Animator獲得一個小數,這個小數代表動畫的時間流逝。插值器修改這個小數以便與它想要提供的動畫的型別一致。Android系統在Android.view.animation包下提供了一套插值器。如果這些插值器沒有一個能滿足你的需要,你可以實現TimeInterpolator介面建立你自己的插值器。

例如,以下比較了預設插值器中加速減速插值器(AccelerateDecelerateInterpolator)和線性插值器(LinearInterpolator)如何計算插值分數。線性插值器對時間流逝分數無影響。加速減速插值器則在動畫開始時加速,動畫結束時減速。下面的方法定義了這兩個插值器的邏輯:

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

下面的表格展示了動畫持續1000ms這兩個插值器計算出的近似值。

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

如表所示,線性插值器每過200ms以相同的速度0.2對值進行改變。加速減速插值器在200ms到600ms對值的改變比線性插值器更快,在600ms到1000ms對值的改變比線性插值器慢。

10. 指定關鍵幀

一個Keyframe(關鍵幀)物件由一個time/value對(時間/值對)組成,允許你在動畫的一個特定的時間確定一個特定的狀態。每一個關鍵幀也可以擁有自己的插值器控制動畫前一個關鍵幀到這一個關鍵幀時間的間隔內的執行狀態。

要例項化一個關鍵幀(KeyFrame)物件,你必須使用ofInt(),ofFloat(),或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);

11. 使用ViewPropertyAnimator執行動畫

ViewPropertyAnimator提供了一個簡單的方式用一個底層的Animator物件,對View的幾個屬性並行的執行動畫。它很像ObjectAnimator,因為它修改控制元件屬性的實際值,但一次性對許多屬性執行動畫時會更有效率。另外,使用ViewPropertyAnimator的程式碼更簡潔易讀。下面的程式碼片段展示了當同時對View的x和y屬性執行動畫時,使用多重ObjectAnimator物件,單個ObjectAnimator,和ViewPropertyAnimator的不同。

Multiple ObjectAnimator objects (多重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();

One ObjectAnimator (單個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);

12. 在xml中宣告屬性動畫

屬性動畫系統允許你用XML宣告屬性動畫代替以程式設計方式。通過在XML中定義動畫,你可以輕鬆地在多個Activity中重用你的動畫,並更容易地編輯動畫序列。

為了區分舊的view animation框架和新的屬性動畫API的動畫檔案,從Android3.1開始,你應該儲存屬性動畫的XML檔案到res/animator/目錄下。
下面的屬性動畫類可以用相應的標籤支援XML宣告:

ValueAnimator - <animator>
ObjectAnimator - <objectAnimator>
AnimatorSet - <set>

為了找出你可以在XML宣告中使用哪些屬性,請看Animation Resouces(動畫資源)。
下面的案例順序播放了兩組ObjectAnimator,第一個是巢狀的動畫集合,一起播放了兩個ObjectAnimator:

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

你也可以在XML中宣告一個ValueAnimator,如下所示:

<animator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:valueType="floatType"
    android:valueFrom="0f"
    android:valueTo="-100f" />

為了在程式碼中使用前面的ValueAnimator,你必須填充這個物件,新增AnimatorUpdateListener監聽,獲得最新的動畫值,把他用於你的控制元件的一個屬性上面,如下所示:

ValueAnimator xmlAnimator = (ValueAnimator) AnimatorInflater.loadAnimator(this,
        R.animator.animator);
xmlAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator updatedAnimation) {
        float animatedValue = (float)updatedAnimation.getAnimatedValue();
        textView.setTranslationX(animatedValue);
    }
});

xmlAnimator.start();

三、ValueAnimator

3.1 ValueAnimator簡述和常用方法

3.1.1 簡述

下面是屬性動畫的繼承關係和層級結構:
public class ValueAnimator
extends Animator

java.lang.Object
    ↳ android.animation.Animator
        ↳ android.animation.ValueAnimator

已知直接子類
    ObjectAnimator,TimeAnimator

屬性動畫是Animator的子類,是ObjectAnimator的父類,是屬性動畫系統中最核心的一個類。

ValueAnimator為動畫的執行提供了一個簡單的時間引擎,這個時間引擎計算動畫值並設定給目標控制元件。

所有的動畫都使用一個定時脈衝。它執行在自定義的handler中,以確保屬性更改發生在UI執行緒上。

預設情況下,ValueAnimator使用非線性插值器,通過AccelerateDecelerateInterpolator類,使動畫在開始時加速結束時減速。我們可以通過setInterpolator(TimeInterpolator)來設定插值器。

Animator既可以用程式碼建立,也可以用資原始檔建立。下面是一個ValueAnimator資原始檔的例子:

<animator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:valueFrom="1"
    android:valueTo="0"
    android:valueType="floatType"
    android:repeatCount="1"
    android:repeatMode="reverse"/>

ValueAnimator在XML資原始檔中使用<animator>標籤表示。從API23開始,它也能夠與ProperityValuesHolderKeyFrame資源標籤結合建立一個多步驟動畫。注意你可以在整個動畫持續期間,為每一個關鍵幀指定明確的分數值(0~1)來決定什麼時候動畫應該到達那個值。或者你可以把那個分數去掉,那麼動畫幀將在總時間內平均非配:

<animator xmlns:android="http://schemas.android.com/apk/res/android"
          android:duration="1000"
          android:repeatCount="1"
          android:repeatMode="reverse">
    <propertyValuesHolder>
        <keyframe android:fraction="0" android:value="1"/>
        <keyframe android:fraction=".2" android:value=".4"/>
        <keyframe android:fraction="1" android:value="0"/>
    </propertyValuesHolder>
</animator>

3.1.2 常用方法

ValueAnimator常用公共方法

返回值 方法描述
void addUpdateListener(ValueAnimator.AnimatorUpdateListener listener)
新增一個動畫更新監聽到監聽器結合中,這個監聽在整個動畫執行期間不斷髮送更新事件。
void cancel()
取消動畫。
ValueAnimator clone()
建立並返回此物件的副本。
void end()
結束動畫。
float getAnimatedFraction()
返回當前動畫小數,這個小數是在動畫最新幀更新中使用的時間流逝/插值分數(小數)。
Object getAnimatedValue()
當只有一個屬性進行動畫時,被這個ValueAnimator計算的最新值。
Object getAnimatedValue(String propertyName)
通過屬性名獲得被這個ValueAnimator計算的最新值。
static ValueAnimator ofArgb(int… values)
構造並返回一個在顏色值之間進行動畫的ValueAnimator。
static ValueAnimator ofFloat(float… values)
構造並返回一個在浮點值之間進行動畫的ValueAnimator。
static ValueAnimator ofInt(int… values)
構造並返回一個在整型值之間進行動畫的ValueAnimator。
static ValueAnimator ofObject(TypeEvaluator evaluator, Object… values)
構造並返回一個在物件之間進行動畫的ValueAnimator。
static ValueAnimator ofPropertyValuesHolder(PropertyValuesHolder… values)
構造並返回一個在PropertyValuesHolder物件指定的值之間進行動畫的ValueAnimator。
void removeAllUpdateListeners()
從集合中刪除所有偵聽此動畫的幀更新的偵聽器。
void removeUpdateListener(ValueAnimator.AnimatorUpdateListener listener)
從集合中刪除一個偵聽此動畫的幀更新的偵聽器。
ValueAnimator setDuration(long duration)
設定動畫的長度。
void setEvaluator(TypeEvaluator value)
設定型別計算器在計算此動畫的動畫值時使用。
void setInterpolator(TimeInterpolator value)
設定一個時間插值器用於對時間流逝分數(或小數,elapsed fraction)計算。
void setRepeatCount(int vlaue)
設定動畫重複次數。
void setRepeatMode(int value)
規定動畫到達終點時應該怎麼做。從新開始或反轉。
void setStartDelay(long startDelay)
設定呼叫start()方法後動畫的延遲時間,單位是ms。
void start()
啟動動畫。

3.1.3 為ValueAnimator新增監聽

ValueAnimator實現動畫的原理是:通過不斷控制 值 的變化,再不斷 手動 賦給物件的屬性,從而實現動畫效果。正如它的名字一樣,ValueAnimator是針對值的動畫,需要我們監聽值的更新,再手動賦值給物件屬性。
要想將值的改變作用到物件的屬性上面,需要通過它的addUpdateListener()方法為ValueAnimator新增一個動畫更新監聽器(ValueAnimator.AnimatorUpdateListener)。

作一個簡單的實驗,通過ValueAnimator的一個工廠方法ofInt()建立一個屬性動畫,設定好開始值和結束值,以及持續時間,新增動畫更新監聽(ValueAnimator.AnimatorUpdateListener),從上面ValueAnimator常用公共方法我們知道,獲得動畫更新值,要通過它的getAnimatedValue()方法。我們知道屬性動畫預設情況下10ms重新整理一幀,我們設定動畫時長為400ms,開始值為0,結束值為100,在動畫更新監聽(AnimatorUpdateListener)的回撥方法onAnimationUpdate中,我們列印以下動畫執行期間動畫的更新值。如下程式碼所示:

ValueAnimator animator = ValueAnimator.ofInt(0,100);
        animator.setDuration(400);

        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int animatedValue = (int) animation.getAnimatedValue();
                Log.i("AnimatorUpdateListener", "onAnimationUpdate: animatedValue = " + animatedValue);
            }
        });

        animator.start();

列印結果如下圖所示:
onAnimationUpdate

從上面的log列印我們發現:

  1. 動畫值並不是均勻變化的,開始和結束時變化慢,中間變化快。正如前面ValueAnimator的簡述中所提到的:預設情況下,ValueAnimator使用非線性插值器,通過AccelerateDecelerateInterpolator類,使動畫在開始時加速結束時減速。所以說,ValueAnimator預設使用AccelerateDecelerateInterpolator插值器(加速減速插值器)。

  2. 列印條數少於40條。理想情況下,時長400ms,重新整理頻率10ms一幀的話應該是40條的。文章最開始我們提到過屬性動畫預設10毫秒重新整理一幀,但還是要取決於系統的佔用和執行速度

3.2 案例

先做一個簡單的demo。demo的效果是一個背景不斷漸變的TextView。使用ValueAnimator做的動畫。先看效果圖。

va_gradient_bg

下面的程式碼就是使用ValueAnimator實現了TextView背景不斷變化的效果:

mBgGradientAnimator = ValueAnimator.ofInt(Color.RED, Color.BLUE).setDuration(2000);
mBgGradientAnimator.setEvaluator(new ArgbEvaluator());
mBgGradientAnimator.setRepeatCount(ValueAnimator.INFINITE);
mBgGradientAnimator.setRepeatMode(ValueAnimator.REVERSE);

mBgGradientAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        int color = (int) animation.getAnimatedValue();
        mBgGradientTextView.setBackground(new ColorDrawable(color));
        int a = (color >> 24) & 0xFF;
        int r = (color >> 16) & 0xFF;
        int g = (color >> 8) & 0xFF;
        int b = color & 0xFF;
        int textColor = a << 24
                | (0xFF - r) << 16
                | (0xFF - g) << 8
                | (0xFF - b);
        mBgGradientTextView.setTextColor(textColor);
    }
});

mBgGradientAnimator.start();

程式碼中使用ValueAnimator的一個工廠方法ofInt()獲得了一個ValueAnimator例項,程式碼ValueAnimator.ofInt(Color.RED, Color.BLUE).setDuration(2000)所示,在呼叫ofInt()方法時,我們傳入了一個開始顏色值和結束顏色值,然後設定了持續時間為2000ms。

由於我們要對顏色進行動畫計算,我們需要一個可以對顏色進行計算的計算器ArgbEvaluator,通過呼叫setEvaluator(new ArgbEvaluator()),我們設定了顏色計算器。如果我們不使用顏色計算器,會發現效果與我們的預期差別很大,顏色雖然變化,但不是連續變化,不斷的閃動。所以這個計算器是必須的。

接著我們又設定了重複模式為翻轉(ValueAnimator.REVERSE),重複次數為無限次(ValueAnimator.INFINITE)。

現在我們呼叫start()方法是不是會達到我們的預期效果呢?顯然不是。從上面的基礎知識我們知道,ValueAnimator顧名思義,它是一個值的動畫,通過開始值,結束值,持續時間,計算器、插值器共同作用的得到開始值與結束值之間連續的動畫值。我們到這裡只是完成了值的計算和生成,但是沒有作用到我們的控制元件上。那麼我們就需要通過方法addUpdateListener(),新增一個監聽ValueAnimator.AnimatorUpdateListener
在這個監聽的回撥方法中,我們通過方法(int) animation.getAnimatedValue()強轉獲得顏色值,把這個顏色值設為TextView的背景顏色,就可以實現我們想要的效果了。

上面我們提到了顏色插值器ArgbEvaluator,它到底是怎麼計算生成顏色值的呢?看一下它的原始碼可以解惑:

/**
 * This evaluator can be used to perform type interpolation between integer
 * values that represent ARGB colors.
 */
public class ArgbEvaluator implements TypeEvaluator {
    private static final ArgbEvaluator sInstance = new ArgbEvaluator();

    /**
     * Returns an instance of <code>ArgbEvaluator</code> that may be used in
     * {@link ValueAnimator#setEvaluator(TypeEvaluator)}. The same instance may
     * be used in multiple <code>Animator</code>s because it holds no state.
     * @return An instance of <code>ArgbEvalutor</code>.
     *
     * @hide
     */
    public static ArgbEvaluator getInstance() {
        return sInstance;
    }

    /**
     * This function returns the calculated in-between value for a color
     * given integers that represent the start and end values in the four
     * bytes of the 32-bit int. Each channel is separately linearly interpolated
     * and the resulting calculated values are recombined into the return value.
     *
     * @param fraction The fraction from the starting to the ending values
     * @param startValue A 32-bit int value representing colors in the
     * separate bytes of the parameter
     * @param endValue A 32-bit int value representing colors in the
     * separate bytes of the parameter
     * @return A value that is calculated to be the linearly interpolated
     * result, derived by separating the start and end values into separate
     * color channels and interpolating each one separately, recombining the
     * resulting values in the same way.
     */
    public Object evaluate(float fraction, Object startValue, Object endValue) {
        int startInt = (Integer) startValue;
        int startA = (startInt >> 24) & 0xff;
        int startR = (startInt >> 16) & 0xff;
        int startG = (startInt >> 8) & 0xff;
        int startB = startInt & 0xff;

        int endInt = (Integer) endValue;
        int endA = (endInt >> 24) & 0xff;
        int endR = (endInt >> 16) & 0xff;
        int endG = (endInt >> 8) & 0xff;
        int endB = endInt & 0xff;

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

從原始碼的evaluate方法中我們找到了顏色的計算邏輯。我們常用的顏色使用一個十六進位制的int數字表示,比如,0xFFFF0000表示紅色,0xFF00FF00表示綠色,0xFF0000FF表示藍色。0x表示十六進位制,後面有8位十六進位制,每兩位為一組分別代表透明、紅、綠、藍。一位十六進位制可以用四位二進位制表示,兩位十六進位制代表8位二進位制,也就是1位元組,8位十六進位制代表4位元組。而我們知道一個int型別資料佔4個位元組,正好與此相符。

從原始碼中我們看到,要取出一個顏色的透明值,就要右移24位(6個十六進位制位)在與0xFF進行&位運算;要取出紅色部分原色,右移16位&上0xFF;取出綠色,右移8位&上0xFF;取出藍色,直接&上0xFF。比如0xFFFFA480,取出四個色值分別為,0xFF(透明), 0xFF(紅),0xA4(綠),0x80(藍)。

原始碼中分別獲得開始顏色和結束顏色的四個色值,每個色值都是從開始色值和結束色值之間變化,比如紅色部分,通過上面的位運算,獲得startR和endR,通過計算(int)((startR + (int)(fraction * (endR - startR))),獲得一個介於startR和endR之間的色值(可能超出兩者範圍,由fraction而定)。拿到了每一種色值,再通過左移(<<)和或(|)運算,把四個色值拼成一個完整的顏色。我們拿到這個顏色就可以設定給TextView的背景了。

藉助於上面的原理,我們把字型顏色也做成不斷改變的。同樣需要分離顏色,計算顏色,合併顏色三部。這裡想達到的效果是,背景顏色如果是0xFF9980F0,字型顏色就是0xFF[0xFF - 0x99][0xFF - 0x80][0xFF - 0xF0],如上程式碼所示。這樣,TextView的背景顏色和字型顏色都會隨著時間不斷變化了。