屬性動畫總覽

嘗試 Compose 方法
Jetpack Compose 是 Android 推薦的 UI 工具包。瞭解如何在 Compose 中使用動畫。

屬性動畫系統是一項強大的架構,可讓您為幾乎任何項目建立動畫。您可以定義動畫,在一段時間內變更任何物件屬性,無論屬性是否繪製在畫面上。屬性動畫會在指定的時間範圍內變更屬性 (物件中的欄位) 值。如要為某件物件建立動畫效果,請指定要產生動畫效果的物件屬性,例如物件在畫面上的位置、添加動畫效果的時間長度,以及要設定動畫效果的值。

屬性動畫系統可讓您定義動畫的下列特性:

  • 時間長度:您可以指定動畫的時間長度。預設長度為 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 中,使用的 TimeInterpolatorAccelerateDecelerateInterpolator,而 TypeEvaluator 則為 IntEvaluator

如要開始播放動畫,請建立 ValueAnimator,並為您要建立動畫效果的屬性提供起始和結束值,以及動畫的時間長度。當您呼叫 start() 時,動畫就會開始。在完整動畫播放期間,ValueAnimator 會根據動畫時間長度和經過的時間長度,計算介於 0 到 1 之間的經過比例。經過時間比例代表動畫播放完畢的時間百分比,0 代表 0%,1 代表 100%。例如,在圖 1 中,由於總時間長度為 t = 40 毫秒,因此在 t = 10 毫秒的情況下經過的分數會是 0.25。

ValueAnimator 完成計算經過計算的分數後,就會呼叫目前設定的 TimeInterpolator,計算內插分數。內插分數會將經過時間比例對應至新的分數,該分數會考量已設定的時間內插。舉例來說,在圖 2 中,由於動畫速度會慢慢加速,內插分數約 0.15,小於經過的分數 (0.25,於 t = 10 毫秒)。在圖 1 中,內插分數永遠與經過計算的分數相同。

計算內插分數時,ValueAnimator 會呼叫適當的 TypeEvaluator,根據動畫的內插分數、起始值和結束值計算要製作動畫的屬性值。舉例來說,在圖 2 中,內插分數是 0.15,方為 t = 10 毫秒,因此此時屬性的值會是 .15 × (40 - 0) 或 6。

屬性動畫與檢視動畫的差異

檢視畫面動畫系統僅提供為 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 Sets 挑選多個動畫」一節。

評估工具會指示屬性動畫系統如何計算指定屬性的值。這些物件會擷取 Animator 類別提供的時間資料、動畫的開始和結束值,並根據這項資料計算屬性的動畫值。屬性動畫系統提供下列評估器:

表 2. 評估人員

類別/介面 說明
IntEvaluator 使用預設評估工具計算 int 屬性的值。
FloatEvaluator 使用預設評估工具計算 float 屬性的值。
ArgbEvaluator 預設評估工具會計算以十六進位值表示的顏色屬性值。
TypeEvaluator 可讓您建立自己的評估工具的介面。如果您要為「不是」intfloat 或顏色的物件屬性建立動畫效果,您必須實作 TypeEvaluator 介面,以指定如何計算物件屬性的動畫值。如果您想以與預設行為不同的方式處理這些類型,也可以為 intfloat 和顏色值指定自訂 TypeEvaluator。如要進一步瞭解如何編寫自訂評估工具,請參閱使用 TypeEvaluator 一節。

時間內插器可定義動畫中特定值如何依據時間函式計算。舉例來說,您可以指定在整個動畫中以線性方式發生動畫 (也就是動畫在整個時間中平均移動),或者指定動畫使用非線性時間,例如在動畫開始時加速及在結尾時減速。表 3 說明 android.view.animation 中包含的內插器。如果提供的內插器都不符合您的需求,請實作 TimeInterpolator 介面並自行建立。如要進一步瞭解如何編寫自訂內插器,請參閱使用內插器

表 3. 內插器

類別/介面 說明
AccelerateDecelerateInterpolator 內插器,變更速率開始和結束的速度緩慢,過程中會加速。
AccelerateInterpolator 內插器的變化速率一開始緩慢,然後加快。
AnticipateInterpolator 內插器,其變更從反向開始,然後向前快速滑過。
AnticipateOvershootInterpolator 內插器的變更會從反向開始,然後向前快速滑過並超過目標值,最後再回到最終值。
BounceInterpolator 會在結尾時彈跳的內插器。
CycleInterpolator 內插器,其動畫會以指定的循環次數重複播放。
DecelerateInterpolator 內插器的變化速率一開始很快,然後減慢。
LinearInterpolator 變化速率為固定的內插器。
OvershootInterpolator 內插器,其變更向前快速滑過並超過最終值,然後再傳回。
TimeInterpolator 可讓您實作自己的內插器的介面。

