MotionLayout介绍

2,340 阅读5分钟

MotionLayout

本文是对Introduction to MotionLayout的翻译,自己可能翻译的有点不好,欢迎大家批评指正。侵权删。

MotionLayout是一个在ConstraintLayout2.0版本库的时候添加的一个新的类,可以帮助开发者处理一些动画。

Android framework已经提供了如此多的方式来帮我们在应用中添加动画,

  • Drawable矢量图
  • 属性动画
  • CoordinatorLayout
  • LayoutTransition animations
  • 利用TransitionManager的Layout transitions

那么MotionLayout与它们有什么不同的地方?

  • MotionLayout是ConstraintLayout的一个子类,受益于ConstraintLayout丰富的特性
  • MotionLayout让layout布局元素能够在动画过渡期间也能够响应用户的UI交互

什么时候使用它?

当想要对与用户有直接交互的UI元素做动画时。

什么是对与用户有着直接交互的UI元素?就是用户的点击,滑动等动作。

添加MotionLayout到你的项目

dependencies {
    implementation 'com.android.support.constraint:constraint-layout:2.0.0-alpha2'
}

使用MotionLayout

MotionLayout是ConstraintLayout的子类。要想已有的ConstraintLayout变成MotionLayout只需要在具体的xml中,将:

<android.support.constraint.ConstraintLayout .../>

改成

<android.support.constraint.motion.MotionLayout .../>

MotionLayout动画的具体的描述文件并没有包含在定义MotionLayout的layout文件中,而是定义在了一个单独的xml文件(一个MotionScene文件)。

MotionScene

就如同之前所说,描述MotionLayout的动画的具体信息被定义在了一个单独的xml文件,MotionScene,通常存储在res/xml目录下

如图所述,一个MotionScene通常包含如下的内容

  • 所使用的ConstraintSets
  • 所使用的ConstraintSets的transition(过渡阶段)
  • keyframes(关键帧)

就像下面的例子,我们通过使用手指拖动一个简单的view从屏幕的一端滑动至另一端,如何做到呢?

实现1:引用已有的layouts

一个指向初始的layout(view在屏幕左端),一个指向结束的layout(view在屏幕的右端),我们需要定义两个layout

view在左端的layout:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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: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"
        android:text="Button"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

view在右端的layout:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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: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"
        android:text="Button"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

然后定义一个MotionLayout文件

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.motion.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/motionLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layoutDescription="@xml/scene_01"
    tools:showPaths="true">

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

</android.support.constraint.motion.MotionLayout>

注意这个layout文件指向了一个MotionScene文件---scene_01

<?xml version="1.0" encoding="utf-8"?>
<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>

scene_01通过指定开始和结束的ConstraintSet(motion_01_cl_startmotion_01_cl_end,规定了过渡动画。注意,我们指定了OnSwipe标签在transition中

OnSwipe标签

scene_01.xml文件中,我们指定了一个OnSwipe标签,在一个Transition标签中,作用是依据我们的手指的动作,来让动画发生。

OnSwipe的具体的各种属性

touchAnchorId:我们想要做动画的object对象,(这里是@+id/button

touchAnchorSide:object的边,(right/left/top/bottom)

dragDirection:我们所想要追踪的手指动作的方向(dragRight/dragLeft/dragUp/dragDown)

实现2:将ConstraintSets直接定义在MotionScene中

第一种实现方式重用了layout(基于ConstraintLayout)

MotionLayout同样支持直接在MotionScene中定义ConstraintSets,这带来了如下优势:

  • 只需要一个文件,就可以包括所有的ConstraintSets
  • 方便处理attributes
  • 未来Android Studio将只支持这种将ConstraintSets直接定义在MotionScene中的方式

让我们将ConstraintSets直接定义在MotionScene中,达到和实现1一样的效果

我们定义一个与实现1中一样的MotionLayout文件,只需要引用scene_02.xml


<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.motion.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" />

</android.support.constraint.motion.MotionLayout>

scene_02.xml中,Transition标签内部是一致的,区别在于我们直接将startend的Constrait定义在了文件中。并由一个ConstraintSet标签包含着。

<?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/button"
            motion:touchAnchorSide="right"
            motion:dragDirection="dragRight" />
    </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" />
    </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" />
    </ConstraintSet>

</MotionScene>

可以被自动插值的属性(Interpolated Attributes)

在Constraint标签中除了可以定义元素的位置之外,还可以定义一些其他的属性,MotionLayout将自动地为这些属性生成中间的过渡状态,也就是插值。

alpha

visibility

elevation

rotation,rotation[x / y]

translation[x / y / z]

scaleX / Y

类似于下面的形式

<Constraint
                android:id="@id/content"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                motion:layout_constraintWidth_default="percent"
                motion:layout_constraintWidth_percent="1"
                android:scaleX="0.8"
                android:scaleY="0.8"
                motion:layout_constraintLeft_toRightOf="@+id/button"
                motion:layout_constraintTop_toTopOf="parent"/>

MotionLayout可以定义的属性

MotionLayout提供了一些属性

app:layoutDescription="reference",指向一个MotionScene XML文件

app:applyMotionScene="boolean"是否使用MotionScene,默认为true

app:showPaths="boolean"展示motion 路径,默认为false,app正式发布的时候要记得关闭

app:pregress="float"明确指定中间进度,区间在0到1

app:currentState="reference"强制一个特定的ConstraintSet

这篇文章只是MotionLayout的基础的功能的一个介绍,MotionLayout能够实现的功能远不止如此。