Android MotionLayout 运动布局的使用

Google 在 2018 年开发者大会上推出一种新的布局组件 MotionLayout。其官方定义如下:

MotionLayout is a layout type that helps you manage motion and widget animation in your app. MotionLayout is a subclass of ConstraintLayout and builds upon its rich layout capabilities.

简单翻译过来:MotionLayout 是一个能够帮助我们在 APP 中管理手势和控件动画的布局组件。它是 ConstraintLayout 的子类,同时可以基于自身丰富的布局功能来进行构建。

MotionLayout 从字面意思可以理解为运动布局。与传统布局组件相比(如 FrameLayout、LinearLayout 等),几乎可以说 MotionLayout 是布局组件中的一个里程碑,因为从此开始告别了 xml 文件只能静态操作 UI 的现状。通过 MotionLayout,我们可以更加轻易地处理其内部子 View 的手势操作和动画效果。



一、准备工作

1.1 导入依赖

implementation 'androidx.constraintlayout:constraintlayout:2.0.1'

1.2 将布局转换为 MotionLayout

MotionLayout 是 ConstraintLayout 的子类,可以直接由 ConstraintLayout 转化。打开 activity_main.xml 布局文件,在 Component Tree 栏选中选择 ConstraintLayout 右击,在菜单栏中点击 Covert to MotionLayout 选项。

在这里插入图片描述
在 res 目录下出现 xml 目录,目录下有一个 activity_main_scene.xml 文件,如图:

在这里插入图片描述

内容为:

<?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:constraintSetEnd="@+id/end"
        motion:constraintSetStart="@id/start"
        motion:duration="1000">
        <KeyFrameSet>
        </KeyFrameSet>
    </Transition>

    <ConstraintSet android:id="@+id/start">
    </ConstraintSet>

    <ConstraintSet android:id="@+id/end">
    </ConstraintSet>
</MotionScene>

同时 activity_main.xml 的 Design 界面出现 MotionLayout 工作台:

在这里插入图片描述

其中:
① 普通状态,选中后预览视图显示原始的状态;
② 表示 id=“start” 的 ConstraintSet(约束集),选中后预览视图会显示开始约束集的布局,对应 xml 中代码:

<ConstraintSet android:id="@+id/start">
</ConstraintSet>

③ 表示 id=“end” 的 ConstraintSet(约束集),选中后预览视图会显示结束约束集的布局,对应 xml 中代码:

<ConstraintSet android:id="@+id/end">
</ConstraintSet>

④ 表示从开始约束集到结束约束集的过渡,对应 xml 中代码:

<Transition
    motion:constraintSetEnd="@+id/end"
    motion:constraintSetStart="@id/start"
    motion:duration="1000">
</Transition>

⑤ 用于创建一个新的约束集 ConstraintSet
⑥ 用于创建一个新的过渡 Transition
⑦ 用于创建触发转场的行为,是点击 Click 还是滑动 Swipe
⑧ 表示约束集中的元素


二、创建普通过渡动画

2.1 创建动画元素

在布局中创建一个 View 如下:

<androidx.constraintlayout.motion.widget.MotionLayout 
    ...
    app:showPaths="true"
    app:layoutDescription="@xml/activity_main_scene">

    <View
        android:id="@+id/rect"
        android:layout_width="70dp"
        android:layout_height="70dp"
        android:background="@color/colorPrimary" />
  
</androidx.constraintlayout.motion.widget.MotionLayout>

app:showPaths=“true” 用于显示运动轨迹。

2.2 定义 View 动画开始与结束状态

在 activity_main_scene.xml 中,在 id=“start” 的 ConstraintSet 里添加该 View 的约束,令 View 位于左侧中间位置:

<ConstraintSet android:id="@+id/start">
    <Constraint
        android:id="@+id/rect"
        android:layout_width="70dp"
        android:layout_height="70dp"
        motion:layout_constraintBottom_toBottomOf="parent"
        motion:layout_constraintStart_toStartOf="parent"
        motion:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>

