MotionLayout 其实是一种布局类型,可帮助您管理动画。MotionLayout 是 ConstraintLayout的子类(关于 ConstraintLayout 的介绍可以参考: Android ConstraintLayout从入门到精通),因此, MotionLayout 拥有者 ConstraintLayout 同样强大的布局功能,它是 ConstraintLayout 库的一部分,可向后兼容 API 级别 14,也就是说,只需要引入 ConstraintLayout 的依赖,就可以使用 MotionLayout 了。
MotionLayout 将布局转换与复杂动画处理结合在一起,同时也在属性动画框架、TransitionManager 和 CoordinatorLayout 之间提供了各种功能。
除了描述布局之间的转换之外,MotionLayout 还能够为任何布局属性添加动画效果。此外,它本身就支持可搜索转换。也就是说,可以根据某个条件(例如触控输入)立即显示转换中的任意点。MotionLayout 还支持关键帧,从而实现完全自定义的转换以满足您的需求。
此外,MotionLayout 是完全声明性的,也就是说可以直接使用 XML 描述任何转换,无论复杂程度如何。
注意:MotionLayout 仅适用于为其直接子级添加动画,不支持嵌套布局层次结构或 Activity 转换。
MotionLayout 是 ConstraintLayout 库的一部分,所以,只需要在程序模块的 build.gradle 文件中增加 ConstraintLayout 库的依赖就可以。
// support包引入,如果项目使用其他support包,使用这个 implementation 'com.android.support.constraint:constraint-layout:1.1.3' // androidx包引入,如果项目使用androidx时使用,跟support包引入只能选其一,否则会冲突 implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta7'前面提到,MotionLayout 是 ConstraintLayout 的子类,所以,直接将布局中的 ConstraintLayout 类接替换成 MotionLayout 也是完全可以的。
<?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" xmlns:tools="http://schemas.android.com/tools" tools:showPaths="true"> <View android:id="@+id/btn6" android:layout_width="100dp" android:layout_height="100dp" android:background="@color/colorAccent" android:text="Button6"/> </androidx.constraintlayout.motion.widget.MotionLayout>MotionLayout 是完全声明性的,使用 XML 文件描述任何转换,MotionLayout 的转换描述 XML 文件放在 res/xml 目录下,其中包含相应布局的所有运动描述。为了将布局信息与运动描述分开,每个 MotionLayout 都引用一个单独的 MotionScene。
注意:MotionScene 中的定义的运动描述优先于 MotionLayout 中的任何类似定义。
<?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/btn6" motion:touchAnchorSide="right" motion:dragDirection="dragRight" /> </Transition> <ConstraintSet android:id="@+id/start"> <Constraint android:id="@+id/btn6" 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" motion:layout_constraintVertical_bias="0.5" android:layout_marginLeft="8dp" > <CustomAttribute motion:attributeName="backgroundColor" motion:customColorValue="#D81B60" /> </Constraint> </ConstraintSet> <ConstraintSet android:id="@+id/end"> <Constraint android:id="@+id/btn6" 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="#FFFF00" /> </Constraint> </ConstraintSet> </MotionScene>提示:运动描述文件通过预定义的元素,声明运动的各种状态,关于描述文件的详细内容,请参考:运动描述文件详解
创建运动描述文件之后,需要在布局文件中添加配置。添加运动描述配置,需要在 MotionLayout 跟节点上,通过 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" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:tools="http://schemas.android.com/tools" app:layoutDescription="@xml/scene_01" tools:showPaths="true"> <View android:id="@+id/btn6" android:layout_width="100dp" android:layout_height="100dp" android:background="@color/colorAccent" android:text="Button6"/> </androidx.constraintlayout.motion.widget.MotionLayout> 效果:注意事项:运动描述文件和布局是一一对应的,一个 MotionLayout 需要但对对应一个描述 XML 文件,并且描述文件中的所有控件的 ID 也必须在布局中能找到。
MotionLayout 除了上述的 layoutDescription 属性以外,还有以下属性: 其他 MotionLayout 属性 除了上述示例中的属性之外,MotionLayout 还包含您可能想要指定的其他属性:
applyMotionScene:布尔类型,表示是否应用 MotionScene,默认值为 true。showPaths:布尔类型,表示在运动进行时是否显示运动路径。默认值为 false。progress:float类型,可以明确指定转换进度(取值0.0~1.0)currentState:reference类型,可指定具体的 ConstraintSet。motionDebug:可显示与运动有关的其他调试信息。可取值有:SHOW_PROGRESS、SHOW_PATH或SHOW_ALL。<MotionScene>是运动场景文件的根元素,必须包含一个或多个 <Transition> 元素,<Transition>元素用于定义运动序列的开始和结束状态,以及这两种状态之间的转换。
语法 <MotionScene xmlns:android="http://schemas.android.com/apk/res/android" xmlns:motion="http://schemas.android.com/apk/res-auto" motion:defaultDuration="500"> </MotionScene><ConstraintSet> 元素是用来指定视图在动画序列中某一点上的位置和属性(可同时指定多个视图),也叫约束条件集合。通常,一个 <Transition> 元素可指向两个 <ConstraintSet> 元素,其中一个定义动画序列的开始,另一个定义动画序列的结束。
以上例子中,ID 为 end2 的 ConstraintSet 包含了 ID 为 end 的 ConstraintSet 中所有的约束。如果在 end2 对同一个视图声明约束(Constraint),那么就会替换掉 end 中声明的对应约束。
<Constraint> 元素用来声明运动序列其中一个视图的位置和属性,也就是视图的约束。
该元素是用来声明运动过程中的开始和结束状态,包括所有预期的过度状态、用户的触发的交互等。
该元素用于指定当用户点按特定视图时要执行的操作,用于 <Transition> 元素内部,指定当用户点击视图时触发动画序列,在一个 <Transition> 内部只能有一个 <OnClick> 元素。
说明:官方文档中说一个 <Transition> 元素内部可以有多个 <OnClick> 元素,但是笔者实践过程中发现不可行,会编译报错,不知道是否跟版本有关。
该元素用于指定当用户在不居中滑动时需要执行的操作。动画序列的速度和目标视图的动画,受滑动的速度和方向影响,滑动的速度和方向是通过可选参数配置来控制的。
说明:官方文档中说一个 <Transition> 元素内部可以有多个 <OnSwipe> 元素,但是笔者实践过程中发现不可行,会编译报错,不知道是否跟版本有关。
前面提到,动画的声明是有 “开始状态” 和 “结束状态” 两个点,目标控件在这两个点之间移动,前面的例子都是直线的动画,显然这个实际使用中需要更复杂的动画。<KeyFrameSet> 就是提供声明更加复杂动画的元素,他声明了动画运动轨迹的关键点集合,它可包含 <KeyPosition> 和 <KeyAttribute>,通过这些元素,可以在动画的 “开始状态” 和 “结束状态” 添加 “中间点”,动画将平滑地移动到每个中间点,完成更复杂的动画。
该元素用于指定视图在运动过程中,在特定的时刻的位置。换句话说就是运动轨迹上的关键点的位置。在一个 <KeyFrameSet> 中可以添加多个 <KeyPosition> 用来添加多个不同的关键点位置。
关键点类型详细说明
parentRelative :父容器关系型,即关键点的位置是相对于整个父容器中的相对位置来指定,percentX 和 percentY 分别表示 X 轴和Y轴相对位置,取值为 -1.0~1.0(负数时目标视图将移动到父容器外面)。需要注意的是:关键点的位置是相对父容器来指定,跟 “开始点” 和 “结束点” 位置无关。deltaRelative:三角区域关系型,在目标视图整个运动序列移动的区域组成一个坐标轴,X 为横轴,Y 为纵轴,原点为 “开始点”,视图移动的方向为坐标的正方向。percentX 和 percentY 分别为 X 轴和 Y 轴上的数值,控制关键点的位置,取值范围是 -1.0 ~ 1.0之间,负数表示在坐标轴的负值方向位置。framePosition 一样控制的是轨迹弧顶的位置。pathRelative:路径关系型,即关键点的位置是相对于路径相对指定的,路径是指 “开始点” 和 “结束点” 的直线路径(构成 X 轴),percentX 表示在 X 轴相对位置,0 表示在开始点, 1表示在结束点;percentY 表示垂直于 X 轴(Y 轴)的相对位置,正数在 X 轴左侧,负数在 X 轴右侧。 |percentY 取值范围为-1.0~1.0,Y 轴距离是以 “开始点” 和 “结束点”之间的距离为基数的百分比(也就是说 Y 轴的最大距离就是开始点和结束点直线距离的最大值,如果该值取0,弧度为0,则轨迹为直线)。另外, framePosition 控制的是轨迹弧顶的位置(而不是关键点的位置),大于 50 时偏向起始点,小于50时偏向结束点,弧顶两端轨迹,运动时间是相等的,如果取了不对等的值(非50),就可以实现快慢变化的效果。deltaRelative示例
示例代码 <?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" motion:defaultDuration="2000"> <Transition motion:constraintSetStart="@+id/btn7_start" motion:constraintSetEnd="@+id/btn7_end"> <KeyFrameSet> <KeyPosition motion:motionTarget="@+id/btn7" motion:framePosition="25" motion:keyPositionType="deltaRelative" motion:percentX="0.5" motion:percentY="-0.2"/> <KeyPosition motion:motionTarget="@+id/btn7" motion:framePosition="75" motion:keyPositionType="deltaRelative" motion:percentX="0.4" motion:percentY="0.6"/> </KeyFrameSet> </Transition> <ConstraintSet android:id="@+id/btn7_start"> <Constraint android:id="@+id/btn7" android:layout_width="80dp" android:layout_height="80dp" motion:layout_constraintTop_toTopOf="parent" motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintLeft_toLeftOf="parent"/> </ConstraintSet> <ConstraintSet android:id="@+id/btn7_end"> <Constraint android:id="@+id/btn7" android:layout_width="80dp" android:layout_height="80dp" motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintRight_toRightOf="parent" /> </ConstraintSet> </MotionScene> 效果(虚线为运动轨迹,中间的方点为关键点)<KeyAttribute> 元素是用来在运动序列的特定时刻,设置视图的任何 标准属性。必须注意的是,只能设置标准属性。
说明:通过 <KeyAttribute> 元素可以设置关键点位置的标准属性,但是在开始位置和结束位置,可以设置视图的属性(包含标准属性和自定义属性),更多详情参考:设置视图属性
前面提到可以通过在 <KeyPosition> 元素中使用 <KeyAttribute> 元素在关键点设置标准属性,改变视图的样式,在 <ConstraintSet> 元素包含的子元素中,也可以设置视图的属性,改变视图样式,包括标准属性和自定义属性。
在 <Constraint> 元素中可包含标准属性,用于设置视图在改状态下的样式。
示例: <?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" motion:defaultDuration="2000"> <Transition motion:constraintSetStart="@+id/btn7_start" motion:constraintSetEnd="@+id/btn7_end"> <OnSwipe motion:touchAnchorId="@+id/btn7" motion:touchAnchorSide="bottom" motion:dragDirection="dragDown" motion:maxVelocity="2"/> <KeyFrameSet> <KeyPosition motion:motionTarget="@+id/btn7" motion:framePosition="25" motion:keyPositionType="deltaRelative" motion:percentX="0.5" motion:percentY="-0.2"/> <KeyPosition motion:motionTarget="@+id/btn7" motion:framePosition="75" motion:keyPositionType="deltaRelative" motion:percentX="0.4" motion:percentY="0.6"/> </KeyFrameSet> </Transition> <ConstraintSet android:id="@+id/btn7_start"> <Constraint android:id="@+id/btn7" android:layout_width="80dp" android:layout_height="80dp" motion:layout_constraintTop_toTopOf="parent" motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintLeft_toLeftOf="parent" android:rotation="45" android:alpha="0.2"> </Constraint> </ConstraintSet> <ConstraintSet android:id="@+id/btn7_end"> <Constraint android:id="@+id/btn7" android:layout_width="80dp" android:layout_height="80dp" motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintRight_toRightOf="parent" android:rotation="145" android:alpha="1.0"> </Constraint> </ConstraintSet> </MotionScene>效果
标准属性包括:
android:alpha:视图的透明度android:visibility:视图是否可见android:elevation:视图的 Z 轴深度(在 API Level 21开始才有,像素单位,如dp)android:rotation:视图的旋转角度(默认方向)android:rotationX:视图 X 轴方向旋转角度android:rotationY:视图 Y 轴方向旋转角度android:scaleX:视图 X 轴方向缩放android:scaleY:视图 Y 轴方向缩放android:translationX:视图 X 轴方向的平移量(像素单位,如dp)android:translationY:视图 Y 轴方向的平移量(像素单位,如dp)android:translationZ:视图 Z 轴方向的平移量(在 API Level 21开始才有,像素单位,如dp)在 <Constraint> 元素中,您可以使用 <CustomAttribute> 元素设置属性,自定义属性不仅仅可以设置标准属性,也可以设置非标准的相关属性(例如:backgroundColor 背景色),但是必须要注意一点,设置的自定义属性,必须是在 View 中定义了 getter 和 setter 方法的,而且属性值的类型必须准确。
一个 <CustomAttribute> 元素必须包含两个属性:
motion:attributeName:属性名(必须)必须包含以下含类型的属性值的一个: motion:customColorValue: 适用于颜色motion:customIntegerValue:适用于整数motion:customFloatValue:适用于浮点值motion:customStringValue:适用于字符串motion:customDimension:适用于尺寸motion:customBoolean:适用于布尔值注意事项:1. 属性名必须正确,且这个属性必须包含对外的 getter 和 setter 方法,否则自定义属性无效=;2. 属性值必须选择正确的类型,否则自定义属性无效(可以查看 setter 方法中的参数类型选择正确的类型)。
示例: <?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" motion:defaultDuration="2000"> <Transition motion:constraintSetStart="@+id/btn7_start" motion:constraintSetEnd="@+id/btn7_end"> <OnClick motion:targetId="@+id/btn_start" motion:clickAction="toggle" /> <KeyFrameSet> <KeyPosition motion:motionTarget="@+id/btn7" motion:framePosition="25" motion:keyPositionType="deltaRelative" motion:percentX="0.5" motion:percentY="-0.2"/> <KeyPosition motion:motionTarget="@+id/btn7" motion:framePosition="75" motion:keyPositionType="deltaRelative" motion:percentX="0.4" motion:percentY="0.6"/> </KeyFrameSet> </Transition> <ConstraintSet android:id="@+id/btn7_start"> <Constraint android:id="@+id/btn7" android:layout_width="80dp" android:layout_height="80dp" motion:layout_constraintTop_toTopOf="parent" motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintLeft_toLeftOf="parent" android:alpha="0.2"> <CustomAttribute motion:attributeName="backgroundColor" motion:customColorValue="@color/colorAccent" /> <CustomAttribute motion:attributeName="rotation" motion:customFloatValue="45.0" /> </Constraint> </ConstraintSet> <ConstraintSet android:id="@+id/btn7_end"> <Constraint android:id="@+id/btn7" android:layout_width="80dp" android:layout_height="80dp" motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintRight_toRightOf="parent" android:alpha="1.0"> <CustomAttribute motion:attributeName="backgroundColor" motion:customColorValue="@color/colorPrimary" /> <CustomAttribute motion:attributeName="rotation" motion:customFloatValue="145.0" /> </Constraint> </ConstraintSet> </MotionScene> 实现效果示例讲解:以上的示例中,使用 <CustomAttribute> 元素添加了两个自定义属性,分别是 backgroundColor 和 rotation,这两个属性均有公开的 getter 和 setter 方法(setBackgroundColor() 和 setRotation()),所以自定义属性有效,其中 backgroundColor 是非标准属性,rotation 是标准属性(可以直接在 <Constraint> 元素中设置)。
MotionLayout 在动画处理方面的能力非常强大,完全配置化,如果需要在布局中使用到动画的同学可以试试这个强大的布局。