MotionLayout使用指南
在传统Android开发中,创建流畅的交互式动画往往需要组合使用属性动画、TransitionManager或CoordinatorLayout等多种技术,代码量大且维护困难。Google在ConstraintLayout 2.0中推出的MotionLayout成功解决了这一痛点,它将布局容器与动画描述分离,通过声明式的XML配置即可实现复杂的交互动画MotionLayout兼具了属性动画的灵活性、TransitionManager的布局过渡能力以及CoordinatorLayout的触摸响应特性,成为一个功能强大的全能型动画解决方案。
一、MotionLayout与MotionScene
MotionLayout本身是一个ViewGroup(视图容器),而MotionScene是其核心的"大脑",专门用于定义和控制动画。
MotionLayout实际是ConstraintLayout的子类,它首先是一个布局容器,负责承载界面元素。同时,它也是一个动画引擎,实时解析和执行MotionScene中的指令。
MotionScene :是一个独立的XML资源文件(通常位于res/xml目录),作为动画的"剧本",详细描述动画的各种状态和过渡过程。MotionLayout通过app:layoutDescription属性与MotionScene文件关联。
这种分离设计让动画逻辑与布局容器解耦,大大提高了代码的可维护性和可读性。
二、项目配置与创建方式
2.1 添加依赖
要使用MotionLayout,首先需要在项目的build.gradle文件中添加ConstraintLayout 2.0及以上版本的依赖
arduino
dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.2.0'
}
2.2 创建方式
-
最直接的方式是对布局进行自动转换。在Android Studio中,打开已有的ConstraintLayout布局文件,在Component Tree中右键点击根布局,选择"Convert to MotionLayout"。
这种方式Android Studio会自动将ConstraintLayout替换为MotionLayout,并在
res/xml目录下创建对应的MotionScene文件,同时通过app:layoutDescription属性建立两者的关联关系。

