MotionLayout 基础篇

2,012 阅读4分钟

ConstraintLayout 2.0 经过漫长时间的迭代终于发布了正式版。MotionLayout是这次更新的重要功能。MotionLayout 是 ConstraintLayout的子类,所以可以直接使用ConstraintLayout的各种特性,此外MotionLayout 的推出极大提高了动画开发的便利。

基本概念

使用 MotionLayout 之前需要对一些基本概念作一些介绍。

只需要描述动画开始和结束的状态,MotionLayout就可以帮我们从开始状态过度到结束的状态。

由于MotionLayout是ConstraintLayout的子类,所以最外层的布局直接改成MotionLayout,里面的 view 不需要做任何变动,此外会多出一行 app:layoutDescription=“@xml/my_motion_scene",这里指定了的 xml 文件描述了动画相关的一些信息。左右侧的ConstraintSet指定了开始和结束的状态,每个ConstraintSet里面可以指定多个Constraint,每个Constraint的作用是对相应的 view 状态进行描述。

ConstraintSet

下面看一下ConstraintSet如何编写

<ConstraintSet android:id="@+id/start">
    <Constraint android:id="@+id/button1">
        <Layout
            android:layout_marginTop="20dp"
            motion:layout_constraintLeft_toLeftOf="parent"
            motion:layout_constraintRight_toRightOf="parent"
            motion:layout_constraintTop_toTopOf="parent" />
        <Transform
            android:rotationX="20"
            android:translationX="100dp" />
        <PropertySet
            android:alpha="0.5"
            android:visibility="visible" />
        <CustomAttribute
            motion:attributeName="backgroundColor"
            motion:customColorValue="@color/colorPrimary" />
        <Motion />
    </Constraint>

    <Constraint android:id="@+id/button2">
    ...
    </Constraint>
</ConstraintSet>

这里的ConstraintSet指定了两个Constraint,两个Constraint分别描述了 button1button2 的状态,Constraint里面有五类标签 

  • <Layout> 布局相关
  • <Transform> 换相关,旋转,平移,缩放...
  • <PropertySet> 透明度和可见性
  • <CustomAttribute>定义属性,类似于属性动画,会去寻找对用属性的 getXxx() setXxx() 方法
  • <Motion>

Transition

有了开始和结束的状态后,还需要Transition 对动画做一些控制,下面看一下Transition的写法

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

    <OnClick motion:targetId="@+id/button" />

    <OnSwipe
        motion:touchAnchorId="@+id/button"
        motion:dragDirection="dragUp"
        motion:touchAnchorSide="bottom" />

    <KeyFrameSet></KeyFrameSet>
</Transition>

constraintSetStartconstraintSetEnd分别指定分别指定了动画开始和结束时的ConstraintSet,duration指定动画执行时间,此外Transition里面还支持三类标签

  •  <OnClick> 指定点击触发动画的控件
  •  <OnSwipe> 滑动触发动画
    • touchAnchorId 指定滑动哪个控件触发动画
    • dragDirection 指定往哪个方向滑动触发动画,这里指定为向上滑动触发动画
    • touchAnchorSide 指定滑控件的哪一边触发动画,这里指定为控件的底部

合起来看 button的底部向上滑触发动画

  • 关键帧 ,后面介绍

案例 1

下面看一个简单的例子

动画开始是 按钮位于最左边 ,结束后位于右侧。 首先需要写一个MotionLayout,期中layoutDescription指定为scene_02这个文件

<androidx.constraintlayout.motion.widget.MotionLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/motionLayout"
    app:layoutDescription="@xml/scene_02"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <View
        android:id="@+id/button"
        android:background="@color/colorAccent"
        android:layout_width="64dp"
        android:layout_height="64dp"
        android:text="Button"
        tools:layout_editor_absoluteX="147dp"
        tools:layout_editor_absoluteY="230dp" />

</androidx.constraintlayout.motion.widget.MotionLayout>

再看下scene_02.xml文件的写法

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

   <ConstraintSet android:id="@+id/start">
        <Constraint android:id="@id/button">
            <Layout
                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" />
        </Constraint>
    </ConstraintSet>
     <ConstraintSet android:id="@+id/end">
        <Constraint android:id="@id/button">
             <Layout
                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" />
        </Constraint>
    </ConstraintSet>

