使用轉場功能以動畫呈現版面配置變更

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

Android 的轉換架構可讓您提供開始和結束版面配置,為 UI 中的各種動作製作動畫。您可以選取所需的動畫類型 (例如淡入/淡出檢視區塊,或變更檢視區塊大小),轉場效果架構會決定如何從起始版面配置動畫化至結束版面配置。

轉移架構包含下列功能:

  • 群組層級動畫: 將動畫效果套用至檢視區塊階層中的所有檢視區塊。
  • 內建動畫: 使用預先定義的動畫,製作淡出或移動等常見效果。
  • 支援資源檔案: 從版面配置資源檔案載入檢視區塊階層和內建動畫。
  • 生命週期回呼: 接收回呼,以便控制動畫和階層的變更程序。

如需在版面配置變更之間加入動畫效果的程式碼範例,請參閱「BasicTransition」。

在兩個版面配置之間建立動畫的基本流程如下:

  1. 為開始和結束版面配置建立 Scene 物件。不過,起始版面配置的場景通常會根據目前的版面配置自動決定。
  2. 建立 Transition 物件,定義所需的動畫類型。
  3. 呼叫 TransitionManager.go(),系統就會執行動畫來交換版面配置。

圖 1 中的圖表說明版面配置、場景、轉場效果和最終動畫之間的關係。

圖 1. 基本插圖:說明轉換架構如何建立動畫。

建立場景

場景會儲存檢視區塊階層的狀態,包括所有檢視區塊及其屬性值。轉換架構可在開始和結束場景之間執行動畫。

您可以從版面配置資源檔案或程式碼中的檢視區塊群組建立場景。不過,轉換的起始場景通常會根據目前的 UI 自動決定。

場景也可以定義自己的動作,在您變更場景時執行。 這項功能有助於在轉換至場景後清除檢視畫面設定。

從版面配置資源建立場景

您可以直接從版面配置資源檔案建立 Scene 例項。如果檔案中的檢視區塊階層大多是靜態,請使用這項技巧。 產生的場景代表您建立 Scene 執行個體時的檢視區塊階層狀態。如果變更檢視區塊階層,請重新建立場景。架構會根據檔案中的整個檢視區塊階層建立場景。您無法從版面配置檔案的一部分建立場景。

如要從版面配置資源檔案建立 Scene 執行個體,請從版面配置中擷取場景根目錄,做為 ViewGroup。然後,使用場景根目錄和版面配置檔案的資源 ID 呼叫 Scene.getSceneForLayout() 函式,該檔案包含場景的檢視區塊階層。

定義場景的版面配置

本節其餘部分的程式碼片段會說明如何使用相同的場景根元素建立兩個不同的場景。程式碼片段也說明您可以載入多個不相關的 Scene 物件,不必暗示這些物件彼此相關。

這個範例包含下列版面配置定義:

  • 活動的主要版面配置,包含文字標籤和子項 FrameLayout
  • 第一個場景的 ConstraintLayout,包含兩個文字欄位。
  • 第二個場景的 ConstraintLayout,其中兩個文字欄位的順序不同。

這個範例的設計是讓所有動畫都發生在活動主要版面配置的子項版面配置中。主要版面配置中的文字標籤會保持靜態。

活動的主要版面配置定義如下:

res/layout/activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/master_layout">
    <TextView
        android:id="@+id/title"
        ...
        android:text="Title"/>
    <FrameLayout
        android:id="@+id/scene_root">
        <include layout="@layout/a_scene" />
    </FrameLayout>
</LinearLayout>

這個版面配置定義包含文字欄位和場景根層級的子項 FrameLayout。第一個場景的版面配置包含在主要版面配置檔案中。 這樣一來,應用程式就能將其顯示為初始使用者介面的一部分,並載入至場景中,因為架構只能將整個版面配置檔案載入至場景。

第一個場景的版面配置定義如下:

