原文章地址

MotionLayout 是ConstraintLayout 2.0 新增的类,指在帮助Android开发者管理应用的中的motion和widget动画.

Why MotionLayout ?

The Android framework already provides several ways of adding animation in your application:
目前Android框架已经提供了如下几种添加动画到应用中的方式:

此节将聚焦于 MotionLayout 于其他方案的异同点.

MotionLayout,正如名字所示,首先是一个layout,承载不同的元素. 并且其也是ConstraintLayout的子类,是基于其强大的布局能力上构建的.

MotionLayout 是为了消除 layout transitions 和复杂的 motion 动画处理之间的差别而创建的. 可以认为具有一种兼具property aniamtion framework, TransitionManager和CoordinatorLayout混合起来的能力.

A mix between the property animation framework, layout transitions with TransitionManager, and CoordinatorLayout(多功能模块混合一起)

MotionLayout 通过在两个layouts间描述transition(类似TransitionManager), 但是能animate任意的属性(不仅仅是layout属性). 而且,其本质上是支持可查找的(seekable)transitions,就像 CoordinatorLayout(能单纯的通过触摸和transition驱动,实时移动到任意点). 支持触摸处理和keyframes, 运行开发者根据自身需要轻易的自定义transitions.

MotionLayout is fully declarative(完全声明式)

另外的一个关键的不同就是 MotionLayout 是完全 声明式的(declaractive) - 完全可以在xml中描述复杂的transition - 不需要任意代码(如果需要用代码表达motion,当前的属性动画框架已经提供了一个很好的方式实现).

MotionLayout tooling(工具管理)

我们相信此基于声明式的动效表达式能简单的创建动效, 并且提供了一个在Android Studio的图形工具去管理.
此工具目前还未ok,一旦ConstraintLayout 2.0 迭代到 stable或beta 后将可用

MotionLayout I - 图1

最终, 此工具会做为ConstraintLayout 2.0的一部分发布, 将作为支持库存在, 向后兼容到 API level 18 (JellyBean MR2) : 意味着95%的Android设备.

当前限制

MotionLayout 只能作用于于其直接children, 而TransitionManager,可用支持nested的布局层级和Activity Transitions.

MotionLayout

添加到gradle文件中:

  1. dependencies {
  2. implementation 'com.android.support.constraint:constraint-layout:2.0.0-alpha1'
  3. }

The main difference between ConstraintLayout and MotionLayout at the XML level is that the actual description of what MotionLayout will do is notnecessarily contained in the layout file.
ConstraintLayout和MotionLayout在xml层的主要不同点在于MotionLayout真正的描述信息不必要包含在layout文件中,而是将所有的信息放置在另外被引用到的xml文件中(MotionScene). 如下所示:

  1. <android.support.constraint.motion.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:app="http://schemas.android.com/apk/res-auto"
  3. xmlns:tools="http://schemas.android.com/tools"
  4. android:id="@+id/motionLayout"
  5. android:layout_width="match_parent"
  6. android:layout_height="match_parent"
  7. app:layoutDescription="@xml/scene_01"
  8. tools:showPaths="true">
  9. <View
  10. android:id="@+id/button"
  11. android:layout_width="64dp"
  12. android:layout_height="64dp"
  13. android:background="@color/colorAccent"
  14. android:text="Button"
  15. tools:layout_editor_absoluteX="147dp"
  16. tools:layout_editor_absoluteY="230dp" />
  17. </android.support.constraint.motion.MotionLayout>

scene_01.xml 是真正的描述文件(单独放置在 res/xml 文件夹目录下):

<MotionScene
    xmlns:motion="http://schemas.android.com/apk/res-auto">

    <Transition
        motion:constraintSetStart="@layout/motion_01_cl_start"
        motion:constraintSetEnd="@layout/motion_01_cl_end"
        motion:duration="1000">
        <OnSwipe
            motion:touchAnchorId="@+id/button"
            motion:touchAnchorSide="right"
            motion:dragDirection="dragRight" />
    </Transition>

</MotionScene>


此原理的灵感来源于ConstraintSets的Keyframes Animaitons, 如果对比不了解可以参考下 Keyframes Animations With ContraintLayout and ConstraintSets的视频
MotionLayout I - 图2

