1. 事前準備
這個程式碼研究室是 Android Kotlin 進階功能課程的一部分。如果你依序完成程式碼研究室,就能充分發揮本課程的價值,但這不是強制要求。所有課程程式碼研究室都會列在「Android Kotlin 的進階程式碼研究室到達網頁」中。
MotionLayout
是一個程式庫,可讓您在 Android 應用程式中加入豐富的動態功能。它是以 ConstraintLayout,
為基礎,可用來為可透過 ConstraintLayout
建構的內容建立動畫。
您可以使用 MotionLayout
,同時為多個檢視畫面的位置、大小、顯示設定、Alpha 通道、顏色、高度、旋轉和其他屬性設定動畫效果。使用宣告式 XML 時,您可以建立包含多個檢視畫面的協調動畫,而難以在程式碼中實現。
動畫是提升應用程式體驗的絕佳方式。動畫的用途如下:
- 顯示變更:在狀態之間建立動畫效果,可讓使用者自然追蹤 UI 的變化。
- 吸引註意力:運用動畫將注意力吸引到重要的 UI 元素。
- 打造美觀的設計:在設計中加入有效的動態效果,可讓應用程式看起來更精美。
必要條件
本程式碼研究室是為具備部分 Android 開發經驗的開發人員而設計。嘗試完成本程式碼研究室之前,請先:
- 瞭解如何使用活動和基本版面配置建立應用程式,以及使用 Android Studio 在裝置或模擬器上執行應用程式。熟悉
ConstraintLayout
。詳閱限製版面配置程式碼研究室,進一步瞭解ConstraintLayout
。
執行步驟
- 使用
ConstraintSets
和MotionLayout
定義動畫 - 根據拖曳事件製作動畫
- 使用
KeyPosition
變更動畫 - 使用
KeyAttribute
變更屬性 - 透過程式碼執行動畫
- 使用
MotionLayout
為可收合標頭建立動畫
軟硬體需求
- Android Studio 4.0 (
MotionLayout
編輯器僅適用於這個版本的 Android Studio)。
2. 踏出第一步
如要下載範例應用程式,請執行下列任一操作:
... 或是使用以下指令從指令列複製 GitHub 存放區:
$ git clone https://github.com/googlecodelabs/motionlayout.git
3. 使用 MotionLayout 建立動畫
首先,您會建立動畫,以便在使用者按下廣告後,將檢視畫面從螢幕頂端移至底部。
如要透過範例程式碼建立動畫,您需要下列主要部分:
MotionLayout,
是ConstraintLayout
的子類別。您可以在MotionLayout
標記中指定要動畫的所有檢視畫面。MotionScene,
,這是描述MotionLayout.
動畫的 XML 檔案Transition,
是MotionScene
的一部分,用於指定動畫持續時間、觸發條件,以及移動檢視畫面的方式。ConstraintSet
會同時指定轉場效果的 start 和 end 限制。
讓我們從 MotionLayout
開始,逐一瞭解這些轉換。
步驟 1:探索現有程式碼
MotionLayout
是 ConstraintLayout
的子類別,因此可在新增動畫時支援所有相同的功能。如要使用 MotionLayout
,請新增 MotionLayout
檢視畫面,而您應該在其中使用 ConstraintLayout.
- 在
res/layout
中開啟activity_step1.xml.
,這裡有一個ConstraintLayout
含有一個星號的ImageView
,並在其中套用了色調。
activity_step1.xml
<!-- initial code -->
<androidx.constraintlayout.widget.ConstraintLayout
...
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ImageView
android:id="@+id/red_star"
...
/>
</androidx.constraintlayout.motion.widget.MotionLayout>
此 ConstraintLayout
沒有任何限制,因此如果您現在執行應用程式,會看到星號螢幕不受限制,因此星號圖示會放在不明位置。Android Studio 會向您發出沒有限制條件的警告。
步驟 2:轉換為動態版面配置
如要使用 MotionLayout,
製作動畫,您必須將 ConstraintLayout
轉換為 MotionLayout
。
如要在版面配置中使用動態場景,必須將其指向該動態場景。
- 方法是開啟設計介面。在 Android Studio 4.0 中,查看版面配置 XML 檔案時,使用右上方的分割或設計圖示,開啟設計介面。
- 開啟設計介面後,在預覽畫面上按一下滑鼠右鍵,然後選取「Convert to MotionLayout」。
這會將 ConstraintLayout
標記替換為 MotionLayout
標記,並將 motion:layoutDescription
新增至指向 @xml/activity_step1_scene.
的 MotionLayout
標記
activity_step1**.xml**
<!-- explore motion:layoutDescription="@xml/activity_step1_scene" -->
<androidx.constraintlayout.motion.widget.MotionLayout
...
motion:layoutDescription="@xml/activity_step1_scene">
動態場景是描述 MotionLayout
中動畫的單一 XML 檔案。
轉換為 MotionLayout
後,設計介面會顯示 Motion Editor
Motion Editor 中有三個新的 UI 元素:
- Overview (總覽) - 這項功能選取模式,可讓你選取動畫的不同部分。在這張圖片中,已選取
start
ConstraintSet
。您也可以按一下start
和end
之間的箭頭,選取轉場效果。 - 區段:總覽下方是版面視窗,會根據目前選取的總覽項目變更。在這張圖片中,選取視窗會顯示
start
ConstraintSet
資訊。 - 屬性 - 屬性面板會顯示總覽或選取視窗中目前所選項目的屬性。這張圖片顯示
start
ConstraintSet
的屬性。
步驟 3:定義開始和結束限制條件
所有動畫都能以開始和結束來定義。開始說明畫面在動畫開始前的外觀,結尾則說明畫面在動畫結束後的外觀。MotionLayout
負責確保開始和結束狀態 (指定時間) 之間的動畫效果。
MotionScene
會使用 ConstraintSet
標記定義開始和結束狀態。ConstraintSet
看起來是這樣的情況,也就是可套用至檢視區塊的一組限制條件。包括寬度、高度和 ConstraintLayout
限制。以及 alpha
等部分屬性。其中不含檢視畫面,只是這些檢視畫面的限制。
在 ConstraintSet
中指定的任何限制條件都會覆寫版面配置檔案中指定的限制。如果您在版面配置和 MotionScene
中定義限制條件,系統只會套用 MotionScene
中的限制條件。
在這個步驟中,您會限制星星檢視從螢幕頂端開始,到畫面底部結束。
如要完成這個步驟,您可以使用 Motion Editor,或是直接編輯 activity_step1_scene.xml
的文字。
- 在總覽面板中選取
start
ConstraintSet
- 在「selection」面板中,選取
red_star
。目前僅顯示layout
的來源,這表示該項目在此ConstraintSet
中不會受到限制。使用右上方的鉛筆圖示來「建立限制」
- 確認在總覽面板中選取
start
ConstraintSet
時,red_star
會顯示start
的來源。 - 在「Attributes」面板中,選取
start
ConstraintSet
中的「red_star
」,然後在頂端新增「Constraint」,接著點選藍色的 按鈕。
- 開啟
xml/activity_step1_scene.xml
即可查看 Motion Editor 為這項限制產生的程式碼。
activity_step1_scene.xml
<!-- Constraints to apply at the start of the animation -->
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/red_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>
ConstraintSet
的 id
為 @id/start
,並指定套用至 MotionLayout
中所有檢視畫面的所有限制條件。由於這個 MotionLayout
只有一項資料檢視,因此只需要一個 Constraint
。
ConstraintSet
中的 Constraint
會指定限制檢視畫面的 ID (在 activity_step1.xml
中定義)。@id/red_star
請務必注意,Constraint
標記只會指定限制和版面配置資訊。Constraint
標記不知道已套用至 ImageView
。
這項限制可指定將 red_star
檢視區塊限制為其父項頂端開頭所需的高度、寬度及其他兩項限制。
- 在總覽面板中選取
end
ConstraintSet。
- 按照上述步驟在
end
ConstraintSet
中新增red_star
的Constraint
。 - 如要使用 Motion Editor 完成這個步驟,請點按藍色的 + 按鈕,在
bottom
和end
中加入限制。
- XML 中的程式碼如下所示:
activitiy_step1_scene.xml
<!-- Constraints to apply at the end of the animation -->
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/red_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>
和 @id/start
一樣,這個 ConstraintSet
在 @id/red_star
中有單一 Constraint
。這次將圖片限制在畫面底部。
您不需要為其命名 @id/start
和 @id/end
,但命名方式相當方便。
步驟 4:定義轉場效果
每個 MotionScene
也至少必須包含一項轉場效果。轉場效果是指從頭到尾,每個動畫的各個部分。
轉場效果必須指定轉換的開始和結束 ConstraintSet
。轉場效果還能指定其他修改動畫的方式,例如動畫的執行時間,或是拖曳檢視畫面的動畫方式。
- Motion Editor 在建立 MotionScene 檔案時預設會為我們建立轉場效果。開啟
activity_step1_scene.xml
來查看產生的轉場效果。
activity_step1_scene.xml
<!-- A transition describes an animation via start and end state -->
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@id/start"
motion:duration="1000">
<KeyFrameSet>
</KeyFrameSet>
</Transition>
這就是 MotionLayout
建立動畫所需的一切。查看每個屬性:
- 系統會在動畫開始時對檢視畫面套用
constraintSetStart
。 - 系統會在動畫結束時的檢視畫面套用
constraintSetEnd
。 duration
會指定動畫播放時間 (以毫秒為單位)。
MotionLayout
會找出開始和結束限制之間的路徑,並在指定期間內建立動畫。
步驟 5:在 Motion Editor 中預覽動畫
動畫:在 Motion Editor 中播放轉場預覽畫面的影片
- 開啟 Motion Editor,然後在總覽面板中按一下
start
和end
之間的箭頭來選取轉場效果。
- 選取轉場效果時,「選取」面板會顯示播放控制項和進度控制列。按一下播放或拖曳目前位置即可預覽動畫。
步驟 6:新增點擊處理常式
您必須定義動畫的開始播放方式。其中一種做法是讓 MotionLayout
回應 @id/red_star
上的點擊事件。
- 開啟動態編輯器,在總覽面板中按一下開始與結尾之間的箭頭,然後選取轉場效果。
- 在總覽面板的工具列中,按一下 「建立點按或滑動處理常式」。這會新增用來啟動轉場的處理常式。
- 在彈出式視窗中選取「Click Handler」。
- 將「View To Click」變更為
red_star
。
- 按一下「Add」,在 Motion Editor 的轉場效果中會以小點表示。
- 在總覽面板中選取轉場效果後,請將
toggle
的clickAction
屬性加入剛在屬性面板中新增的 OnClick 處理常式。
- 開啟
activity_step1_scene.xml
即可查看 Motion Editor 產生的程式碼
activity_step1_scene.xml
<!-- A transition describes an animation via start and end state -->
<Transition
motion:constraintSetStart="@+id/start"
motion:constraintSetEnd="@+id/end"
motion:duration="1000">
<!-- MotionLayout will handle clicks on @id/red_star to "toggle" the animation between the start and end -->
<OnClick
motion:targetId="@id/red_star"
motion:clickAction="toggle" />
</Transition>
Transition
會指示 MotionLayout
使用 <OnClick>
標記執行動畫,回應點擊事件。查看每個屬性:
targetId
是點按的檢視畫面。toggle
的clickAction
會在點擊時切換為開始和結束狀態。您可以在說明文件中查看clickAction
的其他選項。
- 執行程式碼,依序點選「步驟 1」和紅色星號,即可查看動畫!
步驟 5:實際操作動畫
執行應用程式!按一下星號時,您應該就會看到動畫執行了。
已完成的動作場景檔案會定義一個 Transition
,指向開始和結束的 ConstraintSet
。
動畫開始 (@id/start
) 時,星號圖示限制在畫面頂端的開頭。動畫結束時 (@id/end
),星號圖示會顯示在畫面底部。
<?xml version="1.0" encoding="utf-8"?>
<!-- Describe the animation for activity_step1.xml -->
<MotionScene xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- A transition describes an animation via start and end state -->
<Transition
motion:constraintSetStart="@+id/start"
motion:constraintSetEnd="@+id/end"
motion:duration="1000">
<!-- MotionLayout will handle clicks on @id/star to "toggle" the animation between the start and end -->
<OnClick
motion:targetId="@id/red_star"
motion:clickAction="toggle" />
</Transition>
<!-- Constraints to apply at the end of the animation -->
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/red_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>
<!-- Constraints to apply at the end of the animation -->
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/red_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>
</MotionScene>
4. 根據拖曳事件製作動畫
在這個步驟中,您將建立回應使用者拖曳事件 (使用者滑動畫面時) 的動畫,以執行動畫。MotionLayout
支援追蹤觸控事件來移動檢視畫面,也支援透過物理快速滑過手勢讓動作流暢度。
步驟 1:檢查初始程式碼
- 如要開始使用,請開啟含有
MotionLayout
的版面配置檔案activity_step2.xml
。查看程式碼。
activity_step2.xml
<!-- initial code -->
<androidx.constraintlayout.motion.widget.MotionLayout
...
motion:layoutDescription="@xml/step2" >
<ImageView
android:id="@+id/left_star"
...
/>
<ImageView
android:id="@+id/right_star"
...
/>
<ImageView
android:id="@+id/red_star"
...
/>
<TextView
android:id="@+id/credits"
...
motion:layout_constraintTop_toTopOf="parent"
motion:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.motion.widget.MotionLayout>
這個版面配置定義了動畫的所有檢視畫面。三個星號圖示不會在版面配置中受到限制,因為系統會在動態場景中為圖示加上動畫效果。
抵免額 TextView
套用了限制條件,因為在整個動畫中維持不變,且不會修改任何屬性。
步驟 2:為場景製作動畫
和上一個動畫一樣,動畫是由開始和結束的 ConstraintSet,
和 Transition
定義。
定義起始 ConstraintSet
- 開啟動態場景
xml/step2.xml
來定義動畫。 - 新增起始限制
start
的限制條件。一開始,畫面底部的中心會顯示這三顆星星。左右星號的alpha
值是0.0
,表示完全透明且隱藏。
step2.xml
<!-- TODO apply starting constraints -->
<!-- Constraints to apply at the start of the animation -->
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/red_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintBottom_toBottomOf="parent" />
<Constraint
android:id="@+id/left_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.0"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintBottom_toBottomOf="parent" />
<Constraint
android:id="@+id/right_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.0"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>
在此 ConstraintSet
中,為每個星星指定一個 Constraint
。每個限制條件都會在動畫開始時由 MotionLayout
套用。
每個星號檢視都會在畫面底部置中,並使用開始、結束和底部限制。兩顆星 @id/left_star
和 @id/right_star
都有額外的 Alpha 值,因此不會顯示,系統會在動畫開始時套用這個值。
start
和 end
限制可定義動畫的開始和結束。起始限制 (例如 motion:layout_constraintStart_toStartOf
) 會限制檢視畫面的開始至另一個檢視畫面的起始位置。這在一開始可能會造成混淆,因為 start
名稱同時用於「而且」在限制條件的情況下使用。為區分差異,layout_constraintStart
中的 start
是指「啟動」也就是由左至右的語言、由右至左的語言呈現。start
限制組合是指動畫的開始時間。
定義結束限制集
- 定義結束限制,使用鏈結將全部三顆星放在
@id/credits
下方。此外,它會將左右星號的alpha
結束值設為1.0
。
step2.xml
<!-- TODO apply ending constraints -->
<!-- Constraints to apply at the end of the animation -->
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/left_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="1.0"
motion:layout_constraintHorizontal_chainStyle="packed"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toStartOf="@id/red_star"
motion:layout_constraintTop_toBottomOf="@id/credits" />
<Constraint
android:id="@+id/red_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintStart_toEndOf="@id/left_star"
motion:layout_constraintEnd_toStartOf="@id/right_star"
motion:layout_constraintTop_toBottomOf="@id/credits" />
<Constraint
android:id="@+id/right_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="1.0"
motion:layout_constraintStart_toEndOf="@id/red_star"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintTop_toBottomOf="@id/credits" />
</ConstraintSet>
如此一來,檢視畫面會在動畫中向外擴散,並往中央伸展。
此外,由於兩個 ConstraintSets
的 @id/right_start
和 @id/left_star
都設定了 alpha
屬性,因此兩個檢視畫面都會隨著動畫播放而淡入。
根據使用者滑動操作製作動畫
MotionLayout
可以追蹤使用者拖曳事件 (滑動),建立以物理為基礎的「快速滑過」效果這表示如果使用者快速擲回這些檢視畫面,檢視畫面將會繼續執行,就像移動到表面上的實體物件一樣速度變慢。您可以在 Transition
中使用 OnSwipe
標記新增這種類型的動畫。
- 取代 TODO,以
<OnSwipe motion:touchAnchorId="@id/red_star" />
新增OnSwipe
標記。
step2.xml
<!-- TODO add OnSwipe tag -->
<!-- A transition describes an animation via start and end state -->
<Transition
motion:constraintSetStart="@+id/start"
motion:constraintSetEnd="@+id/end">
<!-- MotionLayout will track swipes relative to this view -->
<OnSwipe motion:touchAnchorId="@id/red_star" />
</Transition>
OnSwipe
包含幾個屬性,最重要的是 touchAnchorId
。
touchAnchorId
是回應輕觸的追蹤檢視畫面,MotionLayout
會讓這個檢視畫面與滑動手指的距離相同。touchAnchorSide
會決定要追蹤檢視畫面的哪一側。如果檢視區塊會調整大小、遵循複雜的路徑,或某側移動速度較快,這一點就很重要。dragDirection
會決定動畫的方向 (向上、向下、向左或向右)。
當 MotionLayout
監聽拖曳事件時,事件監聽器會登錄到 MotionLayout
檢視畫面,而非 touchAnchorId
指定的檢視畫面。當使用者在畫面上的任何位置啟動手勢時,MotionLayout
會維持手指與 touchAnchorId
檢視畫面常數的 touchAnchorSide
之間的距離。舉例來說,如果使用者輕觸與錨點距離 100dp 的距離,在整個動畫中,MotionLayout
都會讓該側與手指保持 100 dp 距離。
立即試用
- 再次執行應用程式,然後開啟「步驟 2」畫面。畫面上會顯示動畫。
- 嘗試「快速滑過」或放開手指瀏覽動畫,看看
MotionLayout
如何顯示流暢的物理動畫!
MotionLayout
可以使用 ConstraintLayout
中的功能,在截然不同的設計之間建立動畫效果。
在這段動畫中,畫面底部會顯示這三個檢視畫面,都與其父項檢視畫面相對的位置。最後,三個檢視畫面的位置是相對於鏈結中的 @id/credits
。
儘管這些版面配置非常不同,MotionLayout
仍會在開始和結束之間建立流暢的動畫。
5. 修改路徑
在這個步驟中,您會建立在動畫播放中依複雜路徑顯示的動畫,並在動作期間以動畫方式呈現功勞。MotionLayout
可以使用 KeyPosition
修改檢視畫面在開始和結束之間的路徑。
步驟 1:探索現有程式碼
- 開啟
layout/activity_step3.xml
和xml/step3.xml
,查看現有的版面配置和動態場景。ImageView
和TextView
會顯示月亮和抵免額文字。 - 開啟動態場景檔案 (
xml/step3.xml
)。您會看見從@id/start
到@id/end
的Transition
已定義。動畫使用兩個ConstraintSets
,將月亮圖片從螢幕左下角移至畫面右下方。抵免額的移動時,抵免額文字會從alpha="0.0"
淡出至alpha="1.0"
。 - 執行應用程式,然後選取「步驟 3」。按一下月亮時,您會看到月球從起點到終點的線性路徑 (或直線)。
步驟 2:啟用路徑偵錯功能
在將弧形新增至月球運動之前,建議您在 MotionLayout
中啟用路徑偵錯功能。
您可以利用 MotionLayout
繪製每個檢視畫面的動畫路徑,協助開發複雜的動畫。如果您想以視覺化方式呈現動畫,或微調動態的小細節,這項功能就很實用。
- 如要啟用偵錯路徑,請開啟
layout/activity_step3.xml
並將motion:motionDebug="SHOW_PATH"
新增至MotionLayout
標記。
activity_step3.xml
<!-- Add motion:motionDebug="SHOW_PATH" -->
<androidx.constraintlayout.motion.widget.MotionLayout
...
motion:motionDebug="SHOW_PATH" >
啟用路徑偵錯後,當您再次執行應用程式時,您將會看到所有檢視畫面的路徑,這些路徑都以虛線顯示。
- 圓形表示每個檢視畫面的開始或結束位置。
- 線條代表單一檢視畫面的路徑。
- 「鑽石」代表修改路徑的
KeyPosition
。
以此動畫為例,中間圓圈是指抵免額文字的位置。
步驟 3:修改路徑
MotionLayout
中的所有動畫都是由開始和結束的 ConstraintSet
定義,這個物件可定義動畫開始播放前和動畫結束後的畫面。根據預設,MotionLayout
會在每個變更位置時,繪製開始與結束位置之間的線性路徑 (直線)。
如要建構此範例中的月球弧形等複雜路徑,MotionLayout
會使用 KeyPosition
修改檢視區塊在開始和結束之間移動的路徑。
- 開啟
xml/step3.xml
,並將KeyPosition
新增至場景。KeyPosition
標記位於Transition
標記內。
step3.xml
<!-- TODO: Add KeyFrameSet and KeyPosition -->
<KeyFrameSet>
<KeyPosition
motion:framePosition="50"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.5"
/>
</KeyFrameSet>
KeyFrameSet
是 Transition
的子項,也是轉換期間應套用的所有 KeyFrames
組合 (例如 KeyPosition
)。
由於 MotionLayout
會計算開始與結束之間的月亮路徑,因此會根據 KeyFrameSet
中指定的 KeyPosition
修改路徑。您可以再次執行應用程式,看看此舉如何修改路徑。
KeyPosition
具有多個屬性,說明其如何修改路徑。最重要的是:
framePosition
是介於 0 到 100 之間的數字。這會定義在動畫中套用此KeyPosition
的時機,其中 1 為 1% 的整個動畫,以及 99 到 99% 的動畫。所以如果值是 50,就應該在中間套用。motionTarget
是此KeyPosition
修改路徑的檢視畫面。- 此
KeyPosition
是如何修改路徑的keyPositionType
。可以是parentRelative
、pathRelative
或deltaRelative
(如下一個步驟所述)。 percentX | percentY
是要在framePosition
修改路徑的程度 (介於 0.0 至 1.0 之間的值,可以為負值且值大於 1)。
您可以這樣想:「在 framePosition
修改路徑:framePosition
修改路徑,修改方式是 percentX
,或percentY
根據 keyPositionType
決定的座標修改路徑」。motionTarget
根據預設,MotionLayout
會將修改路徑後引進的任何邊角四捨五入。當您看剛剛建立的動畫時,可以看到月球跟在彎曲的彎曲路徑上。對多數動畫來說,這是你想要的內容;如果沒有,你可以指定 curveFit
屬性來自訂動畫。
馬上試試
如果您再次執行應用程式,就會看到這個步驟的動畫。
月球緊隨弧形,因為它會通過 Transition
中指定的 KeyPosition
。
<KeyPosition
motion:framePosition="50"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.5"
/>
您可以將此 KeyPosition
解讀為:「在 framePosition 50
時 (動畫中半段),根據 parentRelative
(整個 MotionLayout
) 決定的座標,由 50% Y
移動 motionTarget
@id/moon
的路徑 (對畫面的半下方) 修改 motionTarget
@id/moon
的路徑。」
因此,在動畫中途,月亮必須通過畫面下 50% 的 KeyPosition
。此 KeyPosition
完全不會修改 X 動作,因此月亮仍會從開始到結束橫向。MotionLayout
會找出通過這個 KeyPosition
,同時在開始和結束之間移動的順暢路徑。
如果仔細觀察,作者資訊文字就會受到月亮位置的限制。為什麼不垂直移動?
<Constraint
android:id="@id/credits"
...
motion:layout_constraintBottom_toBottomOf="@id/moon"
motion:layout_constraintTop_toTopOf="@id/moon"
/>
就算您修改了月球的行經路徑,月亮的起點和終點卻還是沒有垂直移動。KeyPosition
不會修改開始或結束位置,因此抵免額文字僅限於月球的最終結束位置。
如果想隨著抵免額隨著月亮移動,可以為抵免額新增 KeyPosition
,或是修改 @id/credits
的起始限制。
下一節將深入說明 MotionLayout
中不同類型的 keyPositionType
。
6. 瞭解 keyPositionType
在上一個步驟中,您使用了 parentRelative
的 keyPosition
類型,將路徑偏移至螢幕的 50%。屬性 keyPositionType
會決定 MotionLayout 根據 percentX
或 percentY
修改路徑的「方式」。
<KeyFrameSet>
<KeyPosition
motion:framePosition="50"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.5"
/>
</KeyFrameSet>
可用的 keyPosition
分為三種類型:parentRelative
、pathRelative
和 deltaRelative
。指定類型會變更計算 percentX
和 percentY
時使用的座標系統。
什麼是座標系統?
「座標系統」可用於在空間中指定點。這些註解也可用來描述畫面上的位置。
MotionLayout
座標系統屬於購物車座標系統。也就是有 X 軸和 Y 軸,並由兩條垂直線定義。兩者的主要差異在於 X 軸在畫面上的位置 (Y 軸一律與 X 軸垂直)。
MotionLayout
中的所有座標系統都會在 X 和 Y 軸上使用 0.0
和 1.0
之間的值。可使用負值和大於 1.0
的值。舉例來說,如果 percentX
值為 -2.0
,就代表 X 軸的相反方向。
如果您覺得「代數課程」聽起來太過緊張,請查看下方的圖片!
父項座標
parentRelative
的 keyPositionType
使用與螢幕相同的座標系統。這可將 (0, 0)
定義在整個 MotionLayout
的左上方,並將 (1, 1)
定義在右下方。
您可以隨時使用 parentRelative
製作會穿過整個 MotionLayout
的動畫 (如這個範例中的月球弧形)。
然而,如果您想要修改相對於動態的路徑,例如讓它稍微呈現曲線,則其他兩個座標系統是更好的選擇。
差異座標
差異值是「變化」的數學詞,所以 deltaRelative
是「相對改變」的方式。在 deltaRelative
座標(0,0)
中,檢視畫面的起始位置,(1,1)
則是結束位置。X 和 Y 軸對齊畫面。
螢幕上的 X 軸一律為水平,Y 軸則一律為垂直畫面。與 parentRelative
相比,主要差別在於座標僅代表畫面移動檢視畫面的部分。
deltaRelative
是絕佳的座標系統,可以單獨控制水平或垂直動作。舉例來說,您可以建立動畫,使其在 50% 處完成垂直 (Y) 移動,然後繼續水平 (X) 動畫。
庫存座標
MotionLayout
中的最後一個座標系統是 pathRelative
。X 軸會隨開始到結束的動態路徑,因此也與另外兩軸截然不同。因此,(0,0)
是起始位置,(1,0)
則是結束位置。
你為什麼想這麼做?乍看之下相當令人驚訝,尤其是因為這個座標系統甚至並未對齊螢幕座標。
結果顯示,pathRelative
非常適合用於下列功能。
- 在部分動畫過程中加速、減速或停止檢視畫面。由於 X 維度一律與檢視畫面採用的路徑完全相符,因此您可以使用
pathRelative
KeyPosition
變更路徑中特定點的framePosition
。因此,在framePosition="50"
搭配percentX="0.1"
的KeyPosition
,會導致動畫在前 10% 運動時間佔 50% 的時間。 - 在路徑中加入細微的弧線。Y 維度總是與動態垂直,因此變更 Y 會將路徑改成相對於整體動作的曲線。
- 在
deltaRelative
無法運作時新增第二個維度。如果是完全水平和垂直的動作,deltaRelative
只會建立一個實用的維度。不過,pathRelative
一律會建立可用的 X 和 Y 座標。
在下一個步驟中,您將瞭解如何使用多個 KeyPosition
建構更複雜的路徑。
7. 建立複雜路徑
檢視您在上個步驟建立的動畫,確實會形成平滑曲線,但形狀可能會比較像是「喜歡月亮」。
修改包含多個 KeyPosition 元素的路徑
MotionLayout
可以進一步定義任何動作所需的 KeyPosition
,藉此進一步修改路徑。在這個動畫中,您將建立一個弧形,但您可以視需要在畫面中間上下跳動。
- 開啟
xml/step4.xml
。您會看到這個檢視畫面與上個步驟新增的KeyFrame
相同。 - 如要將曲線的頂端納入圓滑邊緣,請在
@id/moon
的路徑中再加入兩個KeyPositions
,一個在到達頂端之前,一個在路徑頂端。
step4.xml
<!-- TODO: Add two more KeyPositions to the KeyFrameSet here -->
<KeyPosition
motion:framePosition="25"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.6"
/>
<KeyPosition
motion:framePosition="75"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.6"
/>
這些 KeyPositions
會套用動畫效果的 25% 和 75%,並導致 @id/moon
沿著與畫面頂端 60% 距離的路徑移動。與現有 KeyPosition
的 50% 結合之後,即可創造出平滑的月球。
在 MotionLayout
中,您可以視需求新增任意數量的 KeyPositions
,以便取得所需動態路徑。MotionLayout
會在指定的 framePosition
中套用每個 KeyPosition
,並設法建立通過所有 KeyPositions
的流暢動作。
馬上試試
- 再次執行應用程式。前往步驟 4 觀看動畫的實際效果。按一下月亮時,系統會從頭到尾依序瀏覽路徑,瀏覽在
KeyFrameSet
中指定的每個KeyPosition
。
自行探索
開始使用其他類型的 KeyFrame
前,請先嘗試在 KeyFrameSet
中新增更多 KeyPositions
,看看只要使用 KeyPosition
即可建立什麼效果。
以下的例子說明如何建構可在動畫過程中來回移動的複雜路徑。
step4.xml
<!-- Complex paths example: Dancing moon -->
<KeyFrameSet>
<KeyPosition
motion:framePosition="25"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.6"
motion:percentX="0.1"
/>
<KeyPosition
motion:framePosition="50"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.5"
motion:percentX="0.3"
/>
<KeyPosition
motion:framePosition="75"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.6"
motion:percentX="0.1"
/>
</KeyFrameSet>
瞭解 KeyPosition
的各項功能後,您就可以在下一個步驟中繼續進行其他類型的 KeyFrames
。
8. 在動作期間變更屬性
建立動態動畫通常意味著隨著動畫進度而變更檢視畫面的 size
、rotation
或 alpha
。MotionLayout
支援使用 KeyAttribute
,在任何檢視畫面上為許多屬性建立動畫效果。
在這個步驟中,您將使用 KeyAttribute
縮放月亮並旋轉。您也會使用 KeyAttribute
延後文字外觀,直到月球快遞完成為止。
步驟 1:使用 KeyAttribute 調整大小和旋轉
- 開啟
xml/step5.xml
,其中包含您在上個步驟中建立的相同動畫。為方便起見,這個畫面會使用其他空間相片做為背景。 - 如要將月亮放大並旋轉,請在
KeyFrameSet
的keyFrame="50"
和keyFrame="100"
中加入兩個KeyAttribute
標記
step5.xml
<!-- TODO: Add KeyAttributes to rotate and resize @id/moon -->
<KeyAttribute
motion:framePosition="50"
motion:motionTarget="@id/moon"
android:scaleY="2.0"
android:scaleX="2.0"
android:rotation="-360"
/>
<KeyAttribute
motion:framePosition="100"
motion:motionTarget="@id/moon"
android:rotation="-720"
/>
這些 KeyAttributes
會套用動畫的 50% 和 100%。第一個是 50% 的 KeyAttribute
發生在弧形頂部,這會使檢視畫面在大小上加倍,同時旋轉 -360 度 (或一個圓形)。第二個 KeyAttribute
會將第二次旋轉至 -720 度 (兩個完整的圓圈),並將大小放回一般,因為 scaleX
和 scaleY
的值預設為 1.0。
和 KeyPosition
一樣,KeyAttribute
會使用 framePosition
和 motionTarget
指定套用 KeyFrame
的時機和修改檢視畫面。MotionLayout
會在 KeyPositions
之間插入內插,建立流暢動畫。
KeyAttributes
支援可套用至所有資料檢視的屬性。支援變更基本屬性,例如 visibility
、alpha
或 elevation
。您也可以像在這裡一樣變更旋轉角度、使用 rotateX
和 rotateY
旋轉三個尺寸、使用 scaleX
和 scaleY
縮放大小,或者轉換檢視畫面在 X、Y 或 Z 中的位置。
步驟 2:延遲發放抵免額
這個步驟的目標之一是更新動畫,讓製作人員名單文字要等到動畫播放完畢後才顯示。
- 如要延遲抵免額出現,請再定義一個
KeyAttribute
,確保alpha
在keyPosition="85"
前會維持在 0。MotionLayout
仍可從 0 到 100 alpha 順暢地轉換,但會沿著動畫最後 15% 的進度執行。
step5.xml
<!-- TODO: Add KeyAttribute to delay the appearance of @id/credits -->
<KeyAttribute
motion:framePosition="85"
motion:motionTarget="@id/credits"
android:alpha="0.0"
/>
這個 KeyAttribute
會在前 85% 動畫的前 85% 中,將 @id/credits
的 alpha
維持在 0.0。由於一開始的 Alpha 值為 0,因此會在動畫的前 85% 不會顯示。
此 KeyAttribute
的最終效果是製作人員名單顯示在動畫的結尾處。如此一來,介面的外觀就會與畫面右上角的月亮協調相協調。
藉由延遲一個檢視畫面上的動畫,而另一個檢視畫面移動的情形可像此一樣,您可以建立令人印象深刻的動畫,為使用者的動態效果。
馬上試試
- 再次執行應用程式,然後前往步驟 5,查看動畫的實際效果。點選月球後,系統會按照從開始到結束的路徑逐一瀏覽在
KeyFrameSet
中指定的KeyAttribute
。
由於您旋轉了月亮兩個完全圓圈,所以現在會重複向後翻轉,而且製作人員會延遲顯示動畫,直到動畫結束為止。
自行探索
請先嘗試修改 KeyAttributes
中的其他標準屬性,再前往最終的 KeyFrame
類型。舉例來說,請嘗試將 rotation
變更為 rotationX
,看看其產生的動畫。
下列是您可以嘗試的標準屬性清單:
android:visibility
android:alpha
android:elevation
android:rotation
android:rotationX
android:rotationY
android:scaleX
android:scaleY
android:translationX
android:translationY
android:translationZ
9. 變更自訂屬性
豐富的動畫涉及變更檢視畫面的顏色或其他屬性。雖然 MotionLayout
可以使用 KeyAttribute
變更上一個工作中列出的任何標準屬性,但您也可以使用 CustomAttribute
指定任何其他屬性。
CustomAttribute
可用於設定具有 setter 的任何值。舉例來說,您可以使用 CustomAttribute
設定 View 的 backgroundColor。MotionLayout
會使用「反射」尋找 setter,然後重複呼叫,為檢視畫面建立動畫效果。
在這個步驟中,您將使用 CustomAttribute
設定月球上的 colorFilter
屬性,建立下方顯示的動畫。
定義自訂屬性
- 如要開始使用,請開啟
xml/step6.xml
,其中包含您在上個步驟中建立的相同動畫。 - 如要將月亮變色,請在
KeyFrameSet
的keyFrame="0"
、keyFrame="50"
和keyFrame="100".
中新增兩個含有CustomAttribute
的KeyAttribute
step6.xml
<!-- TODO: Add Custom attributes here -->
<KeyAttribute
motion:framePosition="0"
motion:motionTarget="@id/moon">
<CustomAttribute
motion:attributeName="colorFilter"
motion:customColorValue="#FFFFFF"
/>
</KeyAttribute>
<KeyAttribute
motion:framePosition="50"
motion:motionTarget="@id/moon">
<CustomAttribute
motion:attributeName="colorFilter"
motion:customColorValue="#FFB612"
/>
</KeyAttribute>
<KeyAttribute
motion:framePosition="100"
motion:motionTarget="@id/moon">
<CustomAttribute
motion:attributeName="colorFilter"
motion:customColorValue="#FFFFFF"
/>
</KeyAttribute>
您會在 KeyAttribute
中新增 CustomAttribute
。CustomAttribute
會在 KeyAttribute
指定的 framePosition
套用。
在 CustomAttribute
中,必須指定 attributeName
和一個要設定的值。
motion:attributeName
是此自訂屬性將呼叫的 setter 名稱。在這個範例中,系統會呼叫Drawable
上的setColorFilter
。motion:custom*Value
是名稱中註明的類型自訂值,在此範例中,自訂值是指定的顏色。
自訂值可以是下列任一種類型:
- 顏色
- 整數
- 浮點值
- 字串
- 維度
- 布林值
使用這個 API,MotionLayout
可為任何檢視畫面提供 setter 的任何內容加上動畫效果。
馬上試試
- 再次執行應用程式,然後前往步驟 6,查看動畫的實際效果。點選月球後,系統會按照從開始到結束的路徑逐一瀏覽在
KeyFrameSet
中指定的KeyAttribute
。
新增更多 KeyFrames
時,MotionLayout
會將月亮路徑從直線變更為複雜的曲線,在動畫中增加雙向翻轉、調整大小,以及變更色彩。
在真實動畫中,您通常可以同時為數個檢視畫面建立動畫,並控制這些檢視畫面在不同路徑和速度上的動作。透過為每個檢視畫面指定不同的 KeyFrame
,即可利用 MotionLayout
編排多媒體動畫,建立動畫效果的多個檢視畫面。
10. 拖曳事件和複雜的路徑
在這個步驟中,您將瞭解如何使用包含複雜路徑的 OnSwipe
。到目前為止,OnClick
事件監聽器觸發了月亮動畫,且執行時間固定。
使用 OnSwipe
控制含有複雜路徑的動畫 (例如您在最後幾個步驟中建立的月亮動畫),需要瞭解 OnSwipe
的運作方式。
步驟 1:瞭解 OnSwipe 行為
- 開啟
xml/step7.xml
並找出現有的OnSwipe
宣告。
step7.xml
<!-- Fix OnSwipe by changing touchAnchorSide →
<OnSwipe
motion:touchAnchorId="@id/moon"
motion:touchAnchorSide="bottom"
/>
- 在裝置上執行應用程式,然後前往步驟 7。沿著弧形路徑拖曳月亮,看看是否能產生流暢的動畫。
執行這個動畫時,看起來不會很滿意。月球到達弧形頂端後,就會開始跳動。
如要瞭解錯誤,請考慮使用者輕觸弧形頂端下方時會發生什麼事。由於 OnSwipe
標記包含 motion:touchAnchorSide="bottom"
MotionLayout
,系統會嘗試在整個動畫期間,讓手指與檢視畫面常數之間的距離保持不變。
但是,由於月亮底部的方向不一定相同,因此圖示會往上回升,因此 MotionLayout
不知道使用者剛通過弧形頂部時該怎麼做。既然如此,由於你正在追蹤月亮底部,因此使用者輕觸這裡時,應該把它擺放在哪個位置?
步驟 2:使用右側
為避免這類錯誤,請務必選擇在整個動畫播放期間一律朝同一方向的 touchAnchorId
和 touchAnchorSide
。
在這個動畫中,right
側和月亮 left
側都會在螢幕上朝某個方向前進。
但是,bottom
和 top
都會反向方向。當 OnSwipe
嘗試追蹤這些線索時,如果方向改變,就會混淆。
- 如要讓此動畫追蹤觸控事件,請將
touchAnchorSide
變更為right
。
step7.xml
<!-- Fix OnSwipe by changing touchAnchorSide →
<OnSwipe
motion:touchAnchorId="@id/moon"
motion:touchAnchorSide="right"
/>
步驟 3:使用 DragDirection
您也可以將 dragDirection
與 touchAnchorSide
結合,讓側軌和一般的方向不同。仍需讓 touchAnchorSide
只往一個方向進行,但您可以告知 MotionLayout
要追蹤的方向。舉例來說,您可以保留 touchAnchorSide="bottom"
,但新增 dragDirection="dragRight"
。這會讓 MotionLayout
追蹤檢視畫面底部的位置,但只在向右移動時考慮其位置 (會忽略垂直動作)。因此,即使底部上下顛倒,仍會搭配 OnSwipe
正確播放動畫。
- 更新
OnSwipe
即可正確追蹤月亮運動。
step7.xml
<!-- Using dragDirection to control the direction of drag tracking →
<OnSwipe
motion:touchAnchorId="@id/moon"
motion:touchAnchorSide="bottom"
motion:dragDirection="dragRight"
/>
馬上試試
- 請再次執行應用程式,然後試著將月球拖曳到整個路徑。雖然採用複雜的弧線,但
MotionLayout
能夠回應滑動事件的動畫。
11. 透過程式碼跑步
與 CoordinatorLayout
搭配使用時,MotionLayout
可用於建構豐富的動畫。在這個步驟中,您將使用 MotionLayout
建立可收合標頭。
步驟 1:探索現有程式碼
- 如要開始使用,請開啟「
layout/activity_step8.xml
」。 - 在
layout/activity_step8.xml
中,您會看到已建構運作中的CoordinatorLayout
和AppBarLayout
。
activity_step8.xml
<androidx.coordinatorlayout.widget.CoordinatorLayout
...>
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar_layout"
android:layout_width="match_parent"
android:layout_height="180dp">
<androidx.constraintlayout.motion.widget.MotionLayout
android:id="@+id/motion_layout"
... >
...
</androidx.constraintlayout.motion.widget.MotionLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
...
motion:layout_behavior="@string/appbar_scrolling_view_behavior" >
...
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
此版面配置使用 CoordinatorLayout
在 NestedScrollView
和 AppBarLayout
之間共用捲動資訊。因此,當 NestedScrollView
向上捲動時,會向 AppBarLayout
告知變更。這就是在 Android 上實作類似收合工具列的方式,而文字的捲動將會「協調」與收合標題相同
@id/motion_layout
指向的動作場景與最後一個步驟中的動態場景相似。不過,為了與 CoordinatorLayout
搭配使用,系統已移除 OnSwipe
宣告。
- 執行應用程式,然後前往步驟 8。您會發現,捲動文字時,月亮並不會移動。
步驟 2:讓 MotionLayout 捲動
- 如要讓
MotionLayout
檢視畫面在NestedScrollView
捲動時立即捲動,請在MotionLayout
中加入motion:minHeight
和motion:layout_scrollFlags
。
activity_step8.xml
<!-- Add minHeight and layout_scrollFlags to the MotionLayout -->
<androidx.constraintlayout.motion.widget.MotionLayout
android:id="@+id/motion_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
motion:layoutDescription="@xml/step8"
motion:motionDebug="SHOW_PATH"
android:minHeight="80dp"
motion:layout_scrollFlags="scroll|enterAlways|snap|exitUntilCollapsed" >
- 再次執行應用程式,然後前往步驟 8。您會發現,
MotionLayout
會在您向上捲動時收合。不過,動畫尚未根據捲動行為進度。
步驟 3:透過程式碼移動動態
- 開啟
Step8Activity.kt
。編輯coordinateMotion()
函式,告知MotionLayout
捲動位置的變化。
Step8Activity.kt
// TODO: set progress of MotionLayout based on an AppBarLayout.OnOffsetChangedListener
private fun coordinateMotion() {
val appBarLayout: AppBarLayout = findViewById(R.id.appbar_layout)
val motionLayout: MotionLayout = findViewById(R.id.motion_layout)
val listener = AppBarLayout.OnOffsetChangedListener { unused, verticalOffset ->
val seekPosition = -verticalOffset / appBarLayout.totalScrollRange.toFloat()
motionLayout.progress = seekPosition
}
appBarLayout.addOnOffsetChangedListener(listener)
}
此程式碼會註冊一個 OnOffsetChangedListener
,每當使用者以目前的捲動偏移方式捲動網頁時,就會呼叫這個程式碼。
MotionLayout
支援透過設定進度屬性尋找轉場效果。如要在 verticalOffset
和百分比進度之間轉換,請將捲動總範圍除以總捲動範圍。
馬上試試
- 再次部署應用程式,然後執行「步驟 8」動畫。您會看到
MotionLayout
會根據捲動位置播放動畫。
您可以使用 MotionLayout
建構自訂動態收合工具列動畫。使用一系列的 KeyFrames
可以創造非常大膽的效果。
12. 恭喜
本程式碼研究室涵蓋了 MotionLayout
的基本 API。
如要查看更多 MotionLayout
實務範例,請參閱官方範例。請務必詳閱說明文件!
瞭解詳情
MotionLayout
支援更多本程式碼研究室未涵蓋的功能,例如可讓您以重複週期控制路徑或屬性的 KeyCycle,
,以及可讓您根據時鐘時間製作動畫的 KeyTimeCycle,
。請參考範例。
如需本課程其他程式碼研究室的連結,請參閱「Android Kotlin 的進階程式碼研究室到達網頁」連結。