- 也可以自行手动创建,手动修改布局文件的根标签为MotionLayout或者创建文件时指定MotionLayout的根布局,但是这种做法需要我们自己在
res/xml文件下创建对应的MotionScene文件并进行关联。
三、MotionScene的三大核心组件
MotionScene由三个基本组成部分ConstraintSet(约束集) ,Transition(过渡) 和触发机制构成,理解它们是掌握MotionLayout的关键。
3.1 ConstraintSet(约束集)
ConstraintSet定义了动画的关键状态,通常是开始状态和结束状态。每个ConstraintSet详细描述了在该状态下,每个视图的位置、大小、透明度等所有属性。
在开始的ConstrainSet中,我们可以不对View进行约束,会自动继承布局文件中定义的初始约束。但是在<ConstraintSet android:id="@+id/end"> </ConstraintSet>中必须完整写出View的宽高以及约束等属性
ini
<ConstraintSet android:id="@+id/start">
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/button"
android:layout_width="64dp"
android:layout_height="64dp"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>
3.2 Transition(过渡)
Transition定义了动画的过程,指定了从哪个ConstraintSet开始,过渡到哪个ConstraintSet结束
xml
<Transition
motion:constraintSetStart="@id/start"
motion:constraintSetEnd="@+id/end"
motion:duration="1000">
<!-- 触发条件 -->
</Transition>
关键属性:
constraintSetStart:起始状态ConstraintSet的IDconstraintSetEnd:结束状态ConstraintSet的IDduration:动画持续时间(毫秒)
KeyFrame(关键帧)
我们已经知道ConstraintSet描述了Start、End状态,那么动画过渡的路径可以是直线,也可以是任意曲线,而这就需要通过KeyFrameSet关键帧来设置了。keyFrameSet是MotionLayout中用于定义动画关键帧的容器,它包含多个关键帧类型,每种类型可以控制不同的属性或触发不同的事件,主要看下面位置和属性两种关键帧
-
KeyPosition(位置关键帧)
KeyPosition用于定义视图在动画路径上的位置和大小变化,允许通过百分比指定位置、大小、角度等属性。常用属性:framePosition:一个介于 0 到 100之间的整数,这个数值直接对应动画完成的百分比motionTarget:目标视图IDpercentX/percentY:位置百分比(0.0-1.0)keyPositionType:坐标系类型(parentRelative、pathRelative、deltaRelative)
-
KeyAttribute(属性关键帧)
KeyPosition用于在动画的特定时刻动态修改UI元素属性,如透明度、旋转、缩放、平移等。常用属性:android:alpha:透明度android:rotation:旋转角度android:scaleX/scaleY:缩放比例android:translationX/Y:平移距离
再了解一下关键帧的三种坐标系类型
0. parentRelative(父容器相对坐标系),以父容器的左上角为原点(0,0),右下角为(1,1),这是最常用的坐标系- deltaRelative(相对偏移坐标系),以起始点为原点(0,0),结束点为(1,1),基于相对于起始点和结束点的相对偏移量来计算路径
- pathRelative(路径相对坐标系),基于运动路径的百分比位置,从起点连接到终点的方向是X轴,对应坐标分别是(0,0)和(1,0)
下面的实战会演示关键帧,这里就不多赘述了。
3.3 触发机制
Transition中的触发机制让动画与用户交互联系起来,一般就是点击触发或者滑动触发。
OnClick(点击触发)
ini
<OnClick
motion:targetId="@id/button"
motion:clickAction="toggle" />
clickAction常用值:
toggle:在开始 和结束 状态之间来回切换 。无论当前处于何种状态,点击后都会向相反状态过渡transitionToStart/End:过渡到开始或结束 状态。如果当前已是目标状态,则点击无效jumpToStart/End:直接跳转到开始/结束状态(无动画)
OnSwipe(滑动触发)
ini
<OnSwipe
motion:touchAnchorId="@id/button"
motion:touchAnchorSide="right"
motion:dragDirection="dragRight" />
关键属性:
touchAnchorId:拖拽锚点视图dragDirection:拖拽方向(dragLeft/Right/Up/Down)touchAnchorSide:拖拽的锚点边
四、实战使用
了解了上面的基本概念后,我们就可以开始基本的使用了,
ini
<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/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutDescription="@xml/activity_main_scene"
tools:context=".MainActivity">
<View
android:id="@+id/view"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@drawable/closer"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/light_yellow"
android:text="这是一行文本"
android:textColor="@color/black"
android:textSize="25sp"
app:layout_constraintEnd_toEndOf="@id/view"
app:layout_constraintStart_toStartOf="@id/view"
app:layout_constraintTop_toBottomOf="@id/view" />
</androidx.constraintlayout.motion.widget.MotionLayout>
这是活动对应的布局,和约束布局基本没有什么区别,下面是对应的MotionScene
ini
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto">
<Transition
motion:constraintSetEnd="@+id/end"
motion:constraintSetStart="@id/start"
motion:duration="1000">
<OnClick motion:clickAction="transitionToEnd"
motion:targetId="@id/text"/>
<KeyFrameSet>
</KeyFrameSet>
</Transition>
<ConstraintSet android:id="@+id/start">
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
motion:layout_constraintBottom_toTopOf="@id/view"
motion:layout_constraintEnd_toEndOf="@id/view"
motion:layout_constraintStart_toStartOf="@id/view"/>
</ConstraintSet>
</MotionScene>
这样一个最简单的MotionLayout就成型了,我们实际上只在MotionScene进行了动画的设置,完全不需要在活动或者碎片中进行任何操作。在触发机制中设置了TextView的onClick,并且用motion:clickAction="transitionToEnd"规定动画仅触发第一次点击然后移动到终点,后续不会再进行动画。最后的效果如下。

我们还可以结合关键帧,丰富这个动画的效果。在上面KeyFrameSet中加上下面三个关键帧
xml
<KeyFrameSet>
<!-- 位置关键帧:在一半进度时稍微往右上偏一点 -->
<KeyPosition
motion:motionTarget="@id/text"
motion:framePosition="50"
motion:keyPositionType="deltaRelative"
motion:percentX="0.8"
motion:percentY="0.0"/>
<!-- 属性关键帧:在一半进度时放大 1.5 倍 -->
<KeyAttribute
motion:motionTarget="@id/text"
motion:framePosition="50"
android:scaleX="1.5"
android:scaleY="1.5"/>
<!-- 属性关键帧:在结尾时透明度变为 0.2 -->
<KeyAttribute
motion:motionTarget="@id/text"
motion:framePosition="100"
android:alpha="0.2"/>
</KeyFrameSet>
最后的效果如下

总结
MotionLayout 只需要定义开始状态 (start) 和结束状态 (end) 的布局约束(ConstraintSet),以及它们之间的过渡(Transition) 。MotionLayout 会自动计算并执行从一个状态到另一个状态的平滑过渡。避免了传统动画 尤其是属性动画手动操作一个个视图的具体属性(如 x, y, rotation)的缺点,这对于复杂的 UI 状态切换来说,代码量大且难以维护。同时所有的动画和交互逻辑都定义在一个单独的 MotionScene XML 文件中,使得代码更清晰,Activity/Fragment 中的代码可以专注于业务逻辑,而不是动画细节。因此在现代 Android 开发中,除了一些极简单的动画,更多时候推荐使用MotionLayout