MotionLayout 属性

  • app:layoutDescription=”reference” MotionScene XML文件的引用id值

  • app:applyMotionScene=”boolean” 使用或者不使用MotionScene[default=true]

  • app:showPaths=”boolean” 是否显示path [default=false]

  • app:progress=”float” 指定transition进度[0,1]

  • app:currentState=”reference” 强制指定当前的ConstraintSet

MotionScene

正如上诉提到的,跟普通的layouts不同,MotionLayout的具体信息描述都是放置在一个单独的XML文件中,也就是 MotionScene,存放在res/xml文件夹下.

MotionScene的整体设计如下:

MotionLayout I - 图3

MotionScene 文件可以包含素有其所有想表达的动画:

  • ConstraintSets 的使用

  • ConstraintSets 之间的transtions操作

  • keyframes, touch 处理等等


正如Scene_01.xml描述

motion_01_cl_start.xml 内容如下(按钮垂直屏幕居中在左侧):

<android.support.constraint.ConstraintLayout 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">

    <View
        android:id="@+id/button"
        android:background="@color/colorAccent"
        android:layout_width="64dp"
        android:layout_height="64dp"
        android:layout_marginStart="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

motion_01_cl_end.xml 内容如下(按钮垂直屏幕居中在右侧):

<android.support.constraint.ConstraintLayout 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">

    <View
        android:id="@+id/button"
        android:background="@color/colorAccent"
        android:layout_width="64dp"
        android:layout_height="64dp"
        android:layout_marginEnd="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

�剖析下其中的关键点
四个layouts

  • MotionLayout初始化描述的xml文件(motion_01_basic.xml),其中的View属性可随意填写,因为其真实的描述信息并不是在这个文件中

  • MotionSence的描述文件(scene_01.xml),包含了起始和结束点的布局信息

  • 起始位置的布局文件(motion_01_cl_start.xml)

  • 结束位置的布局文件(motion_01_cl_end.xml)

其中直接childe的id必须是相同的(如上代码中的 button id)

OnSwipe handler

回看scene_01.xml布局详情定义中,在Transition中定义了一个OnSwipe handler, 这个的作用是用于通过手势的变动驱动transition.
MotionLayout I - 图4

如上图所示的,其核心属性包含如下:

  • dragDirection:追踪手势动画拖拽方向 (dragRight / dragLeft / dragUp / dragDown 主要是定义[0,1]之间的进度值的设置),如dragLeft值关心往左方向滑动才会被追踪

  • touchAnchorId:追踪手势的对象 在此是 @+id/button

  • touchAnchorSide: 追踪手势对象的某一边?(right / left / top / bottom) 暂时未完全理解

  • maxVelocity: 最大的速率控制

Self-Contained MotionScene

MotionLayout 同样支持在 MotionScene 文件中直接定义 ConstraintSets, 也是res/xml放在文件夹中, 这样做有几大好处:

  • 单个文件维护多个ConstraintSets

  • 增加处理其他属性和自定义属性的功能

  • 预想未来: 即将发布的在Android Studio中的MotionEditor将很可能只支持self-contained MotionScene文件

Interpolated Attributes(属性的截断)

MotionScene文件中定义的ConstraintSets中的Attributes(属性)不仅仅只是普通的layout属性, 除了position和bounds外,下列各种属性也会自动被MotionLayout拦截处理.

  • alpha

  • visibility

  • elevation

  • rotation, rotation[X/Y]

  • translation[X/Y/Z]

  • scaleX/Y

* @see #CustomAttribute_attributeName 
* @see #CustomAttribute_customBoolean
* @see #CustomAttribute_customColorValue
* @see #CustomAttribute_customDimension
* @see #CustomAttribute_customFloatValue
* @see #CustomAttribute_customIntegerValue
* @see #CustomAttribute_customStringValue

ConstraintSets使用举例:属性相关的设置文章二有讲

<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"
        motion:interpolator="linear">
        <OnSwipe
            motion:dragDirection="dragRight"
            motion:touchAnchorId="@id/button"
            motion:touchAnchorSide="right" />
    </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">
            <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>

</MotionScene>