MotionLayout 基础教程
閱讀說明:
- 本文假設讀者已掌握如何使用 ConstraintLayout。
 - 本文是一篇 MotionLayout 基礎教程,如您已了解如何使用 MotionLayout,本文可能對您幫助不大。
 - 本教程共有兩篇文章,這是第一篇,另一篇請點擊 這里。
 - 建議讀者跟隨本文一起動手操作,如您現在不方便,建議稍后閱讀。
 - 本文基于 ConstraintLayout 2.0.0-alpha4 版本編寫,建議讀者優先使用這一版本。
 - 由于 MotionLayout 官方文檔不全,有些知識點是根據筆者自己的理解總結的,如有錯誤,歡迎指正。
 
添加支持庫:
dependencies {...implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha4' } 復制代碼MotionLayout 最低支持到 Android 4.3(API 18),還有就是 MotionLayout 是 ConstraintLayout 2.0 添加的,因此必須確保支持庫的版本不低于 2.0。
簡介
MotionLayout 類繼承自 ConstraintLayout 類,允許你為各種狀態之間的布局設置過渡動畫。由于 MotionLayout 繼承了 ConstraintLayout,因此可以直接在 XML 布局文件中使用 MotionLayout 替換 ConstraintLayout。
MotionLayout 是完全聲明式的,你可以完全在 XML 文件中描述一個復雜的過渡動畫而 無需任何代碼(如果您打算使用代碼創建過渡動畫,那建議您優先使用屬性動畫,而不是 MotionLayout)。
開始使用
由于 MotionLayout 類繼承自 ConstraintLayout 類,因此可以在布局中使用 MotionLayout 替換掉 ConstraintLayout。
MotionLayout 與 ConstraintLayout 不同的是,MotionLayout 需要鏈接到一個 MotionScene 文件。使用 MotionLayout 的 app:layoutDescription 屬性將 MotionLayout 鏈接到一個 MotionScene 文件。
例:
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/scene_01"><ImageViewandroid:id="@+id/image"android:layout_width="48dp"android:layout_height="48dp"android:src="@mipmap/ic_launcher"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /></androidx.constraintlayout.motion.widget.MotionLayout> 復制代碼注意!必須為 MotionLayout 布局的所有直接子 View 都設置一個 Id(允許不為非直接子 View 設置 Id)。
創建 MotionScene 文件
MotionScene 文件描述了兩個場景間的過渡動畫,存放在 res/xml 目錄下。
要使用 MotionLayout 創建過渡動畫,你需要創建兩個 layout 布局文件來描述兩個不同場景的屬性。當從一個場景切換到另一個場景時,MotionLayout 框架會自動檢測這兩個場景中具有相同 id 的 View 的屬性差別,然后針對這些差別屬性應用過渡動畫(類似于 TransitionManger)。
MotionLayout 框架支持的標準屬性:
- android:visibility
 - android:alpha
 - android:elevation
 - android:rotation
 - android:rotationX
 - android:rotationY
 - android:scaleX
 - android:scaleY
 - android:translationX
 - android:translationY
 - android:translationZ
 
MationLayout 除了支持上面列出的標準屬性外,還支持全部的 ConstraintLayout 屬性。
下面來看一個完整的例子,這個例子分為以下 3 步。
第 1 步:創建場景 1 的布局文件:
文件名:activity_main_scene1.xml
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"android:layout_width="match_parent"android:layout_height="match_parent"android:id="@+id/motionLayout"app:layoutDescription="@xml/activity_main_motion_scene"><ImageViewandroid:id="@+id/image"android:layout_width="48dp"android:layout_height="48dp"android:src="@mipmap/ic_launcher"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /></androidx.constraintlayout.motion.widget.MotionLayout> 復制代碼場景 1 的布局預覽如下圖所示:
第 2 步:創建場景 2 的布局文件:
文件名:activity_main_scene2.xml
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"android:id="@+id/motionLayout"android:layout_width="match_parent"android:layout_height="match_parent"app:layoutDescription="@xml/activity_main_motion_scene"><ImageViewandroid:id="@+id/image"android:layout_width="48dp"android:layout_height="48dp"android:src="@mipmap/ic_launcher"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /></androidx.constraintlayout.motion.widget.MotionLayout> 復制代碼場景 2 的布局預覽如下圖所示:
說明:場景 1 與場景 2 中都有一個 id 值為 image 的 ImageView,它們的差別是:場景 1 中的 image 是水平垂直居中放置的,而場景 2 中的 image 是水平居中,垂直對齊到父布局頂部的。因此當從場景 1 切換到場景 2 時,MotionLayout 將針對 image 的位置差別自動應用位移過渡動畫。
第 3 步:創建 MotionScene 文件:
文件名:activity_main_motion_scene.xml,存放在 res/xml 目錄下
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"><Transitionapp:constraintSetStart="@layout/activity_main_scene1"app:constraintSetEnd="@layout/activity_main_scene2"app:duration="1000"><OnClickapp:clickAction="toggle"app:targetId="@id/image" /></Transition></MotionScene> 復制代碼編寫完 MotionLayout 文件后就可以直接運行程序了。點擊 image 即可進行場景切換。當進行場景切換時,MotionLayout 會自動計算出兩個場景之間的差別,然后應用相應的過渡動畫。
下面對 MotionLayout 文件進行說明:
如上例所示,MotionScene 文件的根元素是 <MotionScene>。在 <MotionScene> 元素中使用 <Transition> 子元素來描述一個過渡,使用 <Transition> 元素的 app:constraintSetStart 屬性指定起始場景的布局文件,使用 app:constraintSetEnd 指定結束場景的布局文件。在 <Transition> 元素中使用 <OnClick> 或者 <OnSwip> 子元素來描述過渡的觸發條件。
<Transition> 元素的屬性:
- app:constraintSetStart:設置為起始場景的布局文件 Id。
 - app:constraintSetEnd:設置為結束場景的布局文件 Id。
 - app:duration:過渡動畫的持續時間。
 - app:motionInterpolator:過渡動畫的插值器。共有以下 6 個可選值: 
- linear:線性
 - easeIn:緩入
 - easeOut:緩出
 - easeInOut:緩入緩出
 - bounce:彈簧
 - anticipate:(功能未知,沒有找到文檔)
 
 - app:staggered:【浮點類型】(功能未知,沒有找到文檔)
 
可以在 <Transition> 元素中使用一個 <OnClick> 或者 <OnSwipe> 子元素來描述過渡的觸發條件。
<OnClick> 元素的屬性:
- app:targetId:【id 值】設置用來觸發過渡的那個 View 的 Id(例如:@id/image 或 @+id/image)。
 
提示:app:targetId 的值的前綴既可以是 @+id/ 也可以是 @id/,兩者都可以。官方示例中使用的是 @+id/。不過,使用 @id/ 前綴似乎更加符合語義,因為 @+id/ 前綴在布局中常用來創建一個新的 Id,而 @id/ 前綴則常用來引用其他的 Id 值。為了突出這里引用的是其他的 Id 而不是新建了一個 Id,使用 @id/ 前綴要更加符合語義。
- app:clickAction:設置點擊時執行的動作。該屬性共有以下 5 個可選的值: 
- toggle:在 Start 場景和 End 場景之間循環的切換。
 - transitionToEnd:過渡到 End 場景。
 - transitionToStart:過渡到 Start 場景。
 - jumpToEnd:跳到 End 場景(不執行過渡動畫)。
 - jumpToStart:跳到 Start 場景(不執行過渡動畫)。
 
 
<OnSwipe> 元素的屬性:
- app:touchAnchorId:【id 值】設置拖動操作要關聯到的對象,讓觸摸操作看起來像是在拖動這個對象的由 app:touchAnchorSide 屬性指定的那個邊。
 - app:touchAnchorSide:設置觸摸操作將會拖動對象的哪一邊,共有以下 4 個可選值: 
- top
 - left
 - right
 - bottom
 
 - app:dragDirection:設置拖動的方向(注意,只有設置了 app:touchAnchorId 屬性后該屬性才有效)。共有以下 4 個可選值: 
- dragUp:手指從下往上拖動(↑)。
 - dragDown:手指從上往下拖動(↓)。
 - dragLeft:手指從右往左拖動(←)。
 - dragRight:手指從左往右拖動(→)。
 
 - app:maxVelocity:【浮點值】設置動畫在拖動時的最大速度(單位:像素每秒 px/s)。
 - app:maxAcceleration:【浮點值】設置動畫在拖動時的最大加速度(單位:像素每二次方秒 px/s^2)。
 
可以同時設置 <OnClick> 與 <OnSwipe> ,或者都不設置,而是使用代碼來觸發過渡。
還可以在 <Transition> 元素中設置多個 <OnClick>,每個 <OnClick> 都可以關聯到一個不同的控件上。雖然 <Transition> 元素中也可以設置多個 <OnSwipe>,但是后面的 <OnSwipe> 會替換掉前面的 <OnSwipe>,最終使用的是最后一個 <OnSwipe>。
<OnSwipe> 拖動操作
由于 <OnSwipe> 拖動操作涉及的交互較為復雜,這里單獨對它的以下 3 個屬性進行說明:
- app:touchAnchorId
 - app:dragDirection
 - app:touchAnchorSide
 
首先是 app:touchAnchorId 屬性與 app:dragDirection 屬性。app:touchAnchorId 屬性用于設置拖動操作要關聯到的對象;app:dragDirection 屬性用于指定拖動方向。
默認情況下,由上往下 拖動時會運行過渡動畫,此時 <OnSwipe/> 元素不需要設置任何屬性,只要在 <Transition> 中加一個 <OnSwipe/> 標簽即可。
例:
<Transition...><OnSwipe/></Transition> 復制代碼但是,如果你要支持 由下往上(↑)或者 由左往右(→)或者 由右往左(←),那么至少應該設置好 app:touchAnchorId 與 app:dragDirection 屬性。
app:dragDirection 屬性設置的拖動方向與 app:touchAnchorId 屬性關聯到的對象在 Start 場景和 End 場景中的位置是息息相關的。例如下圖 a 中,End 場景中的 Widget 位于 Start 場景中的 Widget 的上方,那么應該設置 app:dragDirection="dragUp"。再看圖 b 中,End 場景中的 Widget 位于 Start 場景中的 Widget 的右邊,那么應該設置 app:dragDirection="dragRight":
如果 End 場景中的 Widget 相對于 Start 場景中的 Widget 是傾斜的(如下圖所示),將會有兩個可選的方向,下圖中的可選方向是 dragUp、dragRight,具體使用哪個方向,由你自己決定。
設置一個正確的拖動方向是非常重要的,否則拖動時,過渡動畫將表現不佳。
提示:MotionLayout 將使用 app:touchAnchorId 關聯到的對象在 app:dragDirection 方向上的拖動進度(progress)作為整個過渡動畫的進度,當關聯對象在 app:dragDirection 方向上的拖動完成時,也就意味著整個過渡動畫完成了。
例:實現拖動效果
刪除 <Transition> 元素元素的 <OnClick> 標簽,并加入一個 <OnSwipe> 標簽,修改后的 MotionScene 文件內容如下所示:
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"><Transitionapp:constraintSetStart="@layout/activity_main_scene1"app:constraintSetEnd="@layout/activity_main_scene2"app:duration="1000"><!-- 刪除 OnClick,加入 OnSwipe --><OnSwipeapp:touchAnchorId="@id/image"app:dragDirection="dragUp"/></Transition></MotionScene> 復制代碼注意:如果將 <OnClick> 和 <OnSwipe> 關聯到了同一個控件,或者 <OnSwipe> 關聯到的那個控件是可點擊的,點擊事件將會影響到拖動,你將無法按住控件進行拖動,只能按住控件的外面才能拖動。
例:
在 <Transition> 標簽中加入 <OnClick>,修改后的 MotionScene 文件內容如下所示:
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"><Transitionapp:constraintSetStart="@layout/activity_main_scene1"app:constraintSetEnd="@layout/activity_main_scene2"app:duration="1000"><OnSwipeapp:touchAnchorId="@id/image"app:dragDirection="dragUp"/><!-- 加入 OnClick --><OnClickapp:targetId="@id/image"app:clickAction="toggle"/></Transition></MotionScene> 復制代碼效果如下所示:
app:touchAnchorSide 屬性:
app:touchAnchorSide 屬性的功能是 “設置觸摸操作將會拖動對象的哪一邊”,該屬性可用于實現可折疊效果,例如可折疊標題欄。
例:在底部實現一個向上拉的折疊效果。
1. 修改 acticity_main_scene1.xml 文件:
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"android:layout_width="match_parent"android:layout_height="match_parent"android:id="@+id/motionLayout"app:layoutDescription="@xml/activity_main_motion_scene"><ImageViewandroid:id="@+id/image"android:layout_width="48dp"android:layout_height="48dp"android:src="@mipmap/ic_launcher"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /><!-- 增加以下代碼 --><FrameLayoutandroid:id="@+id/bottomBar"android:layout_width="match_parent"android:layout_height="0dp"android:background="@color/colorPrimary"app:layout_constraintTop_toBottomOf="parent"app:layout_constraintBottom_toBottomOf="parent"><ImageViewandroid:layout_gravity="center"android:src="@mipmap/ic_launcher"android:layout_width="wrap_content"android:layout_height="wrap_content"/></FrameLayout></androidx.constraintlayout.motion.widget.MotionLayout> 復制代碼2. 修改 acticity_main_scene2.xml 文件:
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"android:id="@+id/motionLayout"android:layout_width="match_parent"android:layout_height="match_parent"app:layoutDescription="@xml/activity_main_motion_scene"><ImageViewandroid:id="@+id/image"android:layout_width="48dp"android:layout_height="48dp"android:src="@mipmap/ic_launcher"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /><!-- 增加以下代碼 --><FrameLayoutandroid:id="@+id/bottomBar"android:layout_width="match_parent"android:layout_height="120dp"android:background="@color/colorPrimary"app:layout_constraintBottom_toBottomOf="parent"><ImageViewandroid:layout_gravity="center"android:src="@mipmap/ic_launcher"android:layout_width="wrap_content"android:layout_height="wrap_content"/></FrameLayout></androidx.constraintlayout.motion.widget.MotionLayout> 復制代碼3. 修改 MotionScene 文件(文件名:activity_main_motion_scene.xml)
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"><Transitionapp:constraintSetStart="@layout/activity_main_scene1"app:constraintSetEnd="@layout/activity_main_scene2"app:duration="1000"><!-- 關聯到 bottomBar 上--><OnSwipeapp:touchAnchorId="@id/bottomBar"app:touchAnchorSide="top"app:dragDirection="dragUp"/><OnClickapp:targetId="@id/image"app:clickAction="toggle"/></Transition></MotionScene> 復制代碼效果如下所示:
提示:其實 <OnSwipe> 可以不關聯到 bottomBar 上,在因為在前面的例子中我們已經把 <OnSwipe> 關聯到了 image 上,且拖動方向也設置正確(drageUp),這樣其實已經可以正常拖動了。但是由于 bottomBar 是可折疊的,把 <OnSwipe> 拖動關聯到它上面更加合適,這樣可以設置 app:touchAnchorSide="top",告訴 MotionLayout 控件 bottomBar 的上邊界是可拖動的,這樣更符合語義。
使用代碼觸發過渡動畫
除了使用 <OnClick> 元素與 <OnSwipe> 元素來設置觸發過渡動畫的觸發條件外,還可以使用代碼來手動觸發過渡動畫。
下面對場景 1 的布局文件進行修改,在布局中添加 2 個按鈕,預覽如下圖所示:
場景 1 修改后的布局文件內容為:
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"android:id="@+id/motionLayout"android:layout_width="match_parent"android:layout_height="match_parent"app:layoutDescription="@xml/activity_main_motion_scene"><ImageViewandroid:id="@+id/image"android:layout_width="48dp"android:layout_height="48dp"android:src="@mipmap/ic_launcher"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /><Buttonandroid:id="@+id/btnToStartScene"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginBottom="16dp"android:text="To Start Scene"android:textAllCaps="false"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toLeftOf="@id/btnToEndScene" /><Buttonandroid:id="@+id/btnToEndScene"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginBottom="16dp"android:text="To End Scene"android:textAllCaps="false"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toRightOf="@id/btnToStartScene"app:layout_constraintRight_toRightOf="parent" /></androidx.constraintlayout.motion.widget.MotionLayout> 復制代碼場景 2 的布局文件不需要修改。
在 MainActivity 中添加如下代碼來手動執行過渡動畫:
public class MainActivity extends AppCompatActivity {private MotionLayout mMotionLayout;private Button btnToStartScene;private Button btnToEndScene;protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main_scene1);mMotionLayout = findViewById(R.id.motionLayout);btnToStartScene = findViewById(R.id.btnToStartScene);btnToEndScene = findViewById(R.id.btnToEndScene);btnToStartScene.setOnClickListener(new View.OnClickListener() {public void onClick(View v) {// 切換到 Start 場景mMotionLayout.transitionToStart();}});btnToEndScene.setOnClickListener(new View.OnClickListener() {public void onClick(View v) {// 切換到 End 場景mMotionLayout.transitionToEnd();}});} } 復制代碼如上面代碼中所示,調用 MotionLayout 的 transitionToStart() 方法可以切換到 Start 場景,調用 MotionLayout 的 transitionToStart() 方法可以切換到 End 場景。
效果如下所示:
調整過渡動畫的進度
MotionLayout 還支持手動調整過渡動畫的播放進度。使用 MotionLayout 的 setProgress(float pos) 方法(pos 參數的取值范圍為 [0.0 ~ 1.0])來調整過渡動畫的播放進度。
下面對場景 1 的布局文件進行修改,移除兩個按鈕,加入一個 SeekBar,修改后的布局代碼如下所示:
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"android:id="@+id/motionLayout"android:layout_width="match_parent"android:layout_height="match_parent"app:layoutDescription="@xml/activity_main_motion_scene"><ImageViewandroid:id="@+id/image"android:layout_width="48dp"android:layout_height="48dp"android:src="@mipmap/ic_launcher"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent" /><SeekBarandroid:id="@+id/seekBar"android:layout_width="240dp"android:layout_height="wrap_content"android:layout_marginBottom="56dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent" /></androidx.constraintlayout.motion.widget.MotionLayout> 復制代碼布局預覽如下圖所示:
修改 MainActivity 中的代碼:
public class MainActivity extends AppCompatActivity {private MotionLayout mMotionLayout;private SeekBar mSeekBar;protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main_scene1);mMotionLayout = findViewById(R.id.motionLayout);mSeekBar = findViewById(R.id.seekBar);mSeekBar.setMax(0);mSeekBar.setMax(100);mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {mMotionLayout.setProgress((float) (progress * 0.01));}public void onStartTrackingTouch(SeekBar seekBar) {}public void onStopTrackingTouch(SeekBar seekBar) {}});} } 復制代碼效果如下圖所示:
監聽 MotionLayout 過渡
可以調用 MotionLayout 的 setTransitionListener() 方法向 MotionLayout 對象注冊一個過渡動畫監聽器,這個監聽器可以監聽過渡動畫的播放進度和結束事件。
public void setTransitionListener(MotionLayout.TransitionListener listener) 復制代碼TransitionListener 監聽器接口:
public interface TransitionListener {// 過渡動畫正在運行時調用void onTransitionChange(MotionLayout motionLayout, int startId, int endId, float progress);// 過渡動畫結束時調用void onTransitionCompleted(MotionLayout motionLayout, int currentId); } 復制代碼提示:TransitionListener 接口在 alpha 版本中有所改動,可多出了 2 個回調方法:onTransitionStarted 和 onTransitionTrigger。由于 MotionLayout 還處于 alpha 版本,并未正式發布,因此有所改動也是正常。
例:
MotionLayout motionLayout = findViewById(R.id.motionLayout); motionLayout.setTransitionListener(new MotionLayout.TransitionListener() {public void onTransitionChange(MotionLayout motionLayout, int i, int i1, float v) {Log.d("App", "onTransitionChange: " + v);}public void onTransitionCompleted(MotionLayout motionLayout, int i) {Log.d("App", "onTransitionCompleted");} }); 復制代碼結語
本篇文章到此就結束了,你可能會覺得前面的例子不夠炫酷,這里給出一個炫酷點的例子(這個例子很簡單,建議讀者動手嘗試實現一下):
后續文章:
- 《MotionLayout 基礎教程 2》
 
參考文章:
- https://www.jianshu.com/p/5203cf11d943
 
轉載于:https://juejin.im/post/5cadc1ba6fb9a0686e40bb82
總結
以上是生活随笔為你收集整理的MotionLayout 基础教程的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: 和平精英雪地防空洞位置 和平到底是什么
 - 下一篇: 咪咕音乐怎么导入网易云(咪咕视频体育频道