MotionLayout 是一個 Google 官方出品用於製做 Android 中的過渡動畫的框架。用來它就能輕鬆的作出一些較爲複雜的動畫效果。php
因爲 MotionLayout 是基於 ConstraintLayout ,因此其中涉及到了部分關於 ConstraintLayout 的基本知識,本文按下不表,對 ConstraintLayout 不熟悉的同窗,能夠查看鴻洋的這篇博客。html
MotionLayout 是 ConstraintLayout 的子類,而且在 ConstraintLayout 發展到 2.0 時才加入 ConstraintLayout 這個庫,本文所使用的依賴爲:android
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta2'
複製代碼
接下來讓咱們進入正題,先來看看我用 MotionLayout 製做的一個 Demo。git
在這個例子中,當點擊 Login 按鈕時,Login 按鈕的長度進行不斷縮小,縮小到必定尺寸時,外層的 ProgressBar 仍是逐漸由不可見變爲可見,同時,Login 按鈕上的字進行了淡入淡出的動畫效果。github
MotionLayout 能作的不只如此,它還能作到其餘更爲好玩有趣的過渡動畫。如今讓咱們來學一下吧。app
過渡動畫,顧名思義就是在狀態之間進行過渡的動畫效果,防止頁面內 View 出現瞬間移動的效果。而 MotionLayout 的重點其實就是狀態。開發者只須要定義好對應狀態下 View 的相對位置,以及相關屬性,其後 MotionLayout 便會自動爲其增長動的效果。框架
這樣的一個最簡單的效果是怎麼作出來的呢?ide
首先咱們須要在資源文件夾 res
下新建一個名爲 xml 的資源文件夾,而後再 xml 文件夾內新建一個根節點是 MotionScene
的 xml 文件,demo 中這個 xml 的文件名爲 login_animator。佈局
如下就是實現 Login 按鈕長度變換的過渡動畫。學習
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
<Transition app:constraintSetEnd="@id/a_login_end" app:constraintSetStart="@id/a_login_start" app:duration="1000">
<OnClick app:clickAction="toggle" app:targetId="@id/tv_action_login" />
</Transition>
<ConstraintSet android:id="@+id/a_login_start">
<Constraint android:id="@+id/tv_action_login">
<Layout android:layout_width="match_parent" android:layout_height="48dp" android:layout_marginTop="30dp" android:layout_marginStart="30dp" android:layout_marginEnd="30dp" app:layout_constraintTop_toBottomOf="@id/et_passwd" />
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="@+id/a_login_end">
<Constraint android:id="@+id/tv_action_login">
<Layout android:layout_width="48dp" android:layout_height="48dp" android:layout_marginTop="30dp" app:layout_constraintEnd_toEndOf="@+id/et_account" app:layout_constraintStart_toStartOf="@+id/et_account" app:layout_constraintTop_toBottomOf="@id/et_passwd" />
</Constraint>
</ConstraintSet>
</MotionScene>
複製代碼
仔細看其中的信息,其中大部分是咱們都熟悉的,無非就是對 View 的相對位置的約定或是 View 自身屬性的規定,少部分是關於過渡動畫的。
咱們先來看看這個文件的總體結構,首先根節點是 MotionScene ,MotionScene 節點下有一個 Transition 與兩個 ConstraintSet 節點,並且 Transition 中有兩個屬性,一個是 constraintSetStart 另外一個是 constraintSetEnd,這兩個屬性的值正好是兩個 ConstraintSet 節點的 id,而 Transition 內子節點 OnClick 節點內的屬性 targetId 則代表了當前 Transition 所指定的動畫是做用於具體的 View 上。
如你所想,經過在 Transition 內指定某個 View 的兩個狀態下的不一樣屬性,就能產生在這兩個狀態內的過渡動畫,而且在 Translation 內經過組合不一樣的動畫事件進行顯示。好比點擊產生的動畫(OnClick),滑動產生的動畫(OnSwipe),以及可改變某一幀動畫效果的關鍵幀動畫(KeyFrameSet)。
當咱們把初始及結束狀態下的屬性及動畫定義完成後,還須要回到咱們的佈局文件,將須要實現過渡動畫的 View 的父佈局改成 MotionLayout 而且給它添加一個值爲剛纔咱們新建那個 xml 文件的引用的屬性 layoutDescription。
<?xml version="1.0" encoding="utf-8"?>
<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:layout_width="match_parent" android:layout_height="match_parent" app:layoutDescription="@xml/login_animator">
......
</androidx.constraintlayout.motion.widget.MotionLayout>
複製代碼
這就是一個最爲簡單的使用 MotionLayout 實現過渡動畫的例子,它與開頭我本身寫的那個 demo 沒什麼差異,無非就是 demo 中變換的 View 的個數及屬性多少不一樣而已。
在這個例子中,咱們經過在 Transition 中定義了一個 OnClick 的子節點,而達到點擊產生動畫的效果。其中,targetId 即爲產生動畫效果的目標 View 的 id;clickAction 則是指明在是在開始或是再結束狀態時產生動畫,toggle 表示在開始和結束狀態時均有效,它還有 transitionToStart 和 transitionToEnd 表示只在開始或是結束狀態下有效。有興趣的能夠去試試。
除了 OnClick,咱們還能夠在 Translation 中定義 OnSwipe 節點,OnSwipe 就是用來處理屏幕上的滑動事件,以此配合指定的 View 實現過渡動畫的效果。
給 MotionLayout 添加 motionDebug="SHOW_PATH" 這個屬性,便可查看 View 的過渡動畫的軌跡。
經過指定 View 的開始狀態(靠近屏幕左邊)和結束狀態(靠近屏幕右邊),而後在 Translation 中聲明出滑動事件,便可。
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
<Transition app:constraintSetEnd="@id/v_swipe_end" app:constraintSetStart="@id/v_swipe_start" app:duration="1000">
<OnSwipe app:dragDirection="dragRight" app:touchRegionId="@id/v_swipe" />
</Transition>
<ConstraintSet android:id="@+id/v_swipe_start">
<Constraint android:id="@+id/v_swipe">
<Layout android:layout_width="48dp" android:layout_height="48dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" />
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="@+id/v_swipe_end">
<Constraint android:id="@+id/v_swipe">
<Layout android:layout_width="48dp" android:layout_height="48dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" />
</Constraint>
</ConstraintSet>
</MotionScene>
複製代碼
在 OnSwipe 中,有兩個屬性,一個是 dragDirection 表明的是滑動的方向,touchRegionId 則指明瞭監聽的滑動區域爲 View 的滑動區域。既然能做用於 View 的滑動區域,是否是也能做用於整個屏幕的滑動區域呢?沒錯,touchAnchorId 則表示所有的滑動區域。OnSwipe 還有一些其餘屬性,好比:touchAnchorSide 表示監聽 View 的哪一個區域的滑動監聽,若是不設置的話,是 View 外的全部區域;onTouchUp 表示當在滑動過程當中手指擡起時動畫的動做(回到開始狀態、回到結束狀態、自動完成、中止等等)。
說實話,我在開始嘗試 MotionLayout 的時候被 OnSwipe 給嚇到了,可是當我更進一步的使用 KeyFrameSet 的時候直喊 666。緣由就是由於 KeyFrameSet 能作出更炫酷的效果。
KeyFrameSet 是做用於在過渡動畫過程當中的關鍵幀,經過指定動畫關鍵進程時的狀態來實現不一樣的效果。舉個例子,當前的 View 滑動是一條直線,我想讓在滑動過程當中有一個先向上滑動,而後向下滑動以這種效果達到屏幕的最右側。
View 的開始與結束狀態沒有發生改變,只是在過渡動畫的中點區域進行改變 View 的座標。
<Transition app:constraintSetEnd="@id/v_swipe_end" app:constraintSetStart="@id/v_swipe_start" app:duration="1000">
<OnSwipe app:dragDirection="dragRight" app:touchAnchorId="@id/v_swipe" app:touchAnchorSide="bottom" />
<KeyFrameSet>
<KeyPosition app:framePosition="50" app:keyPositionType="parentRelative" app:motionTarget="@+id/v_swipe" app:percentY="0.3" />
</KeyFrameSet>
</Transition>
複製代碼
framePosition 表示在運動到整個運動過程的 50% 處,這個值的取值範圍是 0 - 100,motionTarget 表示做用的 View,而 keyPositionType 與percentY 則共同決定了運動軌跡中弧度的變化方向。keyPositionType 控制 percentY 的座標系的工做方式,它一共有 3 個值。parentRelative、deltaRelative、pathRelative。percentY 取值範圍爲 0 - 1,同時容許負數及大於 1 的值。
parentRelative 表示,座標按照父佈局的座標進行處理,X,Y 軸的最大值均爲1,X 軸向右爲正,向左爲負,Y 軸向下爲正,向上爲負。
deltaRelative 表示開始狀態的中心點爲座標系原點,X,Y 軸的最大值均爲1,X 軸向右爲正,向左爲負,Y 軸向下爲負,向上爲正。
pathRelative 表示開始狀態的中心點爲座標系原點,X 軸爲兩個狀中心點的構成的直線。X,Y 軸的最大值均爲1,X 軸向結束狀態方向爲正,向開始狀態方向爲負,Y 軸向下爲負,向上爲正。
keyPositionType 三個屬性的描述圖均來自 CodeLab
KeyPostition 還有些其餘有趣的屬性,好比,控制運動軌跡是平滑的曲線仍是直線的 curveFit,以及 transitionEasing 控制運動過程的加速或是減速等等。這裏就不一一舉例了。
並且,還能夠同時存在多個關鍵幀進行控制動畫效果。
<KeyFrameSet>
<KeyPosition app:framePosition="50" app:keyPositionType="parentRelative" app:motionTarget="@+id/v_swipe" app:percentY="0.3" />
<KeyAttribute android:alpha="0" app:framePosition="50" app:motionTarget="@+id/v_swipe" />
</KeyFrameSet>
複製代碼
keyAttribute 是用於在過渡動畫中控制 View 的屬性,好比在動畫執行 50% 時,View 的 alpha 值爲 0 ,那麼在從 0 - 50% 及 50% - 100% 的過程當中,則由 MotionLayout 根據其執行時間自動改變 View 的狀態。
剛纔聊的都是關於動畫自己的內容,實際上,MotionLayout 提供更多方式來對 View 進行狀態改變,不僅是經過在 ConstraintSet 中指定 Layout 來改變 View 的相對位置,它還提供了更爲豐富的方法進行改變 View 的狀態,好比:
Motion 用於改變更畫效果,例如加速、減速、先水平方法仍是先垂直方向進行移動
CustomAttribute 用於改變自定義屬性;
PropertySet 用於改變 View 特定的幾個屬性;
Transform 用於改變 View 中涉及到屬性動畫的屬性,例如:rotation、scaleX 等。用法也很簡單,像 Layout 那樣聲明出來便可。
<ConstraintSet android:id="@+id/v_swipe_start">
<Constraint android:id="@+id/v_swipe">
<CustomAttribute app:attributeName="backgroundColor" app:customColorValue="@color/colorAccent" />
<Transform android:scaleX="1.0" android:scaleY="1.0" />
<Layout android:layout_width="48dp" android:layout_height="48dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" />
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="@+id/v_swipe_end">
<Constraint android:id="@+id/v_swipe">
<CustomAttribute app:attributeName="backgroundColor" app:customColorValue="@color/colorPrimary" />
<Transform android:scaleX="3.0" android:scaleY="3.0" />
<Layout android:layout_width="48dp" android:layout_height="48dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" />
</Constraint>
</ConstraintSet>
複製代碼
因爲放大倍數較大,超出屏幕,因此在結束狀態時顯示存在異常。
怎麼樣,MotionLayout 是否是比想象中的好玩一些,就是如今不太方便調試,每次調試都須要運行,不過呢,如今這個還沒發佈正式版,估計在正式版中 Google 應該會解決這個問題。
本文首發於我的博客,文中所有源代碼已上傳至 GitHub,代碼分支爲:motionLayout。喜歡的麻煩點個🌟。
推薦學習網站:CodeLab。