res/layout/a_scene.xml

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/scene_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <TextView
        android:id="@+id/text_view1"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:text="Text Line 1"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>
    <TextView
        android:id="@+id/text_view2"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:text="Text Line 2"
        app:layout_constraintTop_toBottomOf="@id/text_view1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

第二個場景的版面配置包含相同的兩個文字欄位 (具有相同的 ID),但順序不同。定義如下:

res/layout/another_scene.xml

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/scene_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <TextView
        android:id="@+id/text_view2"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:text="Text Line 2"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />
    <TextView
        android:id="@+id/text_view1"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:text="Text Line 1"
        app:layout_constraintTop_toBottomOf="@id/text_view2"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

根據版面配置生成場景

為兩個限制條件版面配置建立定義後,您就能取得每個版面配置的場景。這樣您就能在兩種 UI 設定之間轉換。如要取得場景,您需要參照場景根目錄和版面配置資源 ID。

下列程式碼片段顯示如何取得場景根目錄的參照,並從版面配置檔案建立兩個 Scene 物件:

Kotlin

val sceneRoot: ViewGroup = findViewById(R.id.scene_root)
val aScene: Scene = Scene.getSceneForLayout(sceneRoot, R.layout.a_scene, this)
val anotherScene: Scene = Scene.getSceneForLayout(sceneRoot, R.layout.another_scene, this)

Java

Scene aScene;
Scene anotherScene;

// Create the scene root for the scenes in this app.
sceneRoot = (ViewGroup) findViewById(R.id.scene_root);

// Create the scenes.
aScene = Scene.getSceneForLayout(sceneRoot, R.layout.a_scene, this);
anotherScene =
    Scene.getSceneForLayout(sceneRoot, R.layout.another_scene, this);

在應用程式中,現在有兩個以檢視區塊階層為基礎的 Scene 物件。這兩個場景都使用 res/layout/activity_main.xmlFrameLayout 元素定義的場景根目錄。

在程式碼中建立場景

您也可以在程式碼中從 ViewGroup 物件建立 Scene 執行個體。如果您直接在程式碼中修改檢視區塊階層,或動態產生檢視區塊階層,請使用這項技術。

如要從程式碼中的檢視區塊階層建立場景,請使用 Scene(sceneRoot, viewHierarchy) 建構函式。呼叫這個建構函式,就等於在已擴充版面配置檔案時呼叫 Scene.getSceneForLayout() 函式。

下列程式碼片段說明如何從場景根元素和程式碼中場景的檢視區塊階層,建立 Scene 執行個體:

Kotlin

val sceneRoot = someLayoutElement as ViewGroup
val viewHierarchy = someOtherLayoutElement as ViewGroup
val scene: Scene = Scene(sceneRoot, viewHierarchy)

Java

Scene mScene;

// Obtain the scene root element.
sceneRoot = (ViewGroup) someLayoutElement;

// Obtain the view hierarchy to add as a child of
// the scene root when this scene is entered.
viewHierarchy = (ViewGroup) someOtherLayoutElement;

// Create a scene.
mScene = new Scene(sceneRoot, mViewHierarchy);

建立情境動作

這個架構可讓您定義自訂場景動作,系統會在進入或退出場景時執行這些動作。在許多情況下,您不需要定義自訂場景動作,因為架構會自動為場景之間的變化製作動畫。

場景動作適用於處理下列情況:

  • 如要為不在相同階層中的檢視區塊製作動畫,您可以使用退場和進場場景動作,為開始和結束場景的檢視區塊製作動畫。
  • 如要為轉場效果架構無法自動建立動畫的檢視區塊 (例如 ListView 物件) 建立動畫,詳情請參閱「限制」一節。

如要提供自訂場景動作,請將動作定義為 Runnable 物件,並傳遞至 Scene.setExitAction()Scene.setEnterAction() 函式。架構會在執行轉場動畫前,對起始場景呼叫 setExitAction() 函式,並在執行轉場動畫後,對結束場景呼叫 setEnterAction() 函式。

套用轉場效果

