使用 MotionLayout 管理运动和微件动画

尝试 Compose 方式
Jetpack Compose 是 Android 的推荐 UI 工具包。了解如何在 Compose 中使用动画。

MotionLayout 是一种布局类型,可帮助您在应用中管理运动和微件动画。MotionLayoutConstraintLayout 的子类,并构建在其强大的布局功能之上。作为 ConstraintLayout 库的一部分,MotionLayout 可作为支持库使用。

MotionLayout 弥补了布局转换与复杂运动处理之间的鸿沟,其功能结合了属性动画框架TransitionManagerCoordinatorLayout 的特性。

图 1. 基本的触摸控制运动。

除了描述布局之间的转换外,MotionLayout 还允许您为任何布局属性设置动画。此外,它还原生支持可搜索的转换 (seekable transitions)。这意味着您可以根据某些条件(如触摸输入)即时显示转换过程中的任意点。MotionLayout 还支持关键帧,能够实现完全自定义的转换以满足您的需求。

MotionLayout 是完全声明式的,这意味着无论转换多么复杂,您都可以在 XML 中进行描述。

设计注意事项

MotionLayout 旨在移动、调整大小以及为用户与之交互的 UI 元素(例如按钮和标题栏)设置动画。请勿在应用中滥用运动特效。请利用它来帮助用户理解您的应用正在执行的操作。有关使用运动设计应用的更多信息,请参阅 Material Design 部分的理解运动 (Understanding motion)

开始使用

请按照以下步骤开始在项目中使用 MotionLayout

  1. 添加 ConstraintLayout 依赖项:要在项目中使用 MotionLayout,请将 ConstraintLayout 2.0 依赖项添加到您应用的 build.gradle 文件中。如果您使用的是 AndroidX,请添加以下依赖项

    Groovy

    dependencies {
        implementation "androidx.constraintlayout:constraintlayout:2.2.1"
        // To use constraintlayout in compose
        implementation "androidx.constraintlayout:constraintlayout-compose:1.1.1"
    }

    Kotlin

    dependencies {
        implementation("androidx.constraintlayout:constraintlayout:2.2.1")
        // To use constraintlayout in compose
        implementation("androidx.constraintlayout:constraintlayout-compose:1.1.1")
    }
  2. 创建 MotionLayout 文件:MotionLayoutConstraintLayout 的子类,因此,您可以通过替换布局资源文件中的类名,将任何现有的 ConstraintLayout 转换为 MotionLayout,如下例所示

    AndroidX

    <!-- before: ConstraintLayout -->
    <androidx.constraintlayout.widget.ConstraintLayout .../>
    <!-- after: MotionLayout -->
    <androidx.constraintlayout.motion.widget.MotionLayout .../>
              

    支持库

    <!-- before: ConstraintLayout -->
    <android.support.constraint.ConstraintLayout .../>
    <!-- after: MotionLayout -->
    <android.support.constraint.motion.MotionLayout .../>
              

    以下是一个 MotionLayout 文件的完整示例,它定义了图 1 中所示的布局

    AndroidX

    <?xml version="1.0" encoding="utf-8"?>
    <!-- activity_main.xml -->
    <androidx.constraintlayout.motion.widget.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" />
    
    </androidx.constraintlayout.motion.widget.MotionLayout>
            

    支持库

    <?xml version="1.0" encoding="utf-8"?>
    <!-- activity_main.xml -->
    <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>
            
  3. 创建 MotionScene:在前面的 MotionLayout 示例中,app:layoutDescription 属性引用了一个运动场景 (motion scene)。运动场景是一个 XML 资源文件。在它的 <MotionScene> 根元素内,运动场景包含了相应布局的所有运动描述。为了将布局信息与运动描述分开,每个 MotionLayout 都会引用一个单独的运动场景。运动场景中的定义优先于 MotionLayout 中的任何类似定义。

    以下是一个示例运动场景文件,描述了图 1 中的基本水平运动

    <?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>
        

    请注意以下内容

    • <Transition> 包含了运动的基本定义。

      • motion:constraintSetStartmotion:constraintSetEnd 是指向运动终点的引用。这些终点在运动场景后面的 <ConstraintSet> 元素中定义。

      • motion:duration 指定运动完成所需的毫秒数。

    • <OnSwipe> 允许您为运动创建触摸控制。

      • motion:touchAnchorId 指的是用户可以滑动和拖动的视图。

      • motion:touchAnchorSide 表示视图是从右侧被拖动的。

      • motion:dragDirection 指的是拖动的进度方向。例如,motion:dragDirection="dragRight" 表示随着视图向右拖动,进度会增加。

    • <ConstraintSet> 是定义描述运动的各种约束的地方。在此示例中,为运动的每个终点定义了一个 <ConstraintSet>。这些终点使用 app:layout_constraintTop_toTopOf="parent"app:layout_constraintBottom_toBottomOf="parent" 在垂直方向上居中。在水平方向上,这些终点位于屏幕的最左侧和最右侧。

    有关运动场景支持的各种元素的详细介绍,请参阅 MotionLayout 示例

插值属性

在运动场景文件中,ConstraintSet 元素可以包含在转换期间进行插值的其他属性。除了位置和边界外,MotionLayout 还会对以下属性进行插值

  • alpha (透明度)
  • visibility (可见性)
  • elevation (高度)
  • rotation, rotationX, rotationY (旋转)
  • translationX, translationY, translationZ (平移)
  • scaleX, scaleY (缩放)

自定义属性

<Constraint> 中,您可以使用 <CustomAttribute> 元素为那些与位置或 View 属性不直接相关的属性指定转换。

<Constraint
    android:id="@+id/button" ...>
    <CustomAttribute
        motion:attributeName="backgroundColor"
        motion:customColorValue="#D81B60"/>
</Constraint>

<CustomAttribute> 本身包含两个属性

  • motion:attributeName 是必需的,必须与具有 getter 和 setter 方法的对象匹配。getter 和 setter 必须遵循特定的模式。例如,支持 backgroundColor,因为视图具有底层的 getBackgroundColor()setBackgroundColor() 方法。
  • 您必须提供的另一个属性取决于值类型。请从以下支持的类型中选择
    • motion:customColorValue (颜色)
    • motion:customIntegerValue (整数)
    • motion:customFloatValue (浮点数)
    • motion:customStringValue (字符串)
    • motion:customDimension (尺寸)
    • motion:customBoolean (布尔值)

指定自定义属性时,请在起始和结束的 <ConstraintSet> 元素中定义终点值。

更改背景颜色

基于上一个示例,假设您希望视图的颜色在运动过程中发生变化,如图 2 所示。

图 2. 视图在移动时改变背景颜色。

向每个 ConstraintSet 元素添加 <CustomAttribute> 元素,如下面的代码片段所示

<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>

其他 MotionLayout 属性

除了上述示例中的属性外,MotionLayout 还有其他您可能需要指定的属性

  • app:applyMotionScene="boolean" 指示是否应用运动场景。此属性的默认值为 true
  • app:showPaths="boolean" 指示在运行运动时是否显示运动路径。此属性的默认值为 false
  • app:progress="float" 允许您明确指定转换进度。您可以使用从 0(转换开始)到 1(转换结束)之间的任何浮点值。
  • app:currentState="reference" 允许您指定特定的 ConstraintSet
  • app:motionDebug 允许您显示有关运动的其他调试信息。可能的值为 "SHOW_PROGRESS""SHOW_PATH""SHOW_ALL"

其他资源

有关 MotionLayout 的更多信息,请参阅以下资源