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