轉場架構會以 Transition 物件表示場景間的動畫樣式。您可以使用內建子類別 (例如 AutoTransitionFade) 例項化 Transition,也可以自行定義轉場效果。接著,將結尾 SceneTransition 傳遞至 TransitionManager.go(),即可在場景之間執行動畫。

轉場效果生命週期與活動生命週期類似,代表架構在動畫開始和完成之間監控的轉場效果狀態。在重要的生命週期狀態中,架構會叫用回呼函式,您可以實作這些函式,在轉換的不同階段調整使用者介面。

建立轉場效果

上一節說明如何建立代表不同檢視區塊階層狀態的場景。定義要變更的起始和結束場景後,請建立 Transition 物件來定義動畫。這個框架可讓您在資源檔案中指定內建轉場效果,並在程式碼中加載,或直接在程式碼中建立內建轉場效果的執行個體。

表 1. 內建轉場效果類型。

類別 標記 效果
AutoTransition <autoTransition/> 預設轉場。依序淡出、移動、調整大小及淡入檢視畫面。
ChangeBounds <changeBounds/> 移動及調整檢視區塊大小。
ChangeClipBounds <changeClipBounds/> 擷取場景變更前後的 View.getClipBounds(),並在轉場期間為這些變更加上動畫效果。
ChangeImageTransform <changeImageTransform/> 擷取場景變更前後的 ImageView 矩陣,並在轉場期間製作動畫。
ChangeScroll <changeScroll/> 擷取場景變更前後的目標捲動屬性,並為所有變更製作動畫。
ChangeTransform <changeTransform/> 擷取場景變更前後的檢視區塊縮放比例和旋轉角度,並在轉場期間將這些變更製成動畫。
Explode <explode/> 追蹤開始和結束場景中目標檢視區塊的顯示狀態變化,並將檢視區塊移入或移出場景邊緣。
Fade <fade/> fade_in 會淡入檢視畫面。
fade_out 會淡出檢視畫面。
fade_in_out (預設) 會執行 fade_out,然後執行 fade_in
Slide <slide/> 追蹤開始和結束場景中目標檢視區塊的顯示狀態變化,並將檢視區塊移入或移出場景的其中一個邊緣。

從資源檔案建立轉場效果執行個體

這項技巧可讓您修改轉場效果定義,不必變更活動的程式碼。如指定多個轉場效果一節所示,這項技巧也有助於將複雜的轉場效果定義與應用程式程式碼分開。

如要在資源檔案中指定內建轉場效果,請按照下列步驟操作:

  • res/transition/ 目錄新增至專案。
  • 在這個目錄中建立新的 XML 資源檔案。
  • 為其中一個內建轉場效果新增 XML 節點。

舉例來說,下列資源檔案會指定 Fade 轉場效果:

res/transition/fade_transition.xml

<fade xmlns:android="http://schemas.android.com/apk/res/android" />

下列程式碼片段說明如何從資源檔案,在活動中擴充 Transition 例項:

Kotlin

var fadeTransition: Transition =
    TransitionInflater.from(this)
                      .inflateTransition(R.transition.fade_transition)

Java

Transition fadeTransition =
        TransitionInflater.from(this).
        inflateTransition(R.transition.fade_transition);

在程式碼中建立轉場效果例項

如果您在程式碼中修改使用者介面,這項技術有助於動態建立轉場效果物件,並建立簡單的內建轉場效果例項,且幾乎不需要或完全不需要參數。

如要建立內建轉換的例項,請在 Transition 類別的子類別中,叫用其中一個公開建構函式。舉例來說,以下程式碼片段會建立 Fade 轉場的執行個體:

Kotlin

var fadeTransition: Transition = Fade()

Java

Transition fadeTransition = new Fade();

套用轉場效果

您通常會套用轉場效果,在回應事件 (例如使用者動作) 時,切換不同的檢視區塊階層。舉例來說,假設使用者在搜尋應用程式中輸入搜尋字詞並輕觸搜尋按鈕,應用程式會變更為代表結果版面的場景,同時套用轉場效果,淡出搜尋按鈕並淡入搜尋結果。

