1、概述
I / O '18提到了MotionLayout,當(dāng)時還沒有正式發(fā)布前段時間,在今年的6月26日正式發(fā)布了ConstraintLayout的2.0alpha版,也算正式推出了MotionLayout。 MotionLayout是ConstraintLayout的子類,它具有ConstraintLayout的所有屬性。MotionLayout用來處理兩個ConstraintSet之間的切換,并在根據(jù)兩個ConstraintSet的CustomAttribute參數(shù)來自動生成切換動畫,關(guān)于ConstraintSet下面會討論。同時MotionLayout所增加的是可以直接通過觸摸屏幕來控制動畫的運(yùn)行進(jìn)度。也就是說MotionLayout會管理你的觸摸事件通過跟蹤手指的速度,并將其與系統(tǒng)中的視圖速度相匹配。從而可以自然地在兩者之間通過觸摸滑動平穩(wěn)過渡。并且在動畫里面加入了關(guān)鍵幀的概念,使得其自動生成動畫在運(yùn)行時某一階段會運(yùn)行到關(guān)鍵幀的狀態(tài)。同時MotionLayout支持在XML中完全描述一個復(fù)雜的動畫,而不需要通過Java代碼來實(shí)現(xiàn)。
2、ConstraintLayout動畫
ConstraintLayout中的動畫要借助于ConstraintSet。ConstraintSet是一個輕量級對象,表示ConstraintLayout中所有子元素的constraints,margins和padding 。當(dāng)將 ConstraintSet應(yīng)用于顯示ConstraintLayout時,布局會使用ConstraintSet中的約束來更新對應(yīng)ConstraintLayout中的。ConstraintSet僅為視圖的大小和位置設(shè)置動畫,不會為其他屬性設(shè)置動畫(例如顏色)。
下面的代碼示例顯示了如何動畫將單個按鈕移動到屏幕底部:
public class MainActivity extends AppCompatActivity {
ConstraintLayout constraintLayout;
Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.keyframe_one);
constraintLayout = findViewById(R.id.constraint_layout);
button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
animateToKeyframeTwo();
}
});
}
void animateToKeyframeTwo() {
ConstraintSet constraintSet = new ConstraintSet();
constraintSet.load(this, R.layout.keyframe_two); //載入要更新的布局到constraintSet中
TransitionManager.beginDelayedTransition(constraintLayout); // 開啟
constraintSet.applyTo(constraintLayout);
}
}
這邊要注意一下,只會運(yùn)行R.layout.keyframe_two中與R.layout.keyframe_one中id對應(yīng)的動畫。不會出現(xiàn)R.layout.keyframe_two中有而R.layout.keyframe_one中沒有的視圖,也不會對R.layout.keyframe_one有但R.layout.keyframe_two中沒有的視圖有任何效果。
3、MotionLayout動畫
前面已經(jīng)演示了怎么對ConstraintLayout布局設(shè)置動畫,現(xiàn)在來討論下MotionLayout布局下的動畫。
3.1 MotionScene
與通常的布局不同,MotionLayout所做的約束保存在一個單獨(dú)的XML文件MotionScene中,該文件存儲在您的res/xml目錄中。
MotionScene文件可以包含指定動畫所需的全部內(nèi)容,例如前面提到的ConstraintSets、ConstraintSets直接的過渡、關(guān)鍵幀、觸摸處理等等。
3.2 創(chuàng)建動畫簡單流程
3.2.1 先決條件
① Android Studio 3.2.0或更高版本
② 運(yùn)行Android API等級21或更高版本的設(shè)備或模擬器
③ 添加依賴:
implementation 'com.android.support:appcompat-v7:27.0.2'
implementation 'com.android.support.constraint:constraint-layout:2.0.0-alpha1'
或者
implementation 'androidx.appcompat:appcompat:1.0.0-beta1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha1'
這邊要說明一下androidx也是今年IO剛剛推出的一個依賴,用來替代之前的com.android.support依賴。添加了它就不用添加一大堆v4、v7、v13等依賴了。
3.2.2 定義布局
前面提到過MotionLayout是ConstraintLayout的子類,所以MotionLayout可以直接替換ConstraintLayout。因?yàn)镃onstraintLayout有的功能MotionLayout都有。
3.2.3 創(chuàng)建MotionScene
這一步是MotionLayout的關(guān)鍵,在res下的xml文件夾中創(chuàng)建MotionScene。其實(shí)在MotionLayout中可以不用添加想進(jìn)行動畫的視圖的約束,而將約束放在ConstraintSet中,在將ConstraintSet放在MotionScene中。
<MotionScene
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ConstraintSet android:id="@+id/starting_set">
<Constraint android:id="@+id/actor"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_width="60dp"
android:layout_height="60dp"/>
</ConstraintSet >
<ConstraintSet android:id="@+id/ending_set" >
<Constraint android:id="@+id/actor"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_width="60dp"
android:layout_height="60dp"/>
</ConstraintSet >
</MotionScene>
這邊需要注意的是每個ConstraintSet 里面的元素必須始終指定所需的位置和所需的大小。它會覆蓋任何以前設(shè)置的布局信息。并且里面的id和MotionLayout中的視圖的id要對應(yīng)才會有反應(yīng)。
為了幫助MotionLayout 的視圖理解必須約束集的順序,需要創(chuàng)建一個Transition 元素。通過使用其直觀命名 constraintSetStart 和constraintSetEnd 屬性,可以指定首先應(yīng)用哪個集合以及最后應(yīng)用哪個集合。該Transition 元素還允許指定動畫的持續(xù)時間。
// 放在上面的<MotionScene> 和</MotionScene>之中和ConstraintSet 標(biāo)簽平級。
<Transition
android:id="@+id/my_transition"
app:constraintSetStart="@+id/starting_set"
app:constraintSetEnd="@+id/ending_set"
app:duration="2000"/>
此時,一個簡單的MotionScene完成。但是此時任然沒有和MotionLayout進(jìn)行綁定。需要給MotionLayout添加app:layoutDescription屬性來將上面的MotionScene綁定:
app:layoutDescription="@xml/my_scene"
3.2.4 啟動動畫
運(yùn)行應(yīng)用程序時,MotionLayout 視圖將自動將constraintSetStart 屬性中指定的約束集設(shè)置到自己身上。因此,要啟動動畫,需要做的就是調(diào)用transitionToEnd() 方法從而實(shí)現(xiàn)ConstraintSet之間的轉(zhuǎn)換:
motion_container.transitionToEnd();
3.2.5 動畫執(zhí)行進(jìn)度監(jiān)聽
可以通過給MotionLayout 設(shè)置監(jiān)聽器來監(jiān)聽動畫進(jìn)度,和動畫完成時的回調(diào):
motionLayout.setTransitionListener(new MotionLayout.TransitionListener() {
@Override
public void onTransitionChange(MotionLayout motionLayout, int i, int i1, float v) {
seekBar.setProgress((int)(v*100));
}
@Override
public void onTransitionCompleted(MotionLayout motionLayout, int i) {
}
});
上面對進(jìn)度的監(jiān)聽通過seekbar表示出來
3.3 關(guān)鍵幀(Key Frames)
在上面的動畫中,Button小部件看起來像在直線的路徑中移動。這是因?yàn)镸otionLayout 的視圖此時其實(shí)只有兩個關(guān)鍵幀:起始幀Button位于屏幕的右下角,終點(diǎn)幀Button位于屏幕的左上角。如果要改變路徑的形狀,則必須提供一些介于起點(diǎn)和終點(diǎn)之間關(guān)鍵。
在開始創(chuàng)建關(guān)鍵幀之前,必須將KeyFrameSet 標(biāo)簽添加到MotionScene之中??梢宰杂蓜?chuàng)建任意數(shù)量的關(guān)鍵幀。
<KeyFrameSet >
...
</KeyFrameSet >
MotionLayout 視圖支持許多不同類型的關(guān)鍵幀。這里使用其中兩種類型:KeyPosition 和KeyCycle 。
3.3.1 KeyPosition
KeyPosition 可以幫助視圖改變運(yùn)動路徑的形狀。創(chuàng)建它們時,請確保提供目標(biāo)視圖的ID,沿時間軸的位置,可以是0到100之間的任意數(shù)字,以及指定X或Y坐標(biāo)已經(jīng)運(yùn)行到的百分比??梢栽O(shè)置type參數(shù)指出坐標(biāo)是相對于實(shí)際的X或Y軸,還是相對于路徑本身。
<KeyFrameSet >
<KeyPosition
app:target="@+id/button"
app:framePosition="30"
app:type="deltaRelative"
app:percentX="0.85"/>
<KeyPosition
app:target="@+id/button"
app:framePosition="60"
app:type="deltaRelative"
app:percentX="1"/>
</KeyFrameSet>
上面第一個KeyPosition代表button按鈕在運(yùn)行道30%的時候,相對于運(yùn)行軌跡x已經(jīng)運(yùn)行了85%了。第二個KeyPosition代表button按鈕在運(yùn)行道60%的時候,相對于運(yùn)行軌跡x已經(jīng)運(yùn)行了100%了.效果如下,這樣就可以避開和seekbar的沖突了:
3.3.2 KeyCycle
KeyCycle用來給動畫添加振動??梢酝ㄟ^提供諸如要使用的波形和波形周期等詳細(xì)信息來配置KeyCycle。下面是KeyCycle支持的各種振動波形:
在上述動畫中加入如下KeyCycle
<KeyCycle
app:target="@+id/button"
app:framePosition="30"
android:rotation="50"
app:waveShape="sin"
app:wavePeriod="1"/>
3.4 交互式動畫
上面的動畫運(yùn)行我都是通過對Button按鈕設(shè)置點(diǎn)擊監(jiān)聽事件,然后調(diào)用motion_container.transitionToEnd();方法來使他運(yùn)行的。其實(shí)完全不必這么麻煩,因?yàn)镸otionLayout的視圖允許開發(fā)者將觸摸事件直接附加到視圖中。截止到現(xiàn)在,它支持點(diǎn)擊和滑動事件。要實(shí)現(xiàn)上面實(shí)現(xiàn)的點(diǎn)擊事件可以在MotionScene中增加代碼如下:
<OnClick
app:target="@+id/button"
app:mode="transitionToEnd"/>
而可以通過給MotionScene增加OnSwipe標(biāo)簽來使視圖通過在屏幕滑動而大運(yùn)行。在創(chuàng)建該標(biāo)簽時,必須確保提供正確的拖動方向以及應(yīng)作為拖動控制柄的視圖的邊??梢赃@么理解,相對于初始位置,如果想往上滑起到增加動畫進(jìn)度就設(shè)置為dragUp,想往下滑起到增加動畫進(jìn)度就設(shè)置為dragDown,左右同樣道理。至于touchAnchorSide這個參數(shù)的本意應(yīng)該設(shè)置拉目標(biāo)視圖的邊,但我發(fā)現(xiàn)就算不設(shè)置touchAnchorSide這個參數(shù)或者設(shè)置成任意值top bottom或者left right,對動畫都沒有影響。這可能是MotionLayout的一個bug畢竟現(xiàn)在還只是alpha版。
<OnSwipe
app:touchAnchorId="@+id/actor"
app:dragDirection="dragUp"/>
5、MotionEditor
之前一篇討論ConstraintLayout的文章,基本上都是在布局編輯器中進(jìn)行操作。這也是ConstraintLayout的一大優(yōu)點(diǎn),MotionLayout作為其子類,官方也為它專門提供了強(qiáng)大的可視化編輯器。不過可惜的是,到目前為止還不能使用,下面是MotionEditor的官方預(yù)告片的一個節(jié)選: