在 MotionLayout 中定義運動路徑
介紹
MotionLayout 是一個來自 ConstraintLayout 2.0 的專注於動畫的新佈局。本系列的前幾篇文章對該系統進行了很好的概述。我強烈建議你在閱讀本文前先去檢視它們。
- Introduction to MotionLayout (part I)
- 中文點這 Custom attributes, image transitions, keyframes (part II)
- 中文點這 Taking advantage of MotionLayout in your existing layouts (CoordinatorLayout, DrawerLayout, ViewPager) (part III)
MotionLayout 動畫系統通過在兩種狀態之間插入值(通常是控制元件的位置/大小)來工作,這些值是使用 ConstraintLayout 的約束系統 (ConstranitSets) 以及檢視屬性來指定的。這兩種狀態之間的轉換也可以完全由觸控事件驅動。這個系統通常會為你的過渡提供很好的效果。
除了上面說的狀態之外,MotionLayout 還支援關鍵幀(在本系列的第二部分中簡單介紹過),我們將在本文中深入介紹這些關鍵幀。注意,雖然關鍵幀很好,但是它絕對是一個更專業的工具;你可能不需要或者偶爾才會用到。
請記住,在應用中新增的動畫應該有它的意義;不要濫用!
但是,如果需要對你的過渡效果新增額外的功能,那麼關鍵幀可以幫助你擴充套件 MotionLayout 的功能。如你所見,這裡有很多內容需要覆蓋:
- 關鍵幀 Keyframes
- 位置關鍵幀 Position Keyframes
- 曲線動作 Arc Motion
- 時間模型 Easing
- 屬性關鍵幀 Attributes Keyframes
- 迴圈關鍵幀 Cycle Keyframes & TimeCycle Keyframes (which we will cover in part V)
上手關鍵幀(a Rendez-vous in Time)
從較高的層次上看,關鍵幀可以對你的兩個狀態之間的插值進行一個修改。
MotionLayout 支援不同的關鍵幀:
- 位置關鍵幀 Position keyframe :
KeyPosition
- 屬性關鍵幀 Attribute keyframe :
KeyAttribute
- 迴圈關鍵幀 Cycle keyframe :
KeyCycle
- 週期關鍵整 TimeCycle keyframe :
KeyTimeCycle
注意,每種型別的關鍵幀都是獨立於其他型別的關鍵幀的——也就是說,你不需要在相同的點上定義所有的關鍵幀(但是你不能在相同的點上定義相同型別的多個關鍵幀)
通用屬性
所有關鍵幀(位置、屬性、迴圈、週期)都有一些關鍵的通用屬性:
- 節點
motion:framePosition
: 關鍵幀在過渡中(從0到100)的作用時機 - 目標
motion:target
: 哪個物件受該關鍵幀影響 - 插值器
motion:transitionEasing
: 使用哪種插值器(預設為線性) - 曲線擬合
motion:curveFit
: 樣條(預設)或線形——使用哪個曲線擬合關鍵幀。預設情況下是單調樣條曲線,這使得過渡更加平滑,當然你也可以決定使用線性 (linear) 擬合。
位置關鍵幀
位置關鍵幀可能是你最常使用到通用關鍵幀。它允許你修改控制元件在過渡期間在螢幕上的路徑。舉例,讓我們在 MotionLayout 中為其中的一個控制元件做動畫:
我們有一個起始狀態(左下)和結束狀態(右上),過渡過程就是控制元件在這兩種狀態之間的線性 (linear interpoltion) 直線運動。
通過引入位置關鍵幀,我們可以將運動路徑變成曲線運動:
新增更多的關鍵幀允許你建立複雜的運動路徑。
<KeyFrameSet>
<KeyPosition
motion:keyPositionType="pathRelative"
motion:percentX="0.75"
motion:percentY="-0.3"
motion:framePosition="25"
motion:target="@id/button"/>
<KeyPosition
motion:keyPositionType="pathRelative"
motion:percentY="-0.4"
motion:framePosition="50"
motion:target="@id/button"/>
<KeyPosition
motion:keyPositionType="pathRelative"
motion:percentX="0.25"
motion:percentY="-0.3"
motion:framePosition="75"
motion:target="@id/button"/>
</KeyFrameSet>
複製程式碼
什麼是位置關鍵幀?
如果 ConstraintSets 已經允許你以非常靈活的方式擺放控制元件,那麼你也許會問自己定位關鍵幀的意義是什麼。原因如下:
- 關鍵幀表示臨時修改,而 ConstraintSets 表示“靜止 (resting) ”狀態
- 關鍵幀在計算中相對於 ConstraintSet 更加輕量級
- 位置關鍵幀允許你對一個控制元件的運動路徑進行操縱 —— ConstraintSets 則是指定一個控制元件相對與其他控制元件的位置。
注意:在一個 MotionScene 中定義多個 ConstraintSets 是有可能的,所以如果你有一個多步驟的動作,其中這些步驟是有效的“靜止”狀態,那麼你可以使用它們而不是關鍵幀。狀態到狀態的轉換必須在程式碼中完成(可以使用改動監聽器(change listeners))。
使用 XML 表示
關鍵幀存在於 <KeyFrameSet>
屬性中,<KeyFrameSet>
則存在於 MotionScene 檔案中的 <Transition>
,並且至少包含:
target
: 被關鍵幀影響的控制元件framePosition
: 關鍵幀使用時機,(0-100)keyPositionType
: 所使用的座標系相對父容器(parentRelative), 三角定位(deltaRelative), 相對路徑(pathRelative)
percentX / percentY
:位置的 (x,y) 座標
<Transition ...>
<KeyFrameSet>
<KeyPosition
motion:keyPositionType="parentRelative"
motion:percentY="0.25"
motion:framePosition="50"
motion:target="@+id/button"/>
</KeyFrameSet>
</Transition>
複製程式碼
不同的座標系
在 MotionLayout 中的起始狀態和結束狀態允許複雜的定位。對於 ConstraintSets,它們可以使用 ConstraintLayout 的所有功能。系統將根據密度 (density) 、螢幕方向(screen orientation)、語言(language) 等變化,正確的處理這些狀態。
要使關鍵幀在這樣的系統中發揮作用,我們需要它們能夠以類似自適應的方式進行佈局——而不是簡單的使用固定的位置。
為了解決這個問題,同時保持關鍵幀系統的輕量級,我們提出了一種靈活的方法——在給定的座標系中,每個關鍵幀的位置用(x,y)座標對 (pair) 表示:
motion:percentX=”<float>”
motion:percentY=”<float>”
這個座標的含義取決於所使用的座標系型別:parentRelative
, deltaRelative
, or pathRelative
.
注意:每個關鍵幀的位置都是單獨存在——每個關鍵幀的位置都可以用它們自己相對的座標系表示。
相對父容器(parentRelative)
座標是根據相對父容器表示的。這是一種非常直接和直觀的方式來表達關鍵幀的位置,通常就足夠了。通常情況下,你用它來做與父容器相關的大範圍運動。
由於這個座標系只基於父容器維度,而不是移動的控制元件的開始/結束位置,您可能會遇到這樣的情況,即最後的關鍵幀位置以次優位置結束(相對於開始/結束位置)。(原文: you may encounter situations where the resulting keyframe position ends in a suboptimal position (relative to the start/end positions).)
三角定位(deltaRelative)
第二個座標系通過使用開始/結束位置定義來解決這個問題。座標表示起點和終點之間的百分比。
和相對父容器座標系有點像,這是一個相對直觀的座標系統,一般也會給出很好的結果。當你希望控制元件以水平或垂直運動開始或結束時,它十分有用。
它有一個潛在的問題——因為它是根據控制元件從開始到結束位置之間的差異定義的額,如果差異非常小(或者沒有)關鍵幀將不會在對應軸上發生變化。例如,如果控制元件在螢幕從左向右移動,而保持在相同的高度,那麼對位置關鍵幀使用 deltarelative
percentY
將不會產生任何效果。
相對路徑(pathRelative)
最後一個座標系定義了一個相對於從開始狀態到結束狀態的直線路徑。它可以解決 deltaRelative 座標系中的問題——當一個控制元件沒有在垂直軸移動的情況下,使用 pathRelative 將允許將位置關鍵幀設定為偏離路徑。注意,它也支援負座標。它是一個更特殊的座標系,但是在處理時間上特別有用。下面有一個例子是實現一個曲線形狀(比如“S”形),即使端點發生變化,它也會保持不變。
Arc Motion
在 Material Design 中使用的一種典型的運動型別是圓弧運動(arc motion)。使用 MotionLayout 建立圓弧運動的一種方法是在起始位置和結束位置之間新增正確放置的位置關鍵幀,如前一節所述。
在 ConstraintLayout 2.0.0 alpha 2 中,我們引入了一種實現完美圓弧運動的新方案——而且它更加容易使用。你只需要將motion:pathMotionArc
屬性新增到起始的 ConstraintSet ,從而讓預設的線形運動 (linear motion) 切換到弧線運動 (arc motion) 。
讓我們來看一個簡單的例子,開始狀態是螢幕的右下,結束的位置是螢幕的頂部並且水平居中。新增下面這個屬性就可以產生弧線運動:
motion:pathMotionArc=”startHorizontal”
如果把屬性換成:
motion:pathMotionArc=”startVertical”
就會改變弧線的方向:
你仍然可以使用位置關鍵幀來創造更復雜的弧線路徑。下面是結果:
它是通過在動畫中新增一個垂直居中的位置關鍵幀來實現的:
<KeyPosition
motion:keyPositionType="parentRelative"
motion:percentY="0.5"
motion:framePosition="50"
motion:target="@id/button"/>
複製程式碼
通過設定 motion:pathMotionArc
屬性,還可以在該場景中使用關鍵幀來更改圓弧的方向。屬性可以是flip
(翻轉當前的圓弧方向)、none
(還原為線性運動),也可以是startHorizontal
或startVertical
。
<KeyPosition
motion:keyPositionType="parentRelative"
motion:pathMotionArc="flip"
motion:percentY="0.5"
motion:framePosition="50"
motion:target="@id/button"/>
複製程式碼
<KeyPosition
motion:keyPositionType="parentRelative"
motion:pathMotionArc="none"
motion:percentY="0.5"
motion:framePosition="50"
motion:target="@id/button"/>
複製程式碼
時間模型(Easing)
在前幾節中,我們介紹了各種機制幫助你定義一個運動路徑。對於一個動畫,不僅僅需要選擇合適的路徑;時間也是至關重要的。
由於位置關鍵幀可以由時間指定,你可以使用它們來定義控制元件移動的快慢,具體取決於移動的空間。
但是在一個單獨片段內——開始和結束狀態之間,或者在關鍵幀之間——時間插值器是線形的。(the time interpolation is linear)
你可以使用motion:transitionEasing
屬性來指定一個緩和曲線來修改它。你可以將這個屬性使用在ConstraintSets
或 關鍵幀,它接受這些值:
cubic(float, float , float, float)
, x1,y1,x2,y2 表示一個從 0,0 到 1,1 的三次貝塞爾方程的控制點- 或使用關鍵字:
standard
,accelerate
,decelerate
, 預定義的曲線類似 Material Design definitions.
標準曲線(Standard easing)
通常用於在非觸控驅動的動畫中。它最適合於開始和結束都是靜止狀態的元素。
加速曲線(Accelerate easing)
加速通常用於一個元素移出螢幕。
減速曲線(Decelerate easing)
減速通常用於一個元素進入螢幕。
鍵屬性(KeyAttribute)
屬性關鍵幀允許你在動畫過程中指定控制元件的屬性在給定時間點的更改——換句話說,它們與位置關鍵幀類似,但作用於屬性而不是位置。
上面的例子通過在 MotionScene 檔案中新增 KeyAttribute 元素來實現:
<KeyFrameSet>
<KeyAttribute
android:scaleX="2"
android:scaleY="2"
android:rotation="-45"
motion:framePosition="50"
motion:target="@id/button" />
</KeyFrameSet>
複製程式碼
相比於KeyPostion
,我們需要指定framePosition
(關鍵幀應用時機)和目標(哪個物件受到影響)。
支援的屬性
一些你開箱即用的 View 屬性:
android:visibility
, android:alpha
, android:elevation
, android:rotation
, android:rotationX
, android:rotationY
, android:scaleX
, android:scaleY
, android:translationX
, android:translationY
, android:translationZ
重點
受到應用程式的 SDK level 限制,其中一些屬性將不起作用:
- SDK 21 引入的
android:elevation
- SDK 21 引入的
android:translationZ
自定義屬性(Custom Attributes)
你可以通過新增 <CustionAttribute>
子節點在 ConstraintSets 和 KeyAttribute 節點中宣告自定義屬性。這個節點需要一個屬性名(attributeName
),它是getter/setter
的名稱(除去set/get字首)和要傳入或使用的值型別,屬性型別指定為下方其中一個:
customColorValue
: 顏色值customColorDrawableValue
: 顏色值 DrawablecustomIntegerValue
: IntegercustomFloatValue
: FloatcustomStringValue
: StringcustomDimension
: 尺寸customBoolean
: Boolean
舉例,這面是對應上面動畫的 XML:
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/button" ...>
<CustomAttribute
motion:attributeName="backgroundColor"
motion:customColorValue="#D81B60"/>
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/button" ...>
<CustomAttribute
motion:attributeName="backgroundColor"
motion:customColorValue="#9999FF"/>
</Constraint>
</ConstraintSet>
複製程式碼
總結
本文介紹了 MotionLayout 中 最常見的關鍵幀和路徑規範。我們將在本系列的第五部分討論 迴圈(KeyCycle)和 週期(KeyTimeCycle),它們介紹了一種非常強大的方法,可以將擾動(類似波形)新增到屬性(基於路徑或基於時間),允許各種有趣但可預測的迴圈效果(反彈(bounce)、抖動(shaking)、脈動(pulsations)等)。
使用 MotionLayout 的各種示例可以在 ConstraintLayout examples github repository檢視。