如要在活動中因應事件套用轉場效果時變更場景,請使用結尾場景和用於動畫的轉場效果例項呼叫 TransitionManager.go() 類別函式,如下列程式碼片段所示:

Kotlin

TransitionManager.go(endingScene, fadeTransition)

Java

TransitionManager.go(endingScene, fadeTransition);

架構會在執行轉場效果例項指定的動畫時,使用結尾場景的檢視區塊階層,變更場景根目錄內的檢視區塊階層。起始場景是上次轉場的結束場景。如果沒有先前的轉場效果,系統會根據使用者介面的目前狀態,自動決定起始場景。

如果未指定轉場例項,轉場管理員可以套用自動轉場,在大多數情況下執行合理的動作。詳情請參閱 TransitionManager 類別的 API 參考資料。

選擇特定目標觀看次數

架構預設會將轉場效果套用至開始和結束場景中的所有檢視區塊。在某些情況下,您可能只會想將動畫套用至場景中的部分檢視區塊。這個架構可讓您選取要製作動畫的特定檢視區塊。舉例來說,架構不支援為 ListView 物件的變更製作動畫,因此請勿在轉換期間嘗試為這些物件製作動畫。

轉場動畫的每個檢視區塊都稱為「目標」。您只能選取與場景相關聯的檢視區塊階層中的目標。

如要從目標清單中移除一或多個檢視區塊,請在開始轉場前呼叫 removeTarget() 方法。如要只將您指定的檢視區塊新增至目標清單,請呼叫 addTarget() 函式。詳情請參閱 Transition 類別的 API 參考資料。

指定多個轉場效果

如要讓動畫發揮最大效果,請根據場景間發生的變化類型,舉例來說,如果您要移除某些檢視區塊,並在場景之間新增其他檢視區塊,淡出或淡入動畫會明顯指出某些檢視區塊已無法使用。如果將檢視區塊移到螢幕上的不同位置,最好為移動過程加上動畫效果,讓使用者注意到檢視區塊的新位置。

您不必只選擇一個動畫,因為轉換架構可讓您在轉換集中合併動畫效果,其中包含一組個別的內建或自訂轉換。

如要從 XML 中的轉場效果集合定義轉場效果集,請在 res/transitions/ 目錄中建立資源檔案,並在 TransitionSet 元素下方列出轉場效果。舉例來說,下列程式碼片段說明如何指定與 AutoTransition 類別行為相同的轉換集:

<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
    android:transitionOrdering="sequential">
    <fade android:fadingMode="fade_out" />
    <changeBounds />
    <fade android:fadingMode="fade_in" />
</transitionSet>

如要在程式碼中將轉場效果集擴充為 TransitionSet 物件,請在活動中呼叫 TransitionInflater.from() 函式。TransitionSet 類別會從 Transition 類別擴充,因此您可以像其他 Transition 執行個體一樣,將其用於轉換管理工具。

套用不含場景的轉場效果

變更檢視區塊階層並非修改使用者介面的唯一方法。您也可以在目前的階層中新增、修改及移除子項檢視區塊,藉此進行變更。

舉例來說,您可以透過單一版面配置實作搜尋互動。首先,版面配置會顯示搜尋輸入欄位和搜尋圖示。如要變更使用者介面來顯示結果,請在使用者輕觸搜尋按鈕時,呼叫 ViewGroup.removeView() 函式來移除該按鈕,並呼叫 ViewGroup.addView() 函式來新增搜尋結果。

如果替代方案是使用兩個幾乎相同的階層,則可以採用這個方法。您不必為使用者介面中的微小差異建立及維護兩個不同的版面配置檔案,只要有一個版面配置檔案,其中包含您在程式碼中修改的檢視區塊階層即可。

如果您以這種方式在目前的檢視區塊階層中進行變更,則不需要建立場景。不過,您可以使用延遲轉場效果,在檢視區塊階層的兩種狀態之間建立及套用轉場效果。轉換架構的這項功能會從目前的檢視區塊階層狀態開始,記錄您對檢視區塊所做的變更,並在系統重新繪製使用者介面時,套用動畫效果來呈現變更。