同理,在 id=“end” 的 ConstraintSet 里添加该 View 的约束,令 View 位于右侧中间位置:

<ConstraintSet android:id="@+id/end">
    <Constraint
        android:id="@+id/rect"
        android:layout_width="70dp"
        android:layout_height="70dp"
        motion:layout_constraintBottom_toBottomOf="parent"
        motion:layout_constraintEnd_toEndOf="parent"
        motion:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>

2.3 过渡 Transition

Transition 标签如下:

<Transition
    motion:constraintSetEnd="@+id/end"
    motion:constraintSetStart="@id/start"
    motion:duration="1000">
    <KeyFrameSet></KeyFrameSet>
</Transition>

Transition 标签有以下几个属性:

  1. motion:constraintSetStart:指定运动开始状态,值为 ConstrainSet 的 id;
  2. motion:constraintSetEnd:指定运动结束状态,值为 ConstrainSet 的 id;
  3. motion:duration:指定动画执行的时间;
  4. KeyFrameSet:关键帧标签,这个之后再讲。

但是 Transition 只是定义了 ConstraintSet 开始和结束的状态,要让用户去触发,还要在 Transition 标签下添加触发条件:点击(OnClick) 或滑动(OnSwipe)。

2.3.1 OnClick

表示由用户点击触发,示例如下:

<Transition
    motion:constraintSetEnd="@+id/end"
    motion:constraintSetStart="@id/start"
    motion:duration="1000">
    <OnClick
        motion:clickAction="toggle"
        motion:targetId="@+id/rect" />
</Transition>

OnClick 标签有以下几个属性:

  1. motion:targetId,对应目标 View 的 id;
  2. motion:clickAction,对应点击后进行的行为,可以设置如下几个值:
    1. toggle:通过过渡动画在 motion:constraintSetStart 和 motion:constraintSetEnd 状态之间切换;
    2. transitionToStart:通过过渡动画过渡到 motion:constraintSetStart 属性指定的状态;
    3. transitionToEnd:通过过渡动画过渡到 motion:constraintSetEnd 属性指定的状态;
    4. jumpToStart:直接跳转到 motion:constraintSetStart 属性指定的状态;
    5. jumpToEnd:直接跳转到 motion:constraintSetEnd 属性指定的状态。

预览效果如下:

在这里插入图片描述

2.3.2 OnSwipe

表示由用户滑动触发,它会根据用户滑动行为调整动画的进度,示例如下:

<Transition
    motion:constraintSetEnd="@+id/end"
    motion:constraintSetStart="@id/start"
    motion:duration="1000">
    <OnSwipe
        motion:dragDirection="dragRight"
        motion:touchAnchorId="@+id/rect"
        motion:touchAnchorSide="right" />
</Transition>

OnSwipe 标签有以下几个属性:

  1. motion:touchAnchorId,对应目标 View 的 id;
  2. motion:touchAnchorSide,从目标 View 相对位置拖动 View,示例即 View 右侧;
  3. motion:dragDirection,表示拖动的方向,值可为 “dragLeft”、“dragRight”、“dragUp” 和 “dragDown”。
  4. motion:dragScale, 用户滑动长度 × dragScale = View 移动距离,默认值为 1;
  5. motion:maxVelocity,目标 View 的最大速度;
  6. motion:maxAcceleration,目标 View 的最大加速度。

预览效果如下:

在这里插入图片描述


三、创建属性过渡动画

之前提到,ConstraintSet 的 Constraint 中可以指定约束,上面例子中只用到了位置约束,接下来可以尝试添加某些属性,可添加的属性有:

  1. alpha:透明度;
  2. visibility:可见;
  3. elevation:抬升;
  4. rotation、rotationX、rotationY:旋转;
  5. translationX、translationY、translationZ:偏移;
  6. scaleX、scaleY:缩放。

