屬性動畫系統是強大的架構,可讓您為幾乎所有項目加上動畫效果。您可以定義動畫,隨時間變更任何物件屬性,無論物件是否繪製到畫面上都沒關係。屬性動畫會在指定時間長度內變更屬性 (物件中的欄位) 值。如要製作動畫,請指定要製作動畫的物件屬性,例如物件在畫面上的位置、動畫的製作時間長度,以及要製作動畫的值範圍。
屬性動畫系統可讓您定義動畫的下列特徵:
- 時間長度:您可以指定動畫的時間長度。預設長度為 300 毫秒。
- 時間插補:您可以指定如何根據動畫目前的經過時間,計算屬性的值。
- 重複次數和行為:您可以指定動畫在達到持續時間結尾時是否要重複播放,以及重複播放的次數。您也可以指定是否要反向播放動畫。設定為「反向」會重複正向和反向播放動畫,直到達到重複次數為止。
- 動畫師集合:您可以將動畫分組為邏輯集合,以便同時、依序或在指定延遲後播放。
- 影格重新整理延遲:您可以指定動畫影格的重新整理頻率。預設值為每 10 毫秒重新整理一次,但應用程式重新整理影格的速度最終取決於系統的整體忙碌程度,以及系統服務基礎計時器的速度。
如要查看屬性動畫的完整範例,請參閱 GitHub 上 CustomTransition 範例中的 ChangeColor 類別。
屬性動畫的運作方式
首先,我們來看一個簡單的動畫範例,瞭解動畫的運作方式。圖 1 描繪一個假設物件,該物件的 x 屬性會產生動畫效果,代表物件在畫面上的水平位置。動畫時間長度設為 40 毫秒,移動距離則為 40 像素。每 10 毫秒 (預設影格重新整理率),物件會水平移動 10 像素。40 毫秒後,動畫會停止,物件會停在水平位置 40。這是使用線性插補的動畫範例,表示物件會以固定速度移動。
圖 1. 線性動畫範例
您也可以指定動畫採用非線性插補。圖 2 說明假設的物件在動畫開始時加速,在動畫結束時減速。物件仍會在 40 毫秒內移動 40 像素,但不會以線性方式移動。在動畫開始時,這個動畫會加速到中途,然後從中途開始減速,直到動畫結束。如圖 2 所示,動畫開頭和結尾的移動距離小於中間。
圖 2. 非線性動畫範例
我們來詳細瞭解屬性動畫系統的重要元件,如何計算上述動畫。圖 3 說明主要類別之間的互動方式。
圖 3. 動畫的計算方式
ValueAnimator 物件會追蹤動畫的時間軸,例如動畫的執行時間長度,以及動畫屬性的目前值。
ValueAnimator 會封裝 TimeInterpolator (定義動畫插補) 和 TypeEvaluator (定義如何計算要製作動畫的屬性值)。舉例來說,在圖 2 中,使用的 TimeInterpolator 會是 AccelerateDecelerateInterpolator,而 TypeEvaluator 則會是 IntEvaluator。
如要啟動動畫,請建立 ValueAnimator,並提供要製作動畫的屬性起始值和結束值,以及動畫的持續時間。呼叫 start() 時,動畫會開始播放。在整個動畫期間,ValueAnimator 會根據動畫的持續時間和經過的時間,計算介於 0 到 1 之間的經過的分數。經過的分數代表動畫完成的時間百分比,0 代表 0%,1 代表 100%。舉例來說,在圖 1 中,t = 10 毫秒時經過的分數為 0.25,因為總時間長度為 t = 40 毫秒。
ValueAnimator 計算完經過的分數後,會呼叫目前設定的 TimeInterpolator,計算內插分數。插補分數會將經過的分數對應至新的分數,並考量設定的時間插補。舉例來說,在圖 2 中,由於動畫會緩慢加速,因此在 t = 10 毫秒時,內插分數 (約為 0.15) 小於經過的分數 (0.25)。在圖 1 中,內插分數一律與經過的分數相同。
計算內插分數時,ValueAnimator 會呼叫適當的 TypeEvaluator,根據內插分數、起始值和動畫的結束值,計算要製作動畫的屬性值。舉例來說,在圖 2 中,插補分數在 t = 10 毫秒時為 .15,因此該時間點的屬性值為 .15 × (40 - 0),也就是 6。
屬性動畫與檢視區塊動畫的差異
檢視區塊動畫系統只能為 View 物件製作動畫,因此如要為非 View 物件製作動畫,就必須自行實作程式碼。此外,檢視區塊動畫系統也有限制,只能公開 View 物件的幾個方面來製作動畫,例如檢視區塊的縮放和旋轉,但不能是背景顏色。
檢視區塊動畫系統的另一個缺點是,它只會修改 View 的繪製位置,而非實際的 View 本身。舉例來說,如果您將按鈕動畫設為在畫面上移動,按鈕會正確繪製,但可點選按鈕的實際位置不會變更,因此您必須自行實作邏輯來處理這個問題。
有了屬性動畫系統,這些限制完全移除,您可以為任何物件 (檢視區塊和非檢視區塊) 的任何屬性製作動畫,而且物件本身會實際修改。屬性動畫系統在執行動畫時也更穩固。從高層次來看,您可以將動畫師指派給要製作動畫的屬性,例如顏色、位置或大小,並定義動畫的各個層面,例如插補和多個動畫師的同步處理。
不過,設定檢視區塊動畫系統所需的時間較少,且編寫的程式碼也較少。 如果檢視區塊動畫能完成所有必要工作,或現有程式碼已如您所願運作,則不需要使用屬性動畫系統。如果出現適用的情況,針對不同情境使用這兩種動畫系統可能也是合理的做法。
API 總覽
您可以在 android.animation 中找到大部分的屬性動畫系統 API。由於檢視區塊動畫系統已在 android.view.animation 中定義許多內插器,因此您也可以在屬性動畫系統中使用這些內插器。下表說明屬性動畫系統的主要元件。
Animator 類別提供建立動畫的基本結構。您通常不會直接使用這個類別,因為它只提供最基本的功能,必須擴充才能完整支援動畫值。下列子類別會擴充 Animator:
表 1. 動畫師
| 類別 | 說明 |
|---|---|
ValueAnimator |
屬性動畫的主要時間引擎,也會計算要製作動畫的屬性值。這個類別具備所有核心功能,可計算動畫值,並包含每個動畫的時序詳細資料、動畫是否重複播放的相關資訊、接收更新事件的監聽器,以及設定要評估的自訂型別。動畫屬性有兩個部分:計算動畫值,以及在要製作動畫的物件和屬性上設定這些值。ValueAnimator 不會執行第二個部分,因此您必須監聽 ValueAnimator 計算的值更新,並使用自己的邏輯修改要製作動畫的物件。詳情請參閱「使用 ValueAnimator 製作動畫」一節。 |
ObjectAnimator |
ValueAnimator 的子類別,可讓您設定要加入動畫的目標物件和物件屬性。這個類別會在計算動畫的新值時,相應地更新屬性。您大部分時間都會想使用 ObjectAnimator,因為這樣可大幅簡化目標物件上值的動畫製作程序。不過,有時您會想直接使用 ValueAnimator,因為 ObjectAnimator 有一些限制,例如目標物件必須有特定的存取子方法。 |
AnimatorSet |
提供動畫分組機制,讓動畫彼此相關地執行。您可以設定動畫同時播放、依序播放,或在指定延遲時間後播放。詳情請參閱「使用 Animator 集合編排多個動畫」一節。 |
評估工具會告知屬性動畫系統如何計算特定屬性的值。這些函式會採用 Animator 類別提供的時間資料、動畫的開始和結束值,並根據這些資料計算屬性的動畫值。屬性動畫系統提供下列求值器:
表 2. 評估工具
| 類別/介面 | 說明 |
|---|---|
IntEvaluator |
計算 int 屬性值的預設評估工具。 |
FloatEvaluator |
計算 float 屬性值的預設評估工具。 |
ArgbEvaluator |
計算以十六進位值表示的顏色屬性值的預設評估工具。 |
TypeEvaluator |
可建立自有評估人員的介面。如果動畫物件屬性「不是」int、float 或顏色,您必須實作 TypeEvaluator 介面,指定如何計算物件屬性的動畫值。您也可以為 int、float 和顏色值指定自訂 TypeEvaluator,以便以不同於預設行為的方式處理這些類型。如要進一步瞭解如何編寫自訂評估工具,請參閱「使用 TypeEvaluator」一節。 |
時間內插器會定義如何計算動畫中的特定值 (以時間函式表示)。舉例來說,您可以指定動畫在整個動畫中線性發生,也就是動畫在整個過程中均勻移動,也可以指定動畫使用非線性時間,例如在動畫開頭加速,在動畫結尾減速。表 3 說明 android.view.animation 中包含的插補器。如果提供的任何插補器都不符合需求,請實作 TimeInterpolator 介面並建立自己的插補器。如要進一步瞭解如何編寫自訂插補器,請參閱「使用插補器」一文。
表 3. 內插器
| 類別/介面 | 說明 |
|---|---|
AccelerateDecelerateInterpolator |
插補器,變化速率開頭和結尾時較慢,中間加快。 |
AccelerateInterpolator |
插補器,變化速率一開始很慢,然後逐漸加快。 |
AnticipateInterpolator |
插補器:變更先從反向開始,然後向前快速滑過。 |
AnticipateOvershootInterpolator |
插補器:變化會先從反向開始,然後向前快速滑過並超過目標值,再於最終值停止。 |
BounceInterpolator |
變化會在結尾時彈跳的內插器。 |
CycleInterpolator |
動畫會以指定循環次數重複播放的內插器。 |
DecelerateInterpolator |
插補器,變化速率一開始很快,然後減慢。 |
LinearInterpolator |
變化速率維持一致的內插器。 |
OvershootInterpolator |
此插補器會先快速正向變化,接著超過最終值,然後再回到最終值。 |
TimeInterpolator |
可讓您實作自己的插補器。 |
使用 ValueAnimator 製作動畫
ValueAnimator 類別可讓您指定一組 int、float 或顏色值,在動畫期間為某種型別的值製作動畫。您可以呼叫其中一個工廠方法 (ofInt()、ofFloat() 或 ofObject()) 取得 ValueAnimator。例如:
Kotlin
ValueAnimator.ofFloat(0f, 100f).apply { duration = 1000 start() }
Java
ValueAnimator animation = ValueAnimator.ofFloat(0f, 100f); animation.setDuration(1000); animation.start();
在這段程式碼中,start() 方法執行時,ValueAnimator 會開始計算動畫的值 (介於 0 到 100 之間),時間長度為 1000 毫秒。
您也可以透過下列方式指定要製作動畫的自訂型別:
Kotlin
ValueAnimator.ofObject(MyTypeEvaluator(), startPropertyValue, endPropertyValue).apply { duration = 1000 start() }
Java
ValueAnimator animation = ValueAnimator.ofObject(new MyTypeEvaluator(), startPropertyValue, endPropertyValue); animation.setDuration(1000); animation.start();
在這段程式碼中,當 start() 方法執行時,ValueAnimator 會開始計算動畫的值,介於 startPropertyValue 和 endPropertyValue 之間,並使用 MyTypeEvaluator 提供的邏輯,持續 1000 毫秒。
如要使用動畫的值,請將 AnimatorUpdateListener 新增至 ValueAnimator 物件,如下列程式碼所示:
Kotlin
ValueAnimator.ofObject(...).apply { ... addUpdateListener { 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. textView.translationX = updatedAnimation.animatedValue as Float } ... }
Java
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() 方法中,您可以存取更新後的動畫值,並在其中一個檢視區塊的屬性中使用該值。如要進一步瞭解監聽器,請參閱「動畫監聽器」一節。
使用 ObjectAnimator 製作動畫
ObjectAnimator 是 ValueAnimator 的子類別 (上一節討論過),結合了 ValueAnimator 的時間引擎和值運算,以及為目標物件的具名屬性製作動畫的功能。這樣一來,您就不必再實作 ValueAnimator.AnimatorUpdateListener,因為動畫屬性會自動更新,讓任何物件的動畫效果都更容易製作。
ObjectAnimator 的例項化方式與 ValueAnimator 類似,但您也必須指定物件和該物件屬性的名稱 (以字串形式),以及要加入動畫效果的值:
Kotlin
ObjectAnimator.ofFloat(textView, "translationX", 100f).apply { duration = 1000 start() }
Java
ObjectAnimator animation = ObjectAnimator.ofFloat(textView, "translationX", 100f); animation.setDuration(1000); animation.start();
如要正確更新 ObjectAnimator 屬性,請務必執行下列操作:
- 您要製作動畫的物件屬性必須有 setter 函式 (採用駝峰式大小寫),格式為
set<PropertyName>()。由於ObjectAnimator會在動畫期間自動更新屬性,因此必須能夠透過這個設定器方法存取屬性。舉例來說,如果屬性名稱為foo,您需要有setFoo()方法。如果沒有這個設定器方法,您有三種做法:- 如果您有權這麼做,請將 setter 方法新增至類別。
- 使用您有權變更的包裝函式類別,並讓該包裝函式透過有效的 setter 方法接收值,然後將值轉送至原始物件。
- 請改用
ValueAnimator。
- 如果您在其中一個
ObjectAnimator工廠方法中,只為values...參數指定一個值,系統會將該值視為動畫的結束值。因此,您要製作動畫的物件屬性必須有 getter 函式,用於取得動畫的起始值。getter 函式必須採用get<PropertyName>()形式。舉例來說,如果屬性名稱為foo,您必須有getFoo()方法。 - 您要建立動畫效果的屬性,其 getter (如有需要) 和 setter 方法必須與您指定給
ObjectAnimator的起始值和結束值屬於相同類型。舉例來說,如果您建構下列ObjectAnimator,就必須有targetObject.setPropName(float)和targetObject.getPropName():ObjectAnimator.ofFloat(targetObject, "propName", 1f)
- 視您要製作動畫的屬性或物件而定,您可能需要在 View 上呼叫
invalidate()方法,強制螢幕使用更新的動畫值重新繪製自身。請在onAnimationUpdate()回呼中執行這項操作。舉例來說,如果為 Drawable 物件的顏色屬性製作動畫,只有在該物件重新繪製自身時,畫面才會更新。View 上的所有屬性設定程式 (例如setAlpha()和setTranslationX()) 都會正確地將 View 設為無效,因此使用新值呼叫這些方法時,您不需要將 View 設為無效。如要進一步瞭解監聽器,請參閱「動畫監聽器」一節。
使用 AnimatorSet 編排多個動畫
在許多情況下,您會想根據其他動畫的開始或結束時間播放動畫。Android 系統可讓您將動畫組合到 AnimatorSet 中,以便指定動畫的啟動方式 (同時、依序或在指定延遲時間後)。您也可以在彼此之間巢狀化 AnimatorSet 物件。
下列程式碼片段會以以下方式播放 Animator 物件:
- 播放
bounceAnim。 - 同時播放
squashAnim1、squashAnim2、stretchAnim1和stretchAnim2。 bounceBackAnim後播放。- 播放
fadeAnim。
Kotlin
val bouncer = AnimatorSet().apply { play(bounceAnim).before(squashAnim1) play(squashAnim1).with(squashAnim2) play(squashAnim1).with(stretchAnim1) play(squashAnim1).with(stretchAnim2) play(bounceBackAnim).after(stretchAnim2) } val fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f).apply { duration = 250 } AnimatorSet().apply { play(bouncer).before(fadeAnim) start() }
Java
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.AnimatorListeneronAnimationStart()- 動畫開始時呼叫。onAnimationEnd()- 動畫結束時呼叫。onAnimationRepeat()- 動畫重複播放時呼叫。onAnimationCancel()- 動畫取消時呼叫。無論動畫如何結束,取消動畫也會呼叫onAnimationEnd()。
ValueAnimator.AnimatorUpdateListener-
onAnimationUpdate()- 在動畫的每個影格中呼叫。監聽這個事件,即可在動畫期間使用ValueAnimator產生的計算值。如要使用該值,請查詢傳遞至事件的ValueAnimator物件,然後使用getAnimatedValue()方法取得目前的動畫值。如果您使用ValueAnimator,就必須實作這個事件監聽器。視要製作動畫的屬性或物件而定,您可能需要在 View 上呼叫
invalidate(),強制螢幕的該區域使用新的動畫值重新繪製自身。舉例來說,如果為 Drawable 物件的顏色屬性設定動畫,只有在該物件重新繪製自身時,畫面才會更新。View 上的所有屬性設定程式 (例如setAlpha()和setTranslationX()) 都會正確地將 View 設為無效,因此使用新值呼叫這些方法時,您不需要將 View 設為無效。
-
如果您不想實作 Animator.AnimatorListener 介面的所有方法,可以擴充 AnimatorListenerAdapter 類別,而不是實作 Animator.AnimatorListener 介面。AnimatorListenerAdapter 類別提供方法的空白實作,您可以選擇覆寫這些方法。
舉例來說,下列程式碼片段會建立 AnimatorListenerAdapter,僅用於 onAnimationEnd() 回呼:
Kotlin
ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f).apply { duration = 250 addListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { balls.remove((animation as ObjectAnimator).target) } }) }
Java
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()); }
以動畫呈現 ViewGroup 物件的版面配置變更
屬性動畫系統可讓您為 ViewGroup 物件的變更製作動畫,也能輕鬆為 View 物件製作動畫。
您可以使用 LayoutTransition 類別,在 ViewGroup 中為版面配置變更製作動畫。將 View 新增至 ViewGroup 或從中移除,或使用 VISIBLE、INVISIBLE 或 GONE 呼叫 View 的 setVisibility() 方法時,ViewGroup 內的 View 可以經歷顯示和消失動畫。當您新增或移除 Views 時,ViewGroup 中的其餘 Views 也會以動畫形式移至新位置。您可以呼叫 setAnimator() 並傳入 Animator 物件,在 LayoutTransition 物件中定義下列動畫,並使用下列 LayoutTransition 常數:
APPEARING- 這個旗標表示要在容器中顯示的項目上執行的動畫。CHANGE_APPEARING:旗標,指出因容器中出現新項目而變更的項目所執行的動畫。DISAPPEARING- 這個旗標表示要對從容器中消失的項目執行的動畫。CHANGE_DISAPPEARING- 這個旗標表示項目因從容器中消失而變更時,執行的動畫。
您可以為這四種事件定義自己的自訂動畫,藉此自訂版面配置轉場效果的外觀,或只是告知動畫系統使用預設動畫。
如要將 android:animateLayoutchanges 屬性設為 ViewGroup 的 true,請執行下列操作:
<LinearLayout android:orientation="vertical" android:layout_width="wrap_content" android:layout_height="match_parent" android:id="@+id/verticalContainer" android:animateLayoutChanges="true" />
將這個屬性設為 true 時,系統會自動為 ViewGroup 中新增或移除的 View,以及 ViewGroup 中其餘的 View 加上動畫效果。
使用 StateListAnimator 以動畫呈現檢視畫面狀態變更
StateListAnimator 類別可讓您定義在檢視區塊狀態變更時執行的 Animator。這個物件的行為類似於 Animator 物件的包裝函式,每當指定的檢視畫面狀態 (例如「已按下」或「已聚焦」) 變更時,就會呼叫該動畫。
您可以在 XML 資源中定義 StateListAnimator,方法是使用根 <selector> 元素和子項 <item> 元素,每個子項元素都會指定 StateListAnimator 類別定義的不同檢視區塊狀態。每個 <item> 都包含屬性動畫集的定義。
舉例來說,下列檔案會建立狀態清單動畫師,在檢視區塊遭到按壓時變更其 X 和 Y 比例:
res/xml/animate_scale.xml
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <!-- the pressed state; increase x and y size to 150% --> <item android:state_pressed="true"> <set> <objectAnimator android:propertyName="scaleX" android:duration="@android:integer/config_shortAnimTime" android:valueTo="1.5" android:valueType="floatType"/> <objectAnimator android:propertyName="scaleY" android:duration="@android:integer/config_shortAnimTime" android:valueTo="1.5" android:valueType="floatType"/> </set> </item> <!-- the default, non-pressed state; set x and y size to 100% --> <item android:state_pressed="false"> <set> <objectAnimator android:propertyName="scaleX" android:duration="@android:integer/config_shortAnimTime" android:valueTo="1" android:valueType="floatType"/> <objectAnimator android:propertyName="scaleY" android:duration="@android:integer/config_shortAnimTime" android:valueTo="1" android:valueType="floatType"/> </set> </item> </selector>
如要將狀態清單動畫師附加至檢視區塊,請新增
android:stateListAnimator 屬性,如下所示:
<Button android:stateListAnimator="@xml/animate_scale" ... />
現在,當這個按鈕的狀態變更時,系統會使用 animate_scale.xml 中定義的動畫。
如要在程式碼中將狀態清單動畫師指派給檢視區塊,請使用 AnimatorInflater.loadStateListAnimator() 方法,並透過 View.setStateListAnimator() 方法將動畫師指派給檢視區塊。
或者,您也可以使用 AnimatedStateListDrawable,在狀態變更之間播放可繪項目動畫,而不必為檢視區塊的屬性製作動畫。Android 5.0 中的部分系統小工具預設會使用這些動畫。以下範例說明如何將 AnimatedStateListDrawable 定義為 XML 資源:
<!-- res/drawable/myanimstatedrawable.xml --> <animated-selector xmlns:android="http://schemas.android.com/apk/res/android"> <!-- provide a different drawable for each state--> <item android:id="@+id/pressed" android:drawable="@drawable/drawableP" android:state_pressed="true"/> <item android:id="@+id/focused" android:drawable="@drawable/drawableF" android:state_focused="true"/> <item android:id="@id/default" android:drawable="@drawable/drawableD"/> <!-- specify a transition --> <transition android:fromId="@+id/default" android:toId="@+id/pressed"> <animation-list> <item android:duration="15" android:drawable="@drawable/dt1"/> <item android:duration="15" android:drawable="@drawable/dt2"/> ... </animation-list> </transition> ... </animated-selector>
使用 TypeEvaluator
如要為 Android 系統未知的型別製作動畫,可以實作 TypeEvaluator 介面來建立自己的求值器。Android 系統可辨識的型別為 int、float 或顏色,這些型別由 IntEvaluator、FloatEvaluator 和 ArgbEvaluator 型別評估工具支援。
TypeEvaluator 介面中只有一個方法可供實作,也就是 evaluate() 方法。這樣一來,您使用的動畫師就能在動畫的目前時間點,為動畫屬性傳回適當的值。FloatEvaluator 類別會示範如何執行這項操作:
Kotlin
private class FloatEvaluator : TypeEvaluator<Any> { override fun evaluate(fraction: Float, startValue: Any, endValue: Any): Any { return (startValue as Number).toFloat().let { startFloat -> startFloat + fraction * ((endValue as Number).toFloat() - startFloat) } } }
Java
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 透過 fraction 參數接收的值,因此計算動畫值時,您不必將內插器納入考量。
使用內插器
內插器會定義如何根據時間計算動畫中的特定值。舉例來說,您可以指定動畫在整個動畫中線性發生,也就是動畫在整個過程中均勻移動,也可以指定動畫使用非線性時間,例如在動畫開頭或結尾使用加速或減速。
動畫系統中的內插器會從 Animator 接收分數,代表動畫的經過時間。插補器會修改這個分數,使其與要提供的動畫類型一致。Android 系統會在 android.view.animation package 中提供一組常見的內插器。如果上述選項都不符合需求,您可以實作 TimeInterpolator 介面,自行建立。
舉例來說,以下比較預設插補器 AccelerateDecelerateInterpolator 和 LinearInterpolator 計算插補分數的方式。
LinearInterpolator 不會影響經過的分數。AccelerateDecelerateInterpolator 會加速進入動畫,並減速離開動畫。下列方法會定義這些插補器的邏輯:
AccelerateDecelerateInterpolator
Kotlin
override fun getInterpolation(input: Float): Float = (Math.cos((input + 1) * Math.PI) / 2.0f).toFloat() + 0.5f
Java
@Override public float getInterpolation(float input) { return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f; }
LinearInterpolator
Kotlin
override fun getInterpolation(input: Float): Float = input
Java
@Override public float getInterpolation(float input) { return input; }
下表代表這些插補器為 1000 毫秒的動畫計算出的近似值:
| 經過時間 (毫秒) | 經過的分數/插值分數 (線性) | 內插分數 (加速/減速) |
|---|---|---|
| 0 | 0 | 0 |
| 200 | .2 | .1 |
| 400 | .4 | .345 |
| 600 | .6 | .654 |
| 800 | .8 | .9 |
| 1000 | 1 | 1 |
如表格所示,LinearInterpolator 會以相同速度變更值,每經過 200 毫秒就會變更 0.2。在 200 毫秒到 600 毫秒之間,AccelerateDecelerateInterpolator 的值變化速度比 LinearInterpolator 快,在 600 毫秒到 1000 毫秒之間,AccelerateDecelerateInterpolator 的值變化速度比 LinearInterpolator 慢。
指定主要畫面格
Keyframe 物件由時間/值組組成,可讓您在動畫的特定時間定義特定狀態。每個主要畫面格也可以有自己的插補器,用來控制動畫在先前主要畫面格時間和這個主要畫面格時間之間間隔的行為。
如要例項化 Keyframe 物件,您必須使用其中一個工廠方法 (ofInt()、ofFloat() 或 ofObject()) 取得適當的 Keyframe 類型。然後呼叫 ofKeyframe() 工廠方法,取得 PropertyValuesHolder 物件。取得物件後,您可以傳遞 PropertyValuesHolder 物件和要加入動畫效果的物件,藉此取得動畫工具。以下程式碼片段示範如何執行這項操作:
Kotlin
val kf0 = Keyframe.ofFloat(0f, 0f) val kf1 = Keyframe.ofFloat(.5f, 360f) val kf2 = Keyframe.ofFloat(1f, 0f) val pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2) ObjectAnimator.ofPropertyValuesHolder(target, pvhRotation).apply { duration = 5000 }
Java
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(5000);
以動畫呈現檢視畫面
屬性動畫系統可簡化 View 物件的動畫,並提供比檢視動畫系統更優異的幾項優勢。檢視區塊動畫系統會改變 View 物件的繪製方式,進而轉換這些物件。這是因為 View 本身沒有可供操控的屬性,因此在每個 View 的容器中處理。 這會導致系統為 View 加上動畫效果,但 View 物件本身不會有任何變化。這導致物件即使繪製在螢幕上的不同位置,仍會存在於原始位置等行為。Android 3.0 新增了屬性,以及對應的 getter 和 setter 方法,可消除這項缺點。
屬性動畫系統可以變更檢視區塊物件中的實際屬性,在畫面上製作檢視區塊動畫。此外,每當屬性變更時,View 也會自動呼叫 invalidate() 方法來重新整理畫面。View 類別中可簡化屬性動畫的新屬性如下:
translationX和translationY:這些屬性會控制 View 的位置,以其版面配置容器設定的左側和頂端座標做為增量。rotation、rotationX和rotationY:這些屬性可控制 2D (rotation屬性) 和 3D 中,繞著樞紐點的旋轉。scaleX和scaleY:這些屬性可控制 View 繞著樞紐點的 2D 縮放比例。pivotX和pivotY:這些屬性可控制支點的位置,旋轉和縮放轉換會以支點為中心進行。根據預設,樞紐點位於物件中心。x和y:這些是簡單的公用程式屬性,用於說明 View 在容器中的最終位置,也就是左值和頂端值,以及 translationX 和 translationY 值的總和。alpha:代表檢視區塊的 Alpha 透明度。這個值預設為 1 (不透明),值為 0 則表示完全透明 (不可見)。
如要為 View 物件的屬性 (例如顏色或旋轉值) 製作動畫,只要建立屬性動畫師,並指定要製作動畫的 View 屬性即可。例如:
Kotlin
ObjectAnimator.ofFloat(myView, "rotation", 0f, 360f)
Java
ObjectAnimator.ofFloat(myView, "rotation", 0f, 360f);
如要進一步瞭解如何建立動畫師,請參閱「使用 ValueAnimator 和 ObjectAnimator 製作動畫」一節。
使用 ViewPropertyAnimator 製作動畫
ViewPropertyAnimator 提供簡單的方式,可使用單一基礎 Animator 物件,平行為 View 的多個屬性製作動畫。這項工具的運作方式與 ObjectAnimator 類似,因為它會修改檢視畫面屬性的實際值,但同時為多個屬性加上動畫效果時,效率會更高。此外,使用 ViewPropertyAnimator 的程式碼也更簡潔易讀。下列程式碼片段顯示使用多個 ObjectAnimator 物件、單一 ObjectAnimator 和 ViewPropertyAnimator 時的差異,同時為檢視區塊的 x 和 y 屬性設定動畫。
多個 ObjectAnimator 物件
Kotlin
val animX = ObjectAnimator.ofFloat(myView, "x", 50f) val animY = ObjectAnimator.ofFloat(myView, "y", 100f) AnimatorSet().apply { playTogether(animX, animY) start() }
Java
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
Kotlin
val pvhX = PropertyValuesHolder.ofFloat("x", 50f) val pvhY = PropertyValuesHolder.ofFloat("y", 100f) ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvhY).start()
Java
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f); PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f); ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvhY).start();
ViewPropertyAnimator
Kotlin
myView.animate().x(50f).y(100f)
Java
myView.animate().x(50f).y(100f);
如要進一步瞭解 ViewPropertyAnimator,請參閱相應的 Android 開發人員網誌文章。
在 XML 中宣告動畫
屬性動畫系統可讓您使用 XML 宣告屬性動畫,不必以程式輔助。在 XML 中定義動畫後,您就能輕鬆地在多個活動中重複使用動畫,並更輕鬆地編輯動畫序列。
為區分使用新屬性動畫 API 的動畫檔案,以及使用舊版檢視區塊動畫架構的動畫檔案,從 Android 3.1 開始,您應將屬性動畫的 XML 檔案儲存在 res/animator/ 目錄中。
下列屬性動畫類別支援 XML 宣告,並使用下列 XML 標記:
ValueAnimator-<animator>ObjectAnimator-<objectAnimator>AnimatorSet-<set>
如要瞭解可在 XML 宣告中使用的屬性,請參閱「動畫資源」。以下範例會依序播放兩組物件動畫,其中第一組巢狀物件會同時播放兩個物件動畫:
<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 所有子項的目標物件。以下程式碼說明如何執行這項操作:
Kotlin
(AnimatorInflater.loadAnimator(myContext, R.animator.property_animator) as AnimatorSet).apply { setTarget(myObject) start() }
Java
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext, R.animator.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、取得更新的動畫值,並在其中一個檢視區塊的屬性中使用該值,如下列程式碼所示:
Kotlin
(AnimatorInflater.loadAnimator(this, R.animator.animator) as ValueAnimator).apply { addUpdateListener { updatedAnimation -> textView.translationX = updatedAnimation.animatedValue as Float } start() }
Java
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();
如要瞭解定義屬性動畫的 XML 語法,請參閱「動畫資源 」。
對 UI 效能的潛在影響
更新 UI 的動畫師會導致動畫執行的每個影格都產生額外的算繪工作。因此,使用耗用大量資源的動畫可能會對應用程式效能造成負面影響。
動畫 UI 所需的工作會新增至轉譯管道的動畫階段。如要瞭解動畫是否會影響應用程式效能,請啟用「剖析 GPU 轉譯」並監控動畫階段。詳情請參閱「 剖析 GPU 轉譯逐步說明」。