MotionLayout 是一种布局类型,可帮助您在应用中管理运动和微件动画。MotionLayout 是 ConstraintLayout 的子类,并构建在其强大的布局功能之上。作为 ConstraintLayout 库的一部分,MotionLayout 可作为支持库使用。
MotionLayout 弥补了布局转换与复杂运动处理之间的鸿沟,其功能结合了属性动画框架、TransitionManager 和 CoordinatorLayout 的特性。
除了描述布局之间的转换外,MotionLayout 还允许您为任何布局属性设置动画。此外,它还原生支持可搜索的转换 (seekable transitions)。这意味着您可以根据某些条件(如触摸输入)即时显示转换过程中的任意点。MotionLayout 还支持关键帧,能够实现完全自定义的转换以满足您的需求。
MotionLayout 是完全声明式的,这意味着无论转换多么复杂,您都可以在 XML 中进行描述。
设计注意事项
MotionLayout 旨在移动、调整大小以及为用户与之交互的 UI 元素(例如按钮和标题栏)设置动画。请勿在应用中滥用运动特效。请利用它来帮助用户理解您的应用正在执行的操作。有关使用运动设计应用的更多信息,请参阅 Material Design 部分的理解运动 (Understanding motion)。
开始使用
请按照以下步骤开始在项目中使用 MotionLayout。
-
添加
ConstraintLayout依赖项:要在项目中使用MotionLayout,请将ConstraintLayout2.0 依赖项添加到您应用的build.gradle文件中。如果您使用的是 AndroidX,请添加以下依赖项Groovy
dependencies { implementation "androidx.constraintlayout:constraintlayout:2.2.1" // To use constraintlayout in compose implementation "androidx.constraintlayout:constraintlayout-compose:1.1.1" }
Kotlin
dependencies { implementation("androidx.constraintlayout:constraintlayout:2.2.1") // To use constraintlayout in compose implementation("androidx.constraintlayout:constraintlayout-compose:1.1.1") }
-
创建
MotionLayout文件:MotionLayout是ConstraintLayout的子类,因此,您可以通过替换布局资源文件中的类名,将任何现有的ConstraintLayout转换为MotionLayout,如下例所示AndroidX
<!-- before: ConstraintLayout --> <androidx.constraintlayout.widget.ConstraintLayout .../> <!-- after: MotionLayout --> <androidx.constraintlayout.motion.widget.MotionLayout .../>
支持库
<!-- before: ConstraintLayout --> <android.support.constraint.ConstraintLayout .../> <!-- after: MotionLayout --> <android.support.constraint.motion.MotionLayout .../>
以下是一个
MotionLayout文件的完整示例,它定义了图 1 中所示的布局AndroidX
<?xml version="1.0" encoding="utf-8"?> <!-- activity_main.xml --> <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:id="@+id/motionLayout" android:layout_width="match_parent" android:layout_height="match_parent" app:layoutDescription="@xml/scene_01" tools:showPaths="true"> <View android:id="@+id/button" android:layout_width="64dp" android:layout_height="64dp" android:background="@color/colorAccent" android:text="Button" /> </androidx.constraintlayout.motion.widget.MotionLayout>
支持库
<?xml version="1.0" encoding="utf-8"?> <!-- activity_main.xml --> <android.support.constraint.motion.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:id="@+id/motionLayout" android:layout_width="match_parent" android:layout_height="match_parent" app:layoutDescription="@xml/scene_01" tools:showPaths="true"> <View android:id="@+id/button" android:layout_width="64dp" android:layout_height="64dp" android:background="@color/colorAccent" android:text="Button" /> </android.support.constraint.motion.MotionLayout>
-
创建 MotionScene:在前面的
MotionLayout示例中,app:layoutDescription属性引用了一个运动场景 (motion scene)。运动场景是一个 XML 资源文件。在它的<MotionScene>根元素内,运动场景包含了相应布局的所有运动描述。为了将布局信息与运动描述分开,每个MotionLayout都会引用一个单独的运动场景。运动场景中的定义优先于MotionLayout中的任何类似定义。以下是一个示例运动场景文件,描述了图 1 中的基本水平运动
<?xml version="1.0" encoding="utf-8"?> <MotionScene xmlns:android="http://schemas.android.com/apk/res/android" xmlns:motion="http://schemas.android.com/apk/res-auto"> <Transition motion:constraintSetStart="@+id/start" motion:constraintSetEnd="@+id/end" motion:duration="1000"> <OnSwipe motion:touchAnchorId="@+id/button" motion:touchAnchorSide="right" motion:dragDirection="dragRight" /> </Transition> <ConstraintSet android:id="@+id/start"> <Constraint android:id="@+id/button" android:layout_width="64dp" android:layout_height="64dp" android:layout_marginStart="8dp" motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintTop_toTopOf="parent" /> </ConstraintSet> <ConstraintSet android:id="@+id/end"> <Constraint android:id="@+id/button" android:layout_width="64dp" android:layout_height="64dp" android:layout_marginEnd="8dp" motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintEnd_toEndOf="parent" motion:layout_constraintTop_toTopOf="parent" /> </ConstraintSet> </MotionScene>
请注意以下内容
-
<Transition>包含了运动的基本定义。-
motion:constraintSetStart和motion:constraintSetEnd是指向运动终点的引用。这些终点在运动场景后面的<ConstraintSet>元素中定义。 -
motion:duration指定运动完成所需的毫秒数。
-
-
<OnSwipe>允许您为运动创建触摸控制。-
motion:touchAnchorId指的是用户可以滑动和拖动的视图。 -
motion:touchAnchorSide表示视图是从右侧被拖动的。 -
motion:dragDirection指的是拖动的进度方向。例如,motion:dragDirection="dragRight"表示随着视图向右拖动,进度会增加。
-
-
<ConstraintSet>是定义描述运动的各种约束的地方。在此示例中,为运动的每个终点定义了一个<ConstraintSet>。这些终点使用app:layout_constraintTop_toTopOf="parent"和app:layout_constraintBottom_toBottomOf="parent"在垂直方向上居中。在水平方向上,这些终点位于屏幕的最左侧和最右侧。
有关运动场景支持的各种元素的详细介绍,请参阅 MotionLayout 示例。
-
插值属性
在运动场景文件中,ConstraintSet 元素可以包含在转换期间进行插值的其他属性。除了位置和边界外,MotionLayout 还会对以下属性进行插值
alpha (透明度)visibility (可见性)elevation (高度)rotation,rotationX,rotationY(旋转)translationX,translationY,translationZ(平移)scaleX,scaleY(缩放)
自定义属性
在 <Constraint> 中,您可以使用 <CustomAttribute> 元素为那些与位置或 View 属性不直接相关的属性指定转换。
<Constraint android:id="@+id/button" ...> <CustomAttribute motion:attributeName="backgroundColor" motion:customColorValue="#D81B60"/> </Constraint>
<CustomAttribute> 本身包含两个属性
motion:attributeName是必需的,必须与具有 getter 和 setter 方法的对象匹配。getter 和 setter 必须遵循特定的模式。例如,支持backgroundColor,因为视图具有底层的getBackgroundColor()和setBackgroundColor()方法。- 您必须提供的另一个属性取决于值类型。请从以下支持的类型中选择
motion:customColorValue(颜色)motion:customIntegerValue(整数)motion:customFloatValue(浮点数)motion:customStringValue(字符串)motion:customDimension(尺寸)motion:customBoolean(布尔值)
指定自定义属性时,请在起始和结束的 <ConstraintSet> 元素中定义终点值。
更改背景颜色
基于上一个示例,假设您希望视图的颜色在运动过程中发生变化,如图 2 所示。
向每个 ConstraintSet 元素添加 <CustomAttribute> 元素,如下面的代码片段所示
<ConstraintSet android:id="@+id/start"> <Constraint android:id="@+id/button" android:layout_width="64dp" android:layout_height="64dp" android:layout_marginStart="8dp" motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintTop_toTopOf="parent"> <CustomAttribute motion:attributeName="backgroundColor" motion:customColorValue="#D81B60" /> </Constraint> </ConstraintSet> <ConstraintSet android:id="@+id/end"> <Constraint android:id="@+id/button" android:layout_width="64dp" android:layout_height="64dp" android:layout_marginEnd="8dp" motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintEnd_toEndOf="parent" motion:layout_constraintTop_toTopOf="parent"> <CustomAttribute motion:attributeName="backgroundColor" motion:customColorValue="#9999FF" /> </Constraint> </ConstraintSet>
其他 MotionLayout 属性
除了上述示例中的属性外,MotionLayout 还有其他您可能需要指定的属性
app:applyMotionScene="boolean"指示是否应用运动场景。此属性的默认值为true。app:showPaths="boolean"指示在运行运动时是否显示运动路径。此属性的默认值为false。app:progress="float"允许您明确指定转换进度。您可以使用从0(转换开始)到1(转换结束)之间的任何浮点值。app:currentState="reference"允许您指定特定的ConstraintSet。app:motionDebug允许您显示有关运动的其他调试信息。可能的值为"SHOW_PROGRESS"、"SHOW_PATH"或"SHOW_ALL"。
其他资源
有关 MotionLayout 的更多信息,请参阅以下资源
- Kotlin 高级 Android 开发 03.2:使用 MotionLayout 进行动画设计
- MotionLayout 示例
- GitHub 上的 MotionLayout/ConstraintLayout 示例
- MotionLayout 简介(第 I 部分)
- MotionLayout 简介(第 II 部分)
- MotionLayout 简介(第 III 部分)
- MotionLayout 简介(第 IV 部分)