这里以透明度和旋转作为示例。

3.1 透明度属性

在动画开始位置设置 android:alpha=“1” 结束位置设置 android:alpha=“0.1”,MotionLayout 会自动过渡。

<ConstraintSet android:id="@+id/start">
    <Constraint
        android:id="@+id/rect"
        ...
        android:alpha="1"
        ... />
</ConstraintSet>

<ConstraintSet android:id="@+id/end">
    <Constraint
        android:id="@+id/rect"
        ...
        android:alpha="0.1"
        ... />
</ConstraintSet>

预览效果如下:

在这里插入图片描述

3.2 旋转属性

在动画开始位置设置 android:rotationX=“0” 结束位置设置 android:rotationX=“180”,旋转 180 度,MotionLayout 会自动过渡。

<ConstraintSet android:id="@+id/start">
    <Constraint
        android:id="@+id/rect"
        ...
        android:rotationX="0"
        ... />
</ConstraintSet>

<ConstraintSet android:id="@+id/end">
    <Constraint
        android:id="@+id/rect"
        ...
        android:rotationX="180"
        ... />
</ConstraintSet>

预览效果如下:

在这里插入图片描述

3.3 自定义属性

在 Constraint 中,还可以使用 CustomAttribute 元素为 View 添加自定义属性。一个 CustomAttribute 本身包含两个属性:

  1. motion:attributeName:自定义属性名(必需),并且需要与具有 getter/setter 方法的对象匹配。例如 backgroundColor,因为 View 具有 getBackgroundColor() 和 setBackgroundColor() 方法。
  2. 对应属性的属性值,如下:
    1. motion:customColorValue:颜色值;
    2. motion:customIntegerValue:整数值;
    3. motion:customFloatValue:浮点值;
    4. motion:customStringValue:字符串值;
    5. motion:customDimension:尺寸值;
    6. motion:customBoolean:布尔值。

更改背景颜色示例如下:

<ConstraintSet android:id="@+id/start">
    <Constraint
        android:id="@+id/rect"
        ...
        <CustomAttribute
            motion:attributeName="backgroundColor"
            motion:customColorValue="#F9A825" />
    </Constraint>
</ConstraintSet>

<ConstraintSet android:id="@+id/end">
    <Constraint
        android:id="@+id/rect"
        ...
        <CustomAttribute
            motion:attributeName="backgroundColor"
            motion:customColorValue="#AD1457" />
    </Constraint>
</ConstraintSet>

预览效果如下:

在这里插入图片描述


四、创建关键帧动画

上面的所有动画,我们都只定义了开始与结束两个状态,中间的过渡由 MotionLayout 自己完成。但如果想要改变过渡到中间某一时刻的状态呢,那就需要使用之前还没讲的 KeyFrameSet 关键帧。使用 KeyFrameSet 标签,可以对动画过渡过程中的某些点设置约束或属性,而点与点之间的过渡,仍由 MotionLayout 完成。

可以看 MotionLayout 工作台:

在这里插入图片描述

点击选中 ①,工作台会出现时间轴工具,点击 ② 处播放按钮可以直接在编辑器预览动画效果,点击 ③ 处就可以看到添加 KeyFrameSet 子元素的选项,下面对这几种元素进行分析。

4.1 KeyPosition

KeyPosition 即关键帧位置,如下示例,在 Transition 中添加 KeyPosition 相关设置:

<Transition
    ...>
    <OnClick
        ... />
    <KeyFrameSet>
        <KeyPosition
            motion:framePosition="50"
            motion:keyPositionType="parentRelative"
            motion:motionTarget="@id/rect"
            motion:percentX="0.7"
            motion:percentY="0.2" />
    </KeyFrameSet>
</Transition>

预览效果如下:

在这里插入图片描述