如要在單一檢視區塊階層中建立延遲轉換效果,請按照下列步驟操作:

  1. 觸發轉場效果的事件發生時,請呼叫 TransitionManager.beginDelayedTransition() 函式,並提供要變更的所有檢視畫面的父項檢視畫面,以及要使用的轉場效果。架構會儲存子項檢視畫面的目前狀態及其屬性值。
  2. 視您的用途,變更子項檢視區塊。架構會記錄您對子項檢視區塊及其屬性所做的變更。
  3. 系統根據您的變更重新繪製使用者介面時,架構會為原始狀態和新狀態之間的變更製作動畫。

以下範例說明如何使用延遲轉換,為檢視區塊階層新增文字檢視區塊時加入動畫效果。第一個程式碼片段顯示版面配置定義檔案:

res/layout/activity_main.xml

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/mainLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <EditText
        android:id="@+id/inputText"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />
    ...
</androidx.constraintlayout.widget.ConstraintLayout>

下一個程式碼片段顯示的程式碼會為新增文字檢視區塊的動作加上動畫效果:

MainActivity

Kotlin

setContentView(R.layout.activity_main)
val labelText = TextView(this).apply {
    text = "Label"
    id = R.id.text
}
val rootView: ViewGroup = findViewById(R.id.mainLayout)
val mFade: Fade = Fade(Fade.IN)
TransitionManager.beginDelayedTransition(rootView, mFade)
rootView.addView(labelText)

Java

private TextView labelText;
private Fade mFade;
private ViewGroup rootView;
...
// Load the layout.
setContentView(R.layout.activity_main);
...
// Create a new TextView and set some View properties.
labelText = new TextView(this);
labelText.setText("Label");
labelText.setId(R.id.text);

// Get the root view and create a transition.
rootView = (ViewGroup) findViewById(R.id.mainLayout);
mFade = new Fade(Fade.IN);

// Start recording changes to the view hierarchy.
TransitionManager.beginDelayedTransition(rootView, mFade);

// Add the new TextView to the view hierarchy.
rootView.addView(labelText);

// When the system redraws the screen to show this update,
// the framework animates the addition as a fade in.

定義轉換生命週期回呼

轉場生命週期與活動生命週期類似。代表架構在呼叫 TransitionManager.go() 函式和動畫完成之間,監控的轉場狀態。在重要的生命週期狀態中,架構會叫用 TransitionListener 介面定義的回呼。

舉例來說,在場景變更期間,您可以利用轉場生命週期回呼,將檢視區塊屬性值從起始檢視區塊階層複製到結尾檢視區塊階層。您無法直接將值從起始檢視區塊複製到結束檢視區塊階層中的檢視區塊,因為結束檢視區塊階層要等到轉場效果完成後才會膨脹。相反地,您需要將值儲存在變數中,然後在架構完成轉場效果時,將值複製到結尾檢視區塊階層。如要在轉換完成時收到通知,請在活動中實作 TransitionListener.onTransitionEnd() 函式。

詳情請參閱 TransitionListener 類別的 API 參考資料。

限制

本節列出轉場框架的一些已知限制:

  • 套用至 SurfaceView 的動畫可能無法正確顯示。SurfaceView 執行個體是從非 UI 執行緒更新,因此更新內容可能與其他檢視區塊的動畫不同步。
  • 套用至 TextureView 時,某些特定轉場效果可能無法產生所需的動畫效果。
  • 擴充 AdapterView 的類別 (例如 ListView) 管理子項檢視畫面的方式與轉場效果架構不相容。如果嘗試根據 AdapterView 為檢視區塊製作動畫,裝置螢幕可能會停止回應。
  • 如果您嘗試使用動畫調整 TextView 的大小,文字會在物件完全調整大小前,彈跳到新位置。為避免這個問題,請勿對含有文字的檢視區塊大小變更進行動畫處理。