MotionLayout使用指南
在傳統Android開發中,創建流暢的交互式動畫往往需要組合使用屬性動畫、TransitionManager或CoordinatorLayout等多種技術,代碼量大且維護困難。Google在ConstraintLayout 2.0中推出的MotionLayout成功解決了這一痛點,它將佈局容器與動畫描述分離,通過聲明式的XML配置即可實現複雜的交互動畫MotionLayout兼具了屬性動畫的靈活性、TransitionManager的佈局過渡能力以及CoordinatorLayout的觸摸響應特性,成為一個功能強大的全能型動畫解決方案。
一、MotionLayout與MotionScene
MotionLayout本身是一個ViewGroup(視圖容器),而MotionScene是其核心的"大腦",專門用於定義和控制動畫。
MotionLayout實際是ConstraintLayout的子類,它首先是一個佈局容器,負責承載界面元素。同時,它也是一個動畫引擎,實時解析和執行MotionScene中的指令。
MotionScene:是一個獨立的XML資源文件(通常位於res/xml目錄),作為動畫的"劇本",詳細描述動畫的各種狀態和過渡過程。MotionLayout通過app:layoutDescription屬性與MotionScene文件關聯。
這種分離設計讓動畫邏輯與佈局容器解耦,大大提高了代碼的可維護性和可讀性。
二、項目配置與創建方式
2.1 添加依賴
要使用MotionLayout,首先需要在項目的build.gradle文件中添加ConstraintLayout 2.0及以上版本的依賴
dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.2.0'
}
2.2 創建方式
- 最直接的方式是對佈局進行自動轉換。在Android Studio中,打開已有的ConstraintLayout佈局文件,在Component Tree中右鍵點擊根佈局,選擇"Convert to MotionLayout"。
這種方式Android Studio會自動將ConstraintLayout替換為MotionLayout,並在res/xml目錄下創建對應的MotionScene文件,同時通過app:layoutDescription屬性建立兩者的關聯關係。
- 也可以自行手動創建,手動修改佈局文件的根標籤為MotionLayout或者創建文件時指定MotionLayout的根佈局,但是這種做法需要我們自己在
res/xml文件下創建對應的MotionScene文件並進行關聯。
三、MotionScene的三大核心組件
MotionScene由三個基本組成部分ConstraintSet(約束集),Transition(過渡)和觸發機制構成,理解它們是掌握MotionLayout的關鍵。
3.1 ConstraintSet(約束集)
ConstraintSet定義了動畫的關鍵狀態,通常是開始狀態和結束狀態。每個ConstraintSet詳細描述了在該狀態下,每個視圖的位置、大小、透明度等所有屬性。
在開始的ConstrainSet中,我們可以不對View進行約束,會自動繼承佈局文件中定義的初始約束。但是在<ConstraintSet android:id="@+id/end"> </ConstraintSet>中必須完整寫出View的寬高以及約束等屬性
<ConstraintSet android:id="@+id/start">
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/button"
android:layout_width="64dp"
android:layout_height="64dp"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>
3.2 Transition(過渡)
Transition定義了動畫的過程,指定了從哪個ConstraintSet開始,過渡到哪個ConstraintSet結束
<Transition
motion:constraintSetStart="@id/start"
motion:constraintSetEnd="@+id/end"
motion:duration="1000">
<!-- 觸發條件 -->
</Transition>
關鍵屬性:
constraintSetStart:起始狀態ConstraintSet的IDconstraintSetEnd:結束狀態ConstraintSet的IDduration:動畫持續時間(毫秒)
KeyFrame(關鍵幀)
我們已經知道ConstraintSet描述了Start、End狀態,那麼動畫過渡的路徑可以是直線,也可以是任意曲線,而這就需要通過KeyFrameSet關鍵幀來設置了。keyFrameSet是MotionLayout中用於定義動畫關鍵幀的容器,它包含多個關鍵幀類型,每種類型可以控制不同的屬性或觸發不同的事件,主要看下面位置和屬性兩種關鍵幀
- KeyPosition(位置關鍵幀)
KeyPosition用於定義視圖在動畫路徑上的位置和大小變化,允許通過百分比指定位置、大小、角度等屬性。常用屬性:
framePosition:一個介於 0 到 100之間的整數,這個數值直接對應動畫完成的百分比motionTarget:目標視圖IDpercentX/percentY:位置百分比(0.0-1.0)keyPositionType:座標系類型(parentRelative、pathRelative、deltaRelative)
- KeyAttribute(屬性關鍵幀)
KeyPosition用於在動畫的特定時刻動態修改UI元素屬性,如透明度、旋轉、縮放、平移等。常用屬性:
android:alpha:透明度android:rotation:旋轉角度android:scaleX/scaleY:縮放比例android:translationX/Y:平移距離
再瞭解一下關鍵幀的三種座標系類型
- parentRelative(父容器相對座標系),以父容器的左上角為原點(0,0),右下角為(1,1),這是最常用的座標系
- deltaRelative(相對偏移座標系),以起始點為原點(0,0),結束點為(1,1),基於相對於起始點和結束點的相對偏移量來計算路徑
- pathRelative(路徑相對座標系),基於運動路徑的百分比位置,從起點連接到終點的方向是X軸,對應座標分別是(0,0)和(1,0)
下面的實戰會演示關鍵幀,這裏就不多贅述了。
3.3 觸發機制
Transition中的觸發機制讓動畫與用户交互聯繫起來,一般就是點擊觸發或者滑動觸發。
OnClick(點擊觸發)
<OnClick
motion:targetId="@id/button"
motion:clickAction="toggle" />
clickAction常用值:
toggle:在開始和結束狀態之間來回切換。無論當前處於何種狀態,點擊後都會向相反狀態過渡transitionToStart/End:過渡到開始或結束狀態。如果當前已是目標狀態,則點擊無效jumpToStart/End:直接跳轉到開始/結束狀態(無動畫)
OnSwipe(滑動觸發)
<OnSwipe
motion:touchAnchorId="@id/button"
motion:touchAnchorSide="right"
motion:dragDirection="dragRight" />
關鍵屬性:
touchAnchorId:拖拽錨點視圖dragDirection:拖拽方向(dragLeft/Right/Up/Down)touchAnchorSide:拖拽的錨點邊
四、實戰使用
瞭解了上面的基本概念後,我們就可以開始基本的使用了,
<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutDescription="@xml/activity_main_scene"
tools:context=".MainActivity">
<View
android:id="@+id/view"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@drawable/closer"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/light_yellow"
android:text="這是一行文本"
android:textColor="@color/black"
android:textSize="25sp"
app:layout_constraintEnd_toEndOf="@id/view"
app:layout_constraintStart_toStartOf="@id/view"
app:layout_constraintTop_toBottomOf="@id/view" />
</androidx.constraintlayout.motion.widget.MotionLayout>
這是活動對應的佈局,和約束佈局基本沒有什麼區別,下面是對應的MotionScene
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto">
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@id/start"
motion:duration="1000">
<OnClick motion:clickAction="transitionToEnd"
motion:targetId="@id/text"/>
<KeyFrameSet>
</KeyFrameSet>
</Transition>
<ConstraintSet android:id="@+id/start">
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintBottom_toTopOf="@id/view"
motion:layout_constraintEnd_toEndOf="@id/view"
motion:layout_constraintStart_toStartOf="@id/view"/>
</ConstraintSet>
</MotionScene>
這樣一個最簡單的MotionLayout就成型了,我們實際上只在MotionScene進行了動畫的設置,完全不需要在活動或者碎片中進行任何操作。在觸發機制中設置了TextView的onClick,並且用motion:clickAction="transitionToEnd"規定動畫僅觸發第一次點擊然後移動到終點,後續不會再進行動畫。最後的效果如下。
我們還可以結合關鍵幀,豐富這個動畫的效果。在上面KeyFrameSet中加上下面三個關鍵幀
<KeyFrameSet>
<!-- 位置關鍵幀:在一半進度時稍微往右上偏一點 -->
<KeyPosition
motion:motionTarget="@id/text"
motion:framePosition="50"
motion:keyPositionType="deltaRelative"
motion:percentX="0.8"
motion:percentY="0.0"/>
<!-- 屬性關鍵幀:在一半進度時放大 1.5 倍 -->
<KeyAttribute
motion:motionTarget="@id/text"
motion:framePosition="50"
android:scaleX="1.5"
android:scaleY="1.5"/>
<!-- 屬性關鍵幀:在結尾時透明度變為 0.2 -->
<KeyAttribute
motion:motionTarget="@id/text"
motion:framePosition="100"
android:alpha="0.2"/>
</KeyFrameSet>
最後的效果如下
總結
MotionLayout只需要定義開始狀態 (start) 和結束狀態 (end) 的佈局約束(ConstraintSet),以及它們之間的過渡(Transition)。MotionLayout 會自動計算並執行從一個狀態到另一個狀態的平滑過渡,避免了傳統動畫 尤其是屬性動畫手動操作一個個視圖的具體屬性(如 x, y, rotation)的缺點,這對於複雜的 UI 狀態切換來説,代碼量大且難以維護。同時所有的動畫和交互邏輯都定義在一個單獨的 MotionScene XML 文件中,使得代碼更清晰,Activity/Fragment 中的代碼可以專注於業務邏輯,而不是動畫細節。因此在現代 Android 開發中,除了一些極簡單的動畫,更多時候推薦使用MotionLayout