KeyPosition 标签中添加了以下几个属性:

  1. motion:framePosition:表示关键帧在动画执行的百分比进度,取值范围为 [0, 100]。示例中的 50 表示动画进度执行到 50% 时有一帧关键帧;
  2. motion:motionTarget:表示目标视图 id;
  3. motion:keyPositionType:表示坐标系类型,取值可以为 “parentRelative”、“deltaRelative”、“pathRelative”;
  4. motion:percentY 和 motion:percentX:表示相对参考系的纵向和横向的比例。

重点看一下 keyPositionType 的取值。

4.1.1 parentRelative

<KeyPosition
    ...
    motion:keyPositionType="parentRelative"
    motion:percentX="0.7"
    motion:percentY="0.2" />

parentRelative 表示以 MotionLayout 布局为参考系,布局左上角为 (0, 0),右下角为 (1, 1)。motion:percentX=“0.7”、motion:percentY=“0.2” 就是 (0.7, 0.2) 的位置,如下图所示:

在这里插入图片描述

4.1.2 deltaRelative

<KeyPosition
    ...
    motion:keyPositionType="deltaRelative"
    motion:percentX="0.7"
    motion:percentY="0.2" />

deltaRelative 表示以该 View 的起始点作为 (0, 0),结束点作为 (1, 1),motion:percentX=“0.7”、motion:percentY=“0.2” 就是 (0.7, 0.2) 的位置,(本示例调整了结束点),如下图所示:

在这里插入图片描述

4.1.3 pathRelative

<KeyPosition
    ...
    motion:keyPositionType="pathRelative"
    motion:percentX="0.7"
    motion:percentY="0.2" />

pathRelative 是以 View 的起始点、结束点连线作为 x 轴,两个点分别为 (0, 0) 和 (1, 0)。x 轴顺时针旋转 90° 作为 y 轴,等长距离作为刻度,如下图所示:

在这里插入图片描述

4.2 KeyAttribute

KeyAttribute 即关键帧属性,如下示例,在 Transition 中添加 KeyAttribute 相关设置:

<Transition
    ...>
    <OnClick
        ... />
    <KeyFrameSet>
        <KeyAttribute
            android:alpha="0.1"
            android:rotationY="180"
            motion:framePosition="50"
            motion:motionTarget="@id/rect" />
    </KeyFrameSet>
</Transition>

预览效果如下:

在这里插入图片描述

可以看到 View 先沿 Y 从 0° 旋转到 180° 再旋转回 0°,透明度也由 1 到 0.5 再到 1。

KeyAttribute 标签中添加了以下几个属性:

  1. motion:framePosition:同上,表示关键帧在动画执行的百分比进度,取值范围为 [0, 100]。示例中的 50 表示动画进度执行到 50% 时有一帧关键帧。
  2. motion:motionTarget:表示目标视图 id;
  3. 支持对 View 的所有原生属性如 alpha、rotation 等以及自定义属性 CustomAttribute。

4.3 KeyCycle

KeyCycle 可以让 View 在动画的过程中按照一些周期函数,周期性的改变其属性值。跟某一帧没有关系,影响的是部分动画过程。如下示例,在 Transition 中添加 KeyCycle 相关设置:

<Transition
    ...>
    <OnClick
        ... />
    <KeyFrameSet>
        <KeyCycle
            android:translationY="30dp"
            motion:framePosition="50"
            motion:motionTarget="@+id/rect"
            motion:wavePeriod="1"
            motion:waveShape="sin" />
    </KeyFrameSet>
</Transition>

预览效果如下:

在这里插入图片描述

该示例就是在动画 [0%, 50%] 这个过程中以 sin 这个周期函数,周期性的改变 android:translationY 属性。

KeyCycle 标签中添加了以下几个属性:

  1. motion:framePosition:与之前有细微差别,这里表示 KeyCycle 作用范围在动画执行的 [0%, 50%]。
  2. motion:motionTarget:表示目标视图 id;
  3. motion:wavePeriod:表示运动的周期数;
  4. motion:waveShape:表示周期类型,示例中指定了 sin,就会按 sin 周期函数变化。