使用 ValueAnimator 建立動畫

ValueAnimator 類別可讓您指定一組 intfloat 或顏色值,藉此在動畫播放期間為某些類型的值加上動畫效果。您可以呼叫其中一種工廠方法取得 ValueAnimatorofInt()ofFloat()ofObject()。例如:

Kotlin

ValueAnimator.ofFloat(0f, 100f).apply {
    duration = 1000
    start()
}

Java

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

在這個程式碼中,當 start() 方法執行時,ValueAnimator 會開始計算持續 1000 毫秒的動畫值 (介於 0 到 100 之間)。

你也可以執行下列步驟,指定動畫適用的自訂類型:

Kotlin

ValueAnimator.ofObject(MyTypeEvaluator(), startPropertyValue, endPropertyValue).apply {
    duration = 1000
    start()
}

Java

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

在這個程式碼中,ValueAnimator 會在執行 start() 方法時,使用 MyTypeEvaluator 提供的邏輯計算 startPropertyValueendPropertyValue 之間的動畫值。

您可以在 ValueAnimator 物件中加入 AnimatorUpdateListener,以使用動畫的值,如以下程式碼所示:

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 建立動畫

ObjectAnimatorValueAnimator 的子類別 (如上一節所述),結合了 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 更新屬性正確,您必須執行下列操作:

  • 您要建立動畫的物件屬性必須包含 set<PropertyName>() 格式的 setter 函式 (採用駝峰式大小寫)。ObjectAnimator 會在動畫期間自動更新屬性,因此必須能夠使用此 setter 方法存取屬性。例如,如果屬性名稱為 foo,您需要使用 setFoo() 方法。如果 setter 方法不存在,有三種選項:
    • 如果您有權執行這項操作,請將 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 上的所有屬性 setter (例如 setAlpha()setTranslationX()) 會正確驗證 View,因此使用新值呼叫這些方法時,無須將 View 撤銷。如要進一步瞭解事件監聽器,請參閱「動畫事件監聽器」一節。

使用 AnimatorSet 選擇多個動畫

在許多情況下,您想要播放取決於另一個動畫開始或結束時間的動畫。Android 系統可讓您將動畫組合成 AnimatorSet,以便指定要在指定的延遲時間之後、依序或之後啟動動畫。您也可以在 AnimatorSet 物件的巢狀結構中建立巢狀結構。

下列程式碼片段會以下列方式播放下列 Animator 物件:

  1. 播放《bounceAnim》。
  2. 同時播放 squashAnim1squashAnim2stretchAnim1stretchAnim2
  3. 播放《bounceBackAnim》。
  4. 播放《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.AnimatorListener 介面的所有方法,可以擴充 AnimatorListenerAdapter 類別,而非實作 Animator.AnimatorListener 介面。AnimatorListenerAdapter 類別提供空白的實作方式,您可以選擇覆寫方法。

舉例來說,下列程式碼片段只會為 onAnimationEnd() 回呼建立 AnimatorListenerAdapter

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 中的版面配置變更。將 ViewGroup 中的檢視畫面新增至 ViewGroup 或從 ViewGroup 中移除時,或使用 VISIBLEINVISIBLEGONE 呼叫 View 的 setVisibility() 方法時,檢視畫面可能會出現顯示消失的動畫。當您新增或移除 View 時,ViewGroup 中的其餘檢視區塊也能以動畫形式顯示在新的位置。您可以藉由呼叫 setAnimator(),並使用下列其中一個 LayoutTransition 常數傳入 Animator 物件,在 LayoutTransition 物件中定義下列動畫:

  • APPEARING:此標記代表在容器顯示的項目上執行動畫。
  • CHANGE_APPEARING:此標記表示在容器因出現新項目而變更的項目上執行動畫。
  • DISAPPEARING - 這個標記,表示在從容器中消失的項目上執行的動畫。
  • CHANGE_DISAPPEARING:此標記代表在項目因項目從容器中消失而需要變更的項目上執行動畫。

您可以為這四種事件定義自訂動畫,藉此自訂版面配置轉換的外觀,或直接指示動畫系統使用預設動畫。

如要將 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 時,從 ViewGroup 新增或移除的 View,以及 ViewGroup 中其餘的檢視畫面,都會自動建立動畫效果。

使用 StateListAnimator 動畫檢視畫面狀態變更