</MotionScene>

这里注意第 19 行,指定开始时靠父布局的左侧对齐;第 30 行,指定结束时靠父布局的右侧侧对齐。只需要在 xml里面指定开始和结束的状态,其他的全部交给MotionLayout帮我们实现。

案例 2

现在需求有点变化,需要从左滑到右侧时变化颜色。

<MotionScene>
    <Transition>
       ...
    </Transition>
   <ConstraintSet android:id="@+id/start">
        <Constraint ...>
            <CustomAttribute
                motion:attributeName="BackgroundColor"
                motion:customColorValue="#D81B60" />//开始的颜色
        </Constraint>
    </ConstraintSet>
    <ConstraintSet android:id="@+id/end">
        <Constraint ...>
            <CustomAttribute
                motion:attributeName="BackgroundColor"
                motion:customColorValue="#9999FF" />//结束的颜色
        </Constraint>
    </ConstraintSet>
</MotionScene>

只需要在开始和结束的ConstraintSet里面的CustomAttribute指定颜色值即可。

案例 3

需求再次有点变化,从左滑到右侧并且中间进过一个点。

这时候就需要引入Keyframes(关键帧)的概念

Keyframes(关键帧)

MotionLayout支持动画过程中插入若干关键帧,这样就可以从开始进过这些关键帧,最终过渡到结束的状态。

关键帧写在TransitionKeyFrameSet里面,关键帧有很多类型,这里主要介绍两个常用的KeyPositionKeyAttribute

KeyPosition

KeyPosition用于指定位置,说道位置不得不提到坐标系,MotionLayout为我们提供了三种坐标系,方便我们指定位置。

  • parentRelative parent指的是MotionLayout

  • deltaRelative 以控件开始和结束为基准的坐标系,原点位于控件开始的位置

  • pathRelative x 轴开始处为控件开始的位置,x轴结束的位置为控件结束的位置。

回到上面的动画,只需使用KeyPosition指定一个位置即可。

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

    <OnClick motion:targetId="@+id/button" />

    <OnSwipe
        motion:dragDirection="dragUp"
        motion:touchAnchorId="@+id/button"
        motion:touchAnchorSide="bottom" />

    <KeyFrameSet>
        <KeyPosition
            motion:motionTarget="@id/button"
            motion:framePosition="50"
            motion:keyPositionType="pathRelative"
            motion:percentY="-0.25"/>
        </KeyFrameSet>
</Transition>

framePosition可以理解为动画执行时间,这里的含义是动画执行到一半时;keyPositionType指定坐标系为pathRelative;最后通过percentY指定位置。

案例 4

需求叕变了,这次动画执行到中间时候,需要放大控件,并且旋转一个角度

这时就需要KeyAttribute这种类型的关键帧了,

KeyAttribute

代码如下

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


    <KeyFrameSet>
        <KeyAttribute
            android:rotation="-45"
            android:scaleX="2"
            android:scaleY="2"
            motion:framePosition="50"
            motion:motionTarget="@id/button" />
        <KeyPosition
            motion:framePosition="50"
            motion:keyPositionType="pathRelative"
            motion:motionTarget="@id/button"
            motion:percentY="-0.3" />
    </KeyFrameSet>
</Transition>

KeyAttribute中指定了动画执行到 一半时,放大一倍,且旋转 -45度。

Motion Editor

AS 在 4.0时为MotionLayout引入了一种可视化编辑器,使得开发者可以更加方便实现动画以及实时预览动画,通过操作Motion Editor可以生成上面手动编写的XML 文件

关于Motion Editor 的使用可以参考官方的这篇文章 Build animation with the Motion Editor

此外下面这篇文章手摸手教学,使用Motion Editor生成 twitter 闪屏页动画。 Android MotionLayout: Creating the Twitter splash screen in the simplest way possible

最后

此篇文章为MotionLayout介绍的第一篇,主要介绍一些基本使用。后面一篇会结合项目中某些场景谈一谈具体实践。