4.4 KeyTimeCycle

KeyTimeCycle 可以在关键帧上按照一些周期函数,周期性的改变其属性值。如下示例,在 Transition 中添加 KeyTimeCycle 相关设置:

<Transition
    ...>
    <OnClick
        ... />
    <KeyFrameSet>
        <KeyTimeCycle
            android:translationY="30dp"
            motion:motionTarget="@+id/rect"
            motion:wavePeriod="1"
            motion:waveShape="sin" />
    </KeyFrameSet>
</Transition>

预览效果如下:

在这里插入图片描述

可以看到 KeyTimeCycle 与 KeyCycle 比较,KeyTimeCycle 是在帧上做周期性,KeyCycle 是在动画过程中做周期性。

KeyTimeCycle 标签中的属性与 KeyCycle 基本一致。

4.5 KeyTrigger

KeyTrigger 可以在动画的过程中触发 View 中的函数。
比如自定义一个 TextView 类 MyTextView,在 MyTextView 中定义两个函数 showHello() 和 showWorld(),代码如下:

class MyTextView(context: Context, attrs: AttributeSet) : AppCompatTextView(context, attrs) {
    fun showHello() {
        text = "Hello"
    }

    fun showWorld() {
        text = "World"
    }
}

修改 activity_main.xml 布局如下:

<androidx.constraintlayout.motion.widget.MotionLayout 
    ...
    app:showPaths="true"
    app:layoutDescription="@xml/activity_main_scene">

    <com.example.motionlayoutdemo.MyTextView
        android:id="@+id/rect"
        android:layout_width="70dp"
        android:layout_height="70dp"
        android:background="@color/colorPrimary"
        android:gravity="center"
        android:textSize="20sp"
        android:textColor="@android:color/white" />
  
</androidx.constraintlayout.motion.widget.MotionLayout>

在 activity_main_scene.xml 中 Transition 标签中添加 KeyTrigger 相关设置:

<Transition
    ...>
    <OnClick
        ... />
    <KeyFrameSet>
        <KeyTrigger
            motion:framePosition="20"
            motion:motionTarget="@id/rect"
            motion:onCross="showHello" />
        <KeyTrigger
            motion:framePosition="80"
            motion:motionTarget="@id/rect"
            motion:onCross="showWorld" />
    </KeyFrameSet>
</Transition>

预览效果如下:

在这里插入图片描述

可以看到,在动画进度 20% 的时候触发 id rect 视图的 showHello 方法,80% 的时候触发 id rect 视图的 showWorld 方法。

KeyTrigger 标签中添加了以下几个属性:

  1. motion:framePosition:表示关键帧在动画执行的百分比进度,取值范围为 [0, 100];
  2. motion:motionTarget:表示目标视图 id;
  3. motion:onCross:表示要触发的函数名;

注意,onCross 在动画不论是正向还是逆向,只要到达设置的 framePosition 就会触发。还有两个属性:

  1. motion:onPositiveCross:只有正向执行动画时到达设置的 framePosition 才会执行;
  2. motion:onNegativeCross:只有逆向执行动画时到达设置的 framePosition 才会执行。

五、最后

  1. MotionLayout 的知识点很多,而且可以与 RecyclerViewAppBarLayout 等许多组件联合使用。上面只是对一些属性的简单演示,要想实现复杂的动画,还需要对各种属性多组合多尝试多练习。
  2. MotionLayout 功能还是比较强大的,可以直接实现许多常见的动画效果,比如下面这个搜索框动画:

在这里插入图片描述

  1. MotionLayout 自带一个处理动画的图形化工具如下,如果直接从 xml 上手,可能会比较生疏,可以借助图形化工具进行编辑、预览,再比对生成的 xml,加深理解。

在这里插入图片描述

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值