StateListAnimator 類別可讓您定義在檢視畫面狀態變更時執行的動畫器。這個物件的行為就像是 Animator 物件的包裝函式,會在指定的檢視畫面狀態 (例如「已按下」或「聚焦」) 變更時呼叫該動畫。

在 XML 資源中,可以使用根 <selector> 元素和子項 <item> 元素定義 StateListAnimator,這些元素分別指定由 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 系統已知的類型包括 intfloat 或顏色,而 IntEvaluatorFloatEvaluatorArgbEvaluator 類型評估器支援的類型。

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 參數接收的內容,因此計算動畫值時無需考量內插器。

使用內插器

內插器可定義動畫中特定值如何依據時間函式計算。舉例來說,您可以指定在整個動畫中以線性方式發生動畫 (也就是動畫在整個時間中平均移動),或者指定使用非線性時間的動畫,例如在動畫開始或結束時使用加速或減速功能。

動畫系統中的內插器會從動畫器收到小數,代表動畫經過的時間。內插器會修改此分數,使其符合其要提供的動畫類型。Android 系統在 android.view.animation package 中提供一組常見的內插器。如果這些方法都不符合您的需求,您可以實作 TimeInterpolator 介面並自行建立。

舉例來說,預設內插器 AccelerateDecelerateInterpolatorLinearInterpolator 計算內插分數的方式如下所示。LinearInterpolator 不會影響經過的分數。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;
}

線性插插器

Kotlin

override fun getInterpolation(input: Float): Float = input

Java

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

下表列出這些內插器針對持續 1000 毫秒的動畫計算出的近似值:

歷時 ms 已過分數/插補比例 (線性) 內插比例 (加速/減速)
0 0 0
200 0.9 0.1
400 4 0.345
600 0.6 8.8
800 8.8 0.9
1000 1 1

如表格所示,LinearInterpolator 會以相同的速度變更值,每傳遞 200 毫秒為 0.2。AccelerateDecelerateInterpolator 變更值的速度超過 LinearInterpolator (介於 200 毫秒和 600 毫秒之間),並在 600 毫秒至 1000 毫秒之間慢慢變更。

指定主要畫面格

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 呈現動畫,但不會影響 View 物件本身。這會導致行為 (例如物件仍位於原始位置) 下,即使該物件是在螢幕上的其他位置繪製也是如此。在 Android 3.0 中,我們加入了新屬性與對應的 getter 和 setter 方法,以消除這種缺點。

屬性動畫系統可以透過變更 View 物件中的實際屬性,為畫面上的檢視畫面建立動畫。此外,View 也會自動呼叫 invalidate() 方法,在每次屬性變更時重新整理畫面。View 類別中新增有助於建立屬性動畫的新屬性如下:

  • translationXtranslationY:這些屬性控制檢視畫面所在位置的差距 (由其左方和頂端座標透過版面配置容器設定)。
  • rotationrotationXrotationY:這些屬性可控制樞紐點的 2D (rotation 屬性) 和 3D 旋轉情形。
  • scaleXscaleY:這些屬性可控制檢視畫面聚焦點周圍的 2D 縮放比例。
  • pivotXpivotY:這些屬性可控制樞紐點的位置,也就是旋轉和縮放轉換周圍的位置。根據預設,樞紐點位於物件中心。
  • xy:這些是簡單的公用程式屬性,用於描述檢視區塊在其容器中的最終位置,包括左值和頂端值以及 translationX 和 translationY 值的總和。
  • alpha:代表檢視畫面上的 Alpha 透明度。這個值預設為 1 (不透明),預設值為 0 代表完全透明 (不會顯示)。

如要為 View 物件的屬性 (例如顏色或旋轉值) 建立動畫效果,只需建立屬性動畫工具,並指定您要建立動畫的 View 屬性即可。例如:

Kotlin

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

Java

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

如要進一步瞭解如何建立動畫,請參閱 ValueAnimatorObjectAnimator 建立動畫的章節。

使用 ViewPropertyAnimator 建立動畫

透過 ViewPropertyAnimator,您可以使用單一基礎 Animator 物件,輕鬆為 View 的多個屬性同時建立動畫效果。運作方式與 ObjectAnimator 類似,因為會修改檢視區塊屬性的實際值,但一次為多個屬性建立動畫效果會更有效率。此外,使用 ViewPropertyAnimator 的程式碼更簡潔也更容易閱讀。下列程式碼片段顯示同時使用多個 ObjectAnimator 物件、單一 ObjectAnimator,以及 ViewPropertyAnimator 為檢視畫面的 xy 屬性建立動畫效果的差異。

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

單一物件動畫工具

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 宣告:

如要尋找可在 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 轉譯逐步操作說明」。