使用 Kotlin 03.2 进行高级 Android 开发:使用 MotionLayout 的动画效果

1. 准备工作

此 Codelab 是“使用 Kotlin 进行高级 Android 开发”课程的一部分。如果您按顺序学习这些 Codelab,您将会充分发掘课程的价值,但并不强制要求这样做。“使用 Kotlin 进行高级 Android 开发”Codelab 着陆页中列出了所有课程 Codelab。

MotionLayout 是一个库,借助它,您可以向 Android 应用添加丰富的运动效果。它基于 ConstraintLayout,,可让您使用 ConstraintLayout 为您可以构建的任何对象添加动画效果。

您可以使用 MotionLayout 同时为多个视图的位置、大小、可见性、alpha 值、颜色、高度、旋转度以及其他属性添加动画效果。使用声明式 XML,您可以创建涉及多个视图的协调动画,而这难以通过代码实现。

添加动画是改善应用体验的绝佳方式。您可以将动画用于以下用途:

  • 显示变化 - 在不同状态之间添加动画,可让用户自然而然地跟踪界面中的变化情况。
  • 吸引用户注意 - 使用动画吸引用户注意重要的界面元素。
  • 构建精美的设计 - 在设计中添加有效的运动效果可让应用看起来更精美。

前提条件

此 Codelab 专为具备一定 Android 开发经验的开发者而设计。在尝试完成此 Codelab 之前,您应该具备以下条件:

  • 了解如何使用 Android Studio 打造包含 activity 和基本布局的应用,以及如何在设备或模拟器上运行该应用。熟悉 ConstraintLayout。仔细阅读约束布局 Codelab,详细了解 ConstraintLayout

实践内容

  • 使用 ConstraintSetsMotionLayout 定义动画效果
  • 根据拖动事件添加动画
  • 使用 KeyPosition 更改动画
  • 使用 KeyAttribute 更改属性
  • 使用代码运行动画
  • 使用 MotionLayout 为可收起的标头添加动画

所需条件

  • Android Studio 4.0MotionLayout 编辑器仅适用于该版本的 Android Studio。)

2. 使用入门

若要下载示例应用,您可以执行以下操作之一:

…或从命令行使用下列命令克隆 GitHub 代码库:

$ git clone https://github.com/googlecodelabs/motionlayout.git

3. 使用 MotionLayout 创建动画

首先,您将构建一个动画,以响应用户点击,将视图从屏幕的顶部移动到底部。

若要通过起始代码创建动画,您需要以下几项主要元素:

  • MotionLayout,,它是 ConstraintLayout 的子类。您要在 MotionLayout 标记中指定要添加动画的所有视图。
  • MotionScene,,它是用于一个描述 MotionLayout. 动画的 XML 文件。
  • Transition,MotionScene 的一部分,用于指定动画的时长、触发因素以及如何移动视图。
  • ConstraintSet,用于指定过渡的起始结束约束条件。

我们将逐个了解一下每个元素,首先从 MotionLayout 开始。

第 1 步:探索现有代码

MotionLayoutConstraintLayout 的子类,因此在添加动画时,后者的所有功能它全都支持。若要使用 MotionLayout,请在需要使用 ConstraintLayout. 的位置添加 MotionLayout 视图。

  1. res/layout 中,打开 activity_step1.xml.。这里显示了一个 ConstraintLayout,其中包含一个星形 ImageView,且内部应用了色调。

activity_step1.xml

<!-- initial code -->
<androidx.constraintlayout.widget.ConstraintLayout
       ...
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       >

   <ImageView
           android:id="@+id/red_star"
           ...
   />

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

ConstraintLayout 没有任何约束条件,因此如果您现在运行应用,会看到星形图标显示不受限制,这意味着它们将被放置在未知位置。Android Studio 将向您发出警告,以说明缺少限制条件。

第 2 步:转换为动态布局

如需使用 MotionLayout, 添加动画效果,您必须将 ConstraintLayout 转换为 MotionLayout

为了让布局使用运动场景,必须让布局指向该场景。

  1. 为此,请打开设计图面。在 Android Studio 4.0 中,您可以在查看布局 XML 文件时使用右上角的分屏或设计图标来打开设计界面。

a2beea710c2decb7.png

  1. 打开设计图面后,右键点击预览,然后选择 Convert to MotionLayout

4fa936a98a8393b9

这会将 ConstraintLayout 标记替换为 MotionLayout 标记,并将 motion:layoutDescription 添加到指向 @xml/activity_step1_scene.MotionLayout 标记中

activity_step1**.xml**

<!-- explore motion:layoutDescription="@xml/activity_step1_scene" -->
<androidx.constraintlayout.motion.widget.MotionLayout
       ...
       motion:layoutDescription="@xml/activity_step1_scene">

运动场景是一个单个 XML 文件,用于描述 MotionLayout 中的动画。

一旦转换为 MotionLayout,设计图面就会显示 Motion Editor

66d0e80d5ab4daf8.png

Motion Editor 中有三个新的界面元素:

  1. Overview - 这是一种模态选择,可让您选择动画的不同部分。在此图片中,start ConstraintSet 处于选中状态。您也可以点击 startend 之间的箭头来选择过渡效果。
  2. 版块 - 概览下方是一个版块窗口,会根据当前所选的概览项发生变化。在此图片中,start ConstraintSet 信息显示在选择窗口中。
  3. 属性 - 属性面板用于显示当前选定项的属性,您可以从概览或选择窗口修改当前所选项的属性。此图显示了 start ConstraintSet 的属性。

第 3 步:定义起始和结束约束条件

所有动画都可以根据起始和结束进行定义。起始用于描述动画开始前屏幕的显示效果,结束用于描述动画完成后屏幕的显示效果。MotionLayout 负责确定如何在起始状态和结束状态(随时间变化)之间添加动画。

MotionScene 使用 ConstraintSet 标记来定义起始状态和结束状态。顾名思义,ConstraintSet 就是可应用于视图的约束条件集。其中包括宽度、高度和 ConstraintLayout 约束条件。此外,它还包含一些属性,例如 alpha。它不包含视图本身,而只包含这些视图的约束条件。

ConstraintSet 中指定的任何约束条件都将替换在布局文件中指定的约束条件。如果您在布局和 MotionScene 中都定义了约束条件,系统仅会应用 MotionScene 中的约束条件。

在这一步,您要限制星形视图,使其在屏幕顶部的起始位置开始,并在屏幕底部的结束位置结束。

您可以使用 Motion Editor 或通过直接修改 activity_step1_scene.xml 的文本来完成此步骤。

  1. 在概览面板中选择 start ConstraintSet

6e57661ed358b860

  1. 在“选择”面板中,选择 red_star目前,它显示来源 layout,表示在此 ConstraintSet 中不受限制。使用右上角的铅笔图标创建限制条件

f9564c574b86ea8.gif

  1. 确认在概览面板中选择 start ConstraintSet 后,red_star 显示的来源为 start
  2. 在 Attributes 面板中,在 start ConstraintSet 中选择 red_star,然后在顶部添加一个 Constraint,然后点击蓝色的 + 按钮。

2fce076cd7b04bd

  1. 打开 xml/activity_step1_scene.xml 查看 Motion Editor 为此约束条件生成的代码。

activity_step1_scene.xml

<!-- Constraints to apply at the start of the animation -->
<ConstraintSet android:id="@+id/start">
   <Constraint
           android:id="@+id/red_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>

ConstraintSetid@id/start,它会指定要应用于 MotionLayout 中的所有视图的所有约束条件。由于这个 MotionLayout 只有一个视图,因此它只需要一个 Constraint

ConstraintSet 内的 Constraint 会指定要限制的视图的 ID,即 activity_step1.xml 中定义的 @id/red_star。请务必注意,Constraint 标记仅会指定约束条件和布局信息。Constraint 标记并不知道自己将应用于 ImageView

该约束条件会指定高度、宽度以及将 red_star 约束到其父级顶部的起始位置所需的两个其他约束条件。

  1. 在概览面板中选择 end ConstraintSet。

346e1248639b6f1e

  1. 按照与之前相同的步骤操作,在 end ConstraintSet 中为 red_star 添加 Constraint
  2. 如需使用 Motion Editor 完成此步骤,请通过点击蓝色的 + 按钮为 bottomend 添加约束条件。

fd33c779ff83c80a.png

  1. XML 中的代码如下所示:

activitiy_step1_scene.xml

<!-- Constraints to apply at the end of the animation -->
<ConstraintSet android:id="@+id/end">
   <Constraint
           android:id="@+id/red_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>

@id/start 类似,这个 ConstraintSet@id/red_star 上也有一个 Constraint。这次,它会把它约束在屏幕底部的结束位置。

您不必将它们分别命名为 @id/start@id/end,但这样做很方便。

第 4 步:定义过渡

每个 MotionScene 还必须包含至少一个过渡。过渡定义了动画从起始到结束的所有环节。

过渡必须指定过渡的起始和结束 ConstraintSet。此外,过渡还可以指定如何以其他方式修改动画,例如运行动画的时长,或如何通过拖动视图来添加动画。

  1. 默认情况下,Motion Editor 在创建 MotionScene 文件时为我们创建了转场效果。打开 activity_step1_scene.xml 查看生成的转场效果。

activity_step1_scene.xml

<!-- A transition describes an animation via start and end state -->
<Transition
   motion:constraintSetEnd="@+id/end"
   motion:constraintSetStart="@id/start"
   motion:duration="1000">
  <KeyFrameSet>
  </KeyFrameSet>
</Transition>

这就是 MotionLayout 构建动画所需的所有元素。查看每个属性:

  • 动画开始时,constraintSetStart 将应用于视图。
  • 动画结束时,constraintSetEnd 将应用于视图。
  • duration 以毫秒为单位指定动画应持续的时间。

然后,MotionLayout 会确定起始和结束约束条件之间的路径,并为其添加指定时长的动画。

第 5 步:在 Motion Editor 中预览动画

dff9ecdc1f4a0740.gif

动画:有关在 Motion Editor 中播放过渡预览的视频

  1. 打开 Motion Editor,然后在概览面板中点击 startend 之间的箭头以选择转场效果。

1dc541ae8c43b250

  1. 选择过渡效果后,选择面板会显示播放控件和拖动条。点击“播放”或拖动当前位置即可预览动画。

a0fd2593384dfb36.png

第 6 步:添加点击处理程序

您需要一种方法来启动动画。一种方法是让 MotionLayout 响应 @id/red_star 上的点击事件。

  1. 打开动画编辑器,然后点击“概览”面板中开始与结束之间的箭头以选择转场效果。

b6f94b344ce65290.png

  1. 点击概览面板工具栏中的 699f7ae04024ccf6 创建点击或滑动处理程序。这会添加一个将开始转场的处理程序。
  2. 从弹出式窗口中选择 Click Handler

ccf92d06335105fe.png

  1. View To Click 更改为 red_star

b0d3f0c970604f01.png

  1. 点击 Add(添加),在 Motion Editor 中,点击处理程序由一个小点表示。

cec3913e67fb4105.png

  1. 在概览面板中选择转场效果后,将 clickAction 属性 (toggle) 添加到您刚刚在属性面板中添加的 OnClick 处理程序。

9af6fc60673d093d

  1. 打开 activity_step1_scene.xml 查看 Motion Editor 生成的代码

activity_step1_scene.xml

<!-- A transition describes an animation via start and end state -->
<Transition
    motion:constraintSetStart="@+id/start"
    motion:constraintSetEnd="@+id/end"
    motion:duration="1000">
    <!-- MotionLayout will handle clicks on @id/red_star to "toggle" the animation between the start and end -->
    <OnClick
        motion:targetId="@id/red_star"
        motion:clickAction="toggle" />
</Transition>

Transition 使用 <OnClick> 标记告知 MotionLayout 运行动画来响应点击事件。查看每个属性:

  • targetId 是用于监视点击的视图。
  • 在点击鼠标时,toggleclickAction 会在起始和结束状态之间进行切换。您可以在文档中查看 clickAction 的其他选项。
  1. 运行您的代码,点击第 1 步,然后点击红色星形图案,查看一下动画效果吧!

第 5 步:动画的实际运用

运行应用!点击星标后,您应该会看到动画运行。

7ba88af963fdfe10.gif

完成的动画场景文件定义了一个 Transition,它指向一个起始和结束 ConstraintSet

在动画启动时 (@id/start),系统会将星形图标约束到屏幕顶部的起始位置。在动画结束时 (@id/end),系统会将星形图标约束到屏幕底部的结束位置。

<?xml version="1.0" encoding="utf-8"?>

<!-- Describe the animation for activity_step1.xml -->
<MotionScene xmlns:app="http://schemas.android.com/apk/res-auto"
            xmlns:android="http://schemas.android.com/apk/res/android">
   <!-- A transition describes an animation via start and end state -->
   <Transition
           motion:constraintSetStart="@+id/start"
           motion:constraintSetEnd="@+id/end"
           motion:duration="1000">
       <!-- MotionLayout will handle clicks on @id/star to "toggle" the animation between the start and end -->
       <OnClick
               motion:targetId="@id/red_star"
               motion:clickAction="toggle" />
   </Transition>

   <!-- Constraints to apply at the end of the animation -->
   <ConstraintSet android:id="@+id/start">
       <Constraint
               android:id="@+id/red_star"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               motion:layout_constraintStart_toStartOf="parent"
               motion:layout_constraintTop_toTopOf="parent" />
   </ConstraintSet>

   <!-- Constraints to apply at the end of the animation -->
   <ConstraintSet android:id="@+id/end">
       <Constraint
               android:id="@+id/red_star"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               motion:layout_constraintEnd_toEndOf="parent"
               motion:layout_constraintBottom_toBottomOf="parent" />
   </ConstraintSet>
</MotionScene>

4. 基于拖动事件添加动画效果

在这一步,您将构建一个响应用户拖动事件(当用户滑动屏幕时)的动画,以便运行该动画。MotionLayout 支持跟踪触摸事件以移动视图,也支持跟踪基于物理特性的滑动手势以让运动更加流畅。

第 1 步:检查起始代码

  1. 首先,打开布局文件 activity_step2.xml,其中已有 MotionLayout。看一下代码。

activity_step2.xml

<!-- initial code -->

<androidx.constraintlayout.motion.widget.MotionLayout
       ...
       motion:layoutDescription="@xml/step2" >

   <ImageView
           android:id="@+id/left_star"
           ...
   />

   <ImageView
           android:id="@+id/right_star"
           ...
   />

   <ImageView
           android:id="@+id/red_star"
           ...
   />

   <TextView
           android:id="@+id/credits"
           ...
           motion:layout_constraintTop_toTopOf="parent"
           motion:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.motion.widget.MotionLayout>

这个布局定义了动画的所有视图。三星图标在布局中不受限制,因为它们将在运动场景中以动画形式呈现。

我们确实对所有者信息 TextView 应用了约束条件,这是因为它在整个动画中的位置始终保持不变,并且不会修改任何属性。

第 2 步:为场景添加动画

与上一个动画一样,该动画将由起始和结束 ConstraintSet, 以及 Transition 定义。

定义起始 ConstraintSet

  1. 打开运动场景 xml/step2.xml 来定义动画。
  2. 为起始约束条件 start 添加约束条件。最开始,3 个星形图案均在屏幕底部居中显示。右侧和左侧星形图案的 alpha 值为 0.0,这意味着它们完全透明并处于隐藏状态。

step2.xml

<!-- TODO apply starting constraints -->

<!-- Constraints to apply at the start of the animation -->
<ConstraintSet android:id="@+id/start">
   <Constraint
           android:id="@+id/red_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintBottom_toBottomOf="parent" />

   <Constraint
           android:id="@+id/left_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:alpha="0.0"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintBottom_toBottomOf="parent" />

   <Constraint
           android:id="@+id/right_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:alpha="0.0"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>

在此 ConstraintSet 中,您需为每个星形图案分别指定一个 ConstraintMotionLayout 会在动画启动后应用各项约束条件。

我们已使用起始、结束和底部约束条件让每个星形图案视图都在屏幕底部居中。@id/left_star@id/right_star 这两个星形图案都有一个额外的 alpha 值,该值可让它们处于不可见状态,并会在动画启动时应用。

startend 约束条件集定义了动画的起始和结束。起始时的约束条件(如 motion:layout_constraintStart_toStartOf)会将一个视图的起始位于约束到另一个视图的起始位置。乍一看,这可能会令人困惑,因为二者都用到了 start 这个名称,并且二者都用于约束条件的上下文。为便于区分,我们规定 layout_constraintStart 中的 start 是指视图的“起始位置”。在从左到右书写的语言中,该位置位于左侧;在从右向左书写的语言中,该位置位于右侧。start 约束条件集是指动画的起始位置。

定义结束 ConstraintSet

  1. 定义结束约束条件,以使用将所有三颗星星同时放在 @id/credits 下方。此外,它会将左右星形的 alpha 的结束值设置为 1.0

step2.xml

<!-- TODO apply ending constraints -->

<!-- Constraints to apply at the end of the animation -->
<ConstraintSet android:id="@+id/end">

   <Constraint
           android:id="@+id/left_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:alpha="1.0"
           motion:layout_constraintHorizontal_chainStyle="packed"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintEnd_toStartOf="@id/red_star"
           motion:layout_constraintTop_toBottomOf="@id/credits" />

   <Constraint
           android:id="@+id/red_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           motion:layout_constraintStart_toEndOf="@id/left_star"
           motion:layout_constraintEnd_toStartOf="@id/right_star"
           motion:layout_constraintTop_toBottomOf="@id/credits" />

   <Constraint
           android:id="@+id/right_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:alpha="1.0"
           motion:layout_constraintStart_toEndOf="@id/red_star"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintTop_toBottomOf="@id/credits" />
</ConstraintSet>

最终结果是,当以动画方式显示时,这些视图会从中心向外扩展并向上移动。

此外,由于两个 ConstraintSets 中的 @id/right_start@id/left_star 上都设置了 alpha 属性,因此随着动画的播放,这两个视图会淡入。

根据用户滑动添加动画

MotionLayout 可以跟踪用户拖动事件(或滑动),从而创建基于物理特性的“快速滑动”动画效果。这意味着,如果用户快速滑动视图,视图将会保持移动状态,并像实物在表面上滚动一样降低移动速度。您可以在 Transition 中使用 OnSwipe 标记添加此类动画。

  1. 将用于添加 OnSwipe 标记的 TODO 替换为 <OnSwipe motion:touchAnchorId="@id/red_star" />

step2.xml

<!-- TODO add OnSwipe tag -->

<!-- A transition describes an animation via start and end state -->
<Transition
       motion:constraintSetStart="@+id/start"
       motion:constraintSetEnd="@+id/end">
   <!-- MotionLayout will track swipes relative to this view -->
   <OnSwipe motion:touchAnchorId="@id/red_star" />
</Transition>

OnSwipe 包含几个属性,最重要的是 touchAnchorId

  • touchAnchorId 是所跟踪的视图,它会通过移动来响应轻触操作。MotionLayout 会将该视图保持在与快速滑动的手指相同的距离。
  • touchAnchorSide 用于确定应跟踪视图的哪一侧。对于要调整大小、遵循复杂路径或一侧的移动速度比另一侧快的视图来说,这非常重要。
  • dragDirection 用于确定可对该动画效果产生影响的方向(上、下、左或右)。

MotionLayout 监听拖动事件时,系统将在 MotionLayout 视图(而不是由 touchAnchorId 指定的视图)上注册相应监听器。当用户在屏幕上的任意位置开始某项手势操作时,MotionLayout 会让用户的手指与 touchAnchorId 视图的 touchAnchorSide 之间的距离保持恒定。例如,如果用户轻触的位置与锚定侧的距离为 100dp,那么会在整个动画播放期间,MotionLayout 都会让这一侧与用户的手指保持 100dp 的距离。

试试看

  1. 再次运行该应用,然后打开第 2 步对应的屏幕。您会看到动画。
  2. 试着在动画播放过程中“快速滑动”或松开手指,了解 MotionLayout 如何显示流畅的基于物理特性的动画!

fefcdd690a0dcaec.gif

MotionLayout 可以使用 ConstraintLayout 中的功能在截然不同的设计之间添加动画,以便打造丰富的效果。

在该动画中,3 个视图在起始时都以其父级为参照物,定位在屏幕底部。结束时,3 个视图是以链中的 @id/credits 为参照物进行定位的。

尽管布局截然不同,但 MotionLayout 会在起始位置和结束位置之间创建流畅的动画。

5. 修改路径

在这一步,您将构建一个动画,使其在动画播放期间遵循复杂的路径,并在运动期间让所有者信息以动画方式显示。MotionLayout 可以使用 KeyPosition 修改视图在起始位置和结束位置之间采用的路径。

第 1 步:探索现有代码

  1. 打开 layout/activity_step3.xmlxml/step3.xml 可查看现有的布局和运动场景。ImageViewTextView 用于显示月亮和赠金文本。
  2. 打开动态场景文件 (xml/step3.xml)。您会看到,定义了从 @id/start@id/endTransition。该动画使用两个 ConstraintSets 将月球图片从屏幕左下角移动到屏幕右下角。随着月球移动,致谢文字从 alpha="0.0" 淡入到 alpha="1.0"
  3. 立即运行该应用,然后选择第 3 步。点击月球后,您将看到月球自始至终遵循直线路径(或直线)。

第 2 步:启用路径调试功能

在将弧形路径添加到月球的运动之前,最好在 MotionLayout 中启用路径调试功能。

为了帮助您使用 MotionLayout 开发复杂的动画,您可以绘制每个视图的动画路径。如果您要直观呈现动画,以及对运动的小细节进行微调的话,这会很有用。

  1. 如需启用调试路径功能,请打开 layout/activity_step3.xml,然后将 motion:motionDebug="SHOW_PATH" 添加到 MotionLayout 标记。

activity_step3.xml

<!-- Add motion:motionDebug="SHOW_PATH" -->

<androidx.constraintlayout.motion.widget.MotionLayout
       ...
       motion:motionDebug="SHOW_PATH" >

启用路径调试功能后,如果您再次运行该应用,就会发现系统能够使用虚线直观呈现所有视图的路径。

23bbb604f456f65c

  • 圆圈代表视图的起始位置或结束位置。
  • 线代表视图的路径。
  • 菱形代表用于修改路径的 KeyPosition

例如,在该动画中,中间圆圈代表所有者信息文字的位置。

第 3 步:修改路径

MotionLayout 中的所有动画均由起始和结束 ConstraintSet 定义,后者用于定义在动画启动前和动画结束后屏幕的外观。默认情况下,MotionLayout 会在每个改变位置的视图的起始位置和结束位置之间绘制一条线性路径(直线)。

在此示例中,为了构建像月球弧一样的复杂路径,MotionLayout 使用 KeyPosition 来修改视图在起点和终点之间采用的路径。

  1. 打开 xml/step3.xml 并向场景添加 KeyPositionKeyPosition 标记位于 Transition 标记内。

eae4dae9a12d0410.png

step3.xml

<!-- TODO: Add KeyFrameSet and KeyPosition -->
<KeyFrameSet>
   <KeyPosition
           motion:framePosition="50"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.5"
   />
</KeyFrameSet>

KeyFrameSetTransition 的子级,也是在过渡期间应该应用的所有 KeyFrames(例如 KeyPosition)的集合。

由于 MotionLayout 会计算月亮在起始位置和结束位置之间的路径,因此它会根据 KeyFrameSet 中指定的 KeyPosition 来修改路径。您可以再次运行该应用,以查看这项操作会如何修改路径。

KeyPosition 有多个属性,用于描述它会如何修改路径。其中最重要的属性如下:

  • framePosition 是一个介于 0 到 100 之间的数字。它定义了在动画中应用该 KeyPosition 的时间,其中 1 代表动画播放到 1% 的位置,99 代表动画播放到 99% 的位置。因此,如果值为 50,您可以直接从中间应用它。
  • motionTarget 是被该 KeyPosition 修改路径的视图。
  • keyPositionType 是该 KeyPosition 修改路径的方式,它可以是 parentRelativepathRelativedeltaRelative(如下一步所述)。
  • percentX | percentY 是指在 framePosition 按多大百分比来修改路径(值介于 0.0 到 1.0 之间,允许使用负数值和大于 1 的值)。

您可以这样理解:“在framePosition修改motionTarget 的路径,即根据 keyPositionType 确定的坐标移动percentX percentY 。”

默认情况下,MotionLayout 会将因修改路径而产生的所有角设为圆角。如果您查看刚刚创建的动画,就会发现月亮会沿着弯曲的曲线路径移动。对于大多数动画,这正是您想要的,否则,您可以指定 curveFit 属性来对其进行自定义。

试试看

如果您再次运行该应用,就会看到这一步对应的动画效果。

46b179c01801f19e.gif

月亮会沿弧线移动,因为它要经过 Transition 中指定的 KeyPosition

<KeyPosition
       motion:framePosition="50"
       motion:motionTarget="@id/moon"
       motion:keyPositionType="parentRelative"
       motion:percentY="0.5"
/>

您可以将此 KeyPosition 解读为:“在 framePosition 50(动画播放到一半)处,根据 parentRelative(整个 MotionLayout)确定的坐标,通过 50% Y(在屏幕上向下移动)来修改 motionTarget @id/moon 的路径。”

因此,在动画播放到一半的位置时,月亮必须经过一个位于屏幕高度 50% 处的 KeyPosition。该 KeyPosition 没有对 X 轴的运动做任何修改,因此,月亮在水平方向上仍会从起始位置移到结束位置。MotionLayout 会确定一条流畅的路径,这个路径会在从起始位置移到结束位置的过程中经过该 KeyPosition

如果您仔细观察,就会发现所有者信息文字受月亮位置的约束。为什么它不在垂直方向上一起移动呢?

1c7cf779931e45cc.gif

<Constraint
       android:id="@id/credits"
       ...
       motion:layout_constraintBottom_toBottomOf="@id/moon"
       motion:layout_constraintTop_toTopOf="@id/moon"
/>

实际上,即使您修改了月亮采用的路径,月亮的起始位置和结束位置也根本不会在垂直方向上移动它。KeyPosition 不会修改起始位置或结束位置,因此系统会将所有者信息文字约束到月亮的最终结束位置。

如果您要让所有者信息与月亮一起移动,可以向所有者信息添加 KeyPosition,或修改 @id/credits 的约束条件。

在下一部分中,您将深入了解 MotionLayout 中不同类型的 keyPositionType

6. 了解 keyPositionType

在上一步,您使用 parentRelativekeyPosition 类型将路径偏移了屏幕的 50%。keyPositionType 属性用于确定 MotionLayout 将如何根据 percentXpercentY 来修改路径。

<KeyFrameSet>
   <KeyPosition
           motion:framePosition="50"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.5"
   />
</KeyFrameSet>

您可能会用到 3 种不同类型的 keyPositionparentRelativepathRelativedeltaRelative。指定类型将会更改用于计算 percentXpercentY 的坐标系。

什么是坐标系?

坐标系提供了指定空间中的点的方法。此外,坐标系也可用于描述屏幕上的位置。

MotionLayout 坐标系是笛卡尔坐标系。这意味着,它们具有由两条互相垂直的直线定义的 X 轴和 Y 轴。它们的主要区别在于 X 轴在屏幕上的位置(Y 轴始终与 X 轴垂直)。

MotionLayout 中的所有坐标系在 X 轴和 Y 轴上使用的值都介于 0.01.0 之间。它们允许使用负值和大于 1.0 的值。例如,如果 percentX 的值为 -2.0,则表示沿 X 轴的反方向移动两次。

如果以上内容听起来有点太像代数课,请查看以下图片!

parentRelative 坐标

a7b7568d46d9dec7.png

parentRelativekeyPositionType 使用与屏幕相同的坐标系。它定义了整个 MotionLayout 左上角的 (0, 0),以及右下角的 (1, 1)

您可以随时使用 parentRelative 来制作在整个 MotionLayout 中移动的动画,例如该示例中的月弧。

不过,如果您要相对于运动来修改路径(例如,让路径略微弯曲),则最好使用另外两个坐标系。

deltaRelative 坐标

5680bf553627416c

增量是一个关于变化的数学术语,因此 deltaRelative 是表示“相对变化”的一种方式。在 deltaRelative 坐标中,(0,0) 是视图的起始位置,(1,1) 是结束位置。X 轴和 Y 轴与屏幕对齐。

X 轴在屏幕上始终是水平的,而 Y 轴在屏幕上始终是垂直的。与 parentRelative 相比,主要区别在于此类坐标仅描述视图移动将要经过的部分屏幕。

deltaRelative 是一种非常适合用于单独控制水平或垂直运动的坐标系。例如,您可以创建一个动画,使其在垂直 (Y) 方向上仅完成 50% 的移动,然后在水平方向 (X) 上继续以动画方式显示。

pathRelative 坐标

f3aaadaac8b4a93f.png

MotionLayout 中的最后一个坐标系是 pathRelative。它与前两个坐标系截然不同,因为 X 轴会遵循从起始位置到结束位置的动画路径。因此,(0,0) 是起始位置,(1,0) 是结束位置。

为什么要使用这个坐标系?乍一看很奇怪,尤其是因为该坐标系甚至未与屏幕坐标系对齐。

实际上,pathRelative 在以下几个方面非常有用。

  • 在部分动画内容播放期间让某个视图加速、减速或停止。由于 X 维度将始终与视图采用的路径保持完全一致,因此,您可以使用 pathRelative KeyPosition 来更改相应路径中的特定点所达到的 framePosition。因此,如果 framePosition="50"KeyPosition 且采用 percentX="0.1",则会让动画使用 50% 的时间来传输动画的前 10% 的内容。
  • 向路径添加细微的弧线。由于 Y 轴始终与运动垂直,因此如果更改 Y 轴,就会将路径更改为曲线(相对于整体运动)。
  • deltaRelative 不起作用时添加第二个维度。对于完全水平和垂直的运动,deltaRelative 只会创建一个有用的维度。不过,pathRelative 始终会创建可用的 X 和 Y 坐标。

在下一步,您将学习如何使用多个 KeyPosition 构建更复杂的路径。

7. 构建复杂路径

看一下您在上一步中构建的动画,它确实能创建一个流畅的曲线,但曲线的形状还可以“更像月亮”。

使用多个 KeyPosition 元素修改路径

MotionLayout 可以根据需要定义任意数量的 KeyPosition 以进一步修改路径,从而实现任何运动。对于该动画,您将构建一个弧线,但如果您愿意,您可以让月亮在屏幕中间上下挑动。

  1. 打开 xml/step4.xml。您会发现该视图具有与上一步相同的视图和 KeyFrame 部分。
  2. 如需使曲线的顶部圆滑,请向 @id/moon 的路径再添加两个 KeyPositions,一个在到达顶部之前,另一个在之后。

500b5ac2db48ef87

step4.xml

<!-- TODO: Add two more KeyPositions to the KeyFrameSet here -->
<KeyPosition
       motion:framePosition="25"
       motion:motionTarget="@id/moon"
       motion:keyPositionType="parentRelative"
       motion:percentY="0.6"
/>
<KeyPosition
       motion:framePosition="75"
       motion:motionTarget="@id/moon"
       motion:keyPositionType="parentRelative"
       motion:percentY="0.6"
/>

系统会在动画播放到 25% 和 75% 的位置时分别应用这两个 KeyPositions,并让 @id/moon 经过距屏幕顶部 60% 的路径。结合现有的 50% 的 KeyPosition,即可创建一条流畅的弧线,并让月亮沿该弧线移动。

MotionLayout 中,您可以根据需要添加任意数量的 KeyPositions,以实现所需的动画路径。MotionLayout 会在指定的 framePosition 上应用每个 KeyPosition,并确定如何创建经过所有 KeyPositions 的流畅的运动。

试试看

  1. 再次运行该应用。转到第 4 步,查看动画的实际效果。当您点击月球时,月球会沿着路径从头到尾经过 KeyFrameSet 中指定的每个 KeyPosition

自行探索

在继续使用其他类型的 KeyFrame 之前,请尝试向 KeyFrameSet 添加更多 KeyPositions,看看您可以只使用 KeyPosition 创建什么样的效果。

以下示例说明了如何构建在动画播放期间能够来回移动的复杂路径。

cd9faaffde3dfef.png

step4.xml

<!-- Complex paths example: Dancing moon -->
<KeyFrameSet>
   <KeyPosition
           motion:framePosition="25"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.6"
           motion:percentX="0.1"
   />
   <KeyPosition
           motion:framePosition="50"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.5"
           motion:percentX="0.3"
   />
   <KeyPosition
           motion:framePosition="75"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.6"
           motion:percentX="0.1"
   />
</KeyFrameSet>

探索完 KeyPosition 之后,您将在下一步中学习其他类型的 KeyFrames

8. 在运动过程中更改属性

构建动态动画通常意味着,要随着动画的播放不断更改视图的 sizerotationalphaMotionLayout 支持使用 KeyAttribute 为任何视图上的许多属性添加动画效果。

在这一步,您将使用 KeyAttribute 让月亮进行缩放和旋转。此外,您还将使用 KeyAttribute 将文字的显示时间延迟到月亮的移动过程接近完成时。

第 1 步:使用 KeyAttribute 调整大小和进行旋转

  1. 打开 xml/step5.xml,其中包含的动画与您在上一步中构建的相同。为了丰富多样,此屏幕使用不同的太空图片作为背景。
  2. 为了让月亮实现放大和旋转,请在 KeyFrameSet 中的 keyFrame="50"keyFrame="100" 处添加两个 KeyAttribute 标记。

bbae524a2898569.png

step5.xml

<!-- TODO: Add KeyAttributes to rotate and resize @id/moon -->

<KeyAttribute
       motion:framePosition="50"
       motion:motionTarget="@id/moon"
       android:scaleY="2.0"
       android:scaleX="2.0"
       android:rotation="-360"
/>
<KeyAttribute
       motion:framePosition="100"
       motion:motionTarget="@id/moon"
       android:rotation="-720"
/>

这两个 KeyAttributes 分别应用于动画播放到 50% 和 100% 的位置。第一个 KeyAttribute 应用于动画播放到 50% 的位置,它将发生在弧线的顶部,并让视图放大一倍,同时旋转 -360 度(或一整圈)。第二个 KeyAttribute 会完成第二次旋转(角度为 -720 度,即两整圈),并将视图缩小为正常大小,因为 scaleXscaleY 的值默认为 1.0。

KeyPosition 一样,KeyAttribute 使用 framePositionmotionTarget 来指定何时应用 KeyFrame 以及要修改哪个视图。MotionLayout 会在 KeyPositions 之间插值,以创建流畅的动画。

KeyAttributes 支持可应用于所有视图的属性。它们支持更改 visibilityalphaelevation 等基本属性。此外,您还可以像完成以上设置时一样更改旋转角度,使用 rotateXrotateY 让视图在 3 个维度上旋转,使用 scaleXscaleY 缩放视图的大小,或平移视图在 X 轴、Y 轴 或 Z 轴上的位置。

第 2 步:延迟所有者信息的显示时间

这一步的目的之一是更新动画,让所有者信息文字在动画快播放完毕时再显示。

  1. 若要延迟所有者信息的显示时间,请再定义一个 KeyAttribute,用于确保 alphakeyPosition="85" 之前一直为 0。MotionLayout 仍会从 Alpha 0 平滑过渡到 100 Alpha,但会在动画的最后 15% 过渡。

step5.xml

<!-- TODO: Add KeyAttribute to delay the appearance of @id/credits -->

<KeyAttribute
       motion:framePosition="85"
       motion:motionTarget="@id/credits"
       android:alpha="0.0"
/>

KeyAttribute 可让 @id/creditsalpha 在动画的前 85% 的内容中一直为 0.0。由于它的起始 alpha 值为 0,因此这意味着它在动画的前 85% 的内容中是不可见的。

KeyAttribute 的最终效果,就是让所有者信息在动画快播放完毕时显示。这样一来,所有者信息的显示就会与月亮落在屏幕右下角的运动协调一致。

通过让一个视图的动画延迟播放,同时让另一个视图像这样移动,您可以构建出让用户有动态感的令人惊艳的动画。

试试看

  1. 再次运行该应用,然后转到第 5 步,查看动画的实际效果。如果您点击月亮,它会按照从起始位置到结束位置的路径移动,并经过 KeyFrameSet 中指定的每个 KeyAttribute

2f4bfdd681c1fa98.gif

由于您让月亮旋转了两整圈,因此,月亮现在会完成两次后空翻,并且所有者信息会延迟到动画快播放完毕时再显示。

自行探索

在继续执行最终类型的 KeyFrame 之前,请尝试修改 KeyAttributes 中的其他标准属性。例如,尝试将 rotation 更改为 rotationX,以查看其生成的动画。

下面列出了您可以尝试的标准属性:

  • android:visibility
  • android:alpha
  • android:elevation
  • android:rotation
  • android:rotationX
  • android:rotationY
  • android:scaleX
  • android:scaleY
  • android:translationX
  • android:translationY
  • android:translationZ

9. 更改自定义属性

丰富的动画效果涉及更改视图的颜色或其他属性。虽然 MotionLayout 可以使用 KeyAttribute 更改上一个任务中列出的所有标准属性,但您要使用 CustomAttribute 来指定所有其他属性。

CustomAttribute 可用于设置任何具有 setter 的值。例如,您可以使用 CustomAttribute 在 View 上设置 backgroundColorMotionLayout 将使用反射查找 setter,然后反复调用它,为视图添加动画效果。

在此步骤中,您将使用 CustomAttribute 在月球上设置 colorFilter 属性,以构建如下所示的动画。

5fb6792126a09fda.gif

定义自定义属性

  1. 首先,打开 xml/step6.xml,其中包含的动画与您在上一步中构建的相同。
  2. 若要让月亮更改颜色,请在 KeyFrameSet 中的 keyFrame="0"keyFrame="50"keyFrame="100". 处添加两个包含 CustomAttributeKeyAttribute

214699d5fdd956da

step6.xml

<!-- TODO: Add Custom attributes here -->
<KeyAttribute
       motion:framePosition="0"
       motion:motionTarget="@id/moon">
   <CustomAttribute
           motion:attributeName="colorFilter"
           motion:customColorValue="#FFFFFF"
   />
</KeyAttribute>
<KeyAttribute
       motion:framePosition="50"
       motion:motionTarget="@id/moon">
   <CustomAttribute
           motion:attributeName="colorFilter"
           motion:customColorValue="#FFB612"
   />
</KeyAttribute>
<KeyAttribute
       motion:framePosition="100"
       motion:motionTarget="@id/moon">
   <CustomAttribute
           motion:attributeName="colorFilter"
           motion:customColorValue="#FFFFFF"
   />
</KeyAttribute>

您要在 KeyAttribute 中添加 CustomAttributeCustomAttribute 将应用于 KeyAttribute 指定的 framePosition

CustomAttribute 中,您必须指定一个 attributeName 和一个要设置的值。

  • motion:attributeName 是这个自定义属性将要调用的 setter 的名称。在此示例中,系统将对 Drawable 调用 setColorFilter
  • motion:custom*Value 是名称中注明的类型的自定义值,在该示例中,自定义值是指定的颜色。

自定义值可以是以下任一类型:

  • 颜色
  • 整数
  • 浮点数
  • 字符串
  • 维度
  • 布尔值

使用此 API,MotionLayout 可以为任何在任意视图上提供 setter 的任何对象添加动画效果。

试试看

  1. 再次运行该应用,然后转到第 6 步,查看动画的实际效果。如果您点击月亮,它会按照从起始位置到结束位置的路径移动,并经过 KeyFrameSet 中指定的每个 KeyAttribute

5fb6792126a09fda.gif

您添加更多 KeyFrames 后,MotionLayout 会将月球的路径从直线更改为复杂的曲线,同时在动画播放过程中添加两次翻转、调整大小和颜色变化。

在真实的动画中,您通常需要同时为多个视图添加动画效果,以控制这些视图沿不同路径以不同速度移动。通过为每个视图指定不同的 KeyFrame,您可以使用 MotionLayout 编排丰富的动画,以为多个视图添加动画效果。

10. 拖动事件和复杂路径

在此步骤中,您将了解如何将 OnSwipe 与复杂的路径结合使用。到目前为止,月亮的动画效果一直是由 OnClick 监听器触发的,并会在固定的时长内运行。

若要使用 OnSwipe 控制采用复杂路径的动画(例如您在前几个步骤中创建的月亮的动画),您必须了解 OnSwipe 的工作原理。

第 1 步:探索 OnSwipe 行为

  1. 打开 xml/step7.xml 并找到现有的 OnSwipe 声明。

step7.xml

<!-- Fix OnSwipe by changing touchAnchorSide 

<OnSwipe
       motion:touchAnchorId="@id/moon"
       motion:touchAnchorSide="bottom"
/>
  1. 在您的设备上运行该应用,然后转到第 7 步。看看您能否通过沿着弧线路径拖动月亮来生成顺畅的动画。

当您运行该动画时,其效果并不理想。当月亮到达弧线的顶部后,它就会开始来回跳动。

ed96e3674854a548.gif

若要理解这个错误,您要考虑当用户轻触弧线顶部正下方时会发生什么情况。由于 OnSwipe 标记具有 motion:touchAnchorSide="bottom",因此 MotionLayout 会在整个动画播放期间尝试让手指与视图底部之间的距离保持恒定。

但是,由于月亮底部并非始终朝同一方向移动,它会先上移,然后再往回下移,因此,用户的操作位置一越过弧线顶部,MotionLayout 就会无法确定要如何响应。考虑到这一点,由于您要跟踪月亮的底部,因此当用户轻触此处时,应该将它放置到哪个位置呢?

56cd575c5c77eddd

第 2 步:使用右侧

为避免出现此类错误,请务必始终选择在整个动画播放时长中始终朝一个方向移动的 touchAnchorIdtouchAnchorSide

在该动画中,月亮的 right 侧和 left 侧都会朝一个方向在屏幕上移动。

不过,bottomtop 都会反转方向。当跟踪它们时,如果它们更改方向,OnSwipe 就会困惑。

  1. 若要让该动画遵循触摸事件播放,请将 touchAnchorSide 更改为 right

step7.xml

<!-- Fix OnSwipe by changing touchAnchorSide 

<OnSwipe
       motion:touchAnchorId="@id/moon"
       motion:touchAnchorSide="right"
/>

第 3 步:使用 DragDirection

您还可以将 dragDirectiontouchAnchorSide 结合使用,使一侧所跟踪的方向与正常跟踪的方向不同。此时,仍然务必要让 touchAnchorSide 仅朝一个方向移动,但您可以指示 MotionLayout 跟踪哪个方向。例如,您可以保留 touchAnchorSide="bottom",但添加 dragDirection="dragRight"。这会导致 MotionLayout 跟踪视图底部的位置,但只有在向右移动时才考虑这个位置(它会忽略垂直运动)。因此,即使底部上下移动,它仍然会使用 OnSwipe 正确地添加动画效果。

  1. 更新 OnSwipe 以便正确地跟踪月亮的运动。

step7.xml

<!-- Using dragDirection to control the direction of drag tracking 

<OnSwipe
       motion:touchAnchorId="@id/moon"
       motion:touchAnchorSide="bottom"
       motion:dragDirection="dragRight"
/>

试试看

  1. 再次运行该应用,然后尝试沿整个路径拖动月亮。尽管它遵循复杂的弧线,但 MotionLayout 也能继续播放动画以响应滑动事件。

5458dff382261427.gif

11. 使用代码运行运动

MotionLayout 可与 CoordinatorLayout 一起用于构建丰富的动画效果。在这一步,您将使用 MotionLayout 构建一个可收起的标头。

第 1 步:探索现有代码

  1. 首先,打开 layout/activity_step8.xml
  2. layout/activity_step8.xml 中,您会看到已构建完成且可以运行的 CoordinatorLayoutAppBarLayout

activity_step8.xml

<androidx.coordinatorlayout.widget.CoordinatorLayout
       ...>
   <com.google.android.material.appbar.AppBarLayout
           android:id="@+id/appbar_layout"
           android:layout_width="match_parent"
           android:layout_height="180dp">
       <androidx.constraintlayout.motion.widget.MotionLayout
               android:id="@+id/motion_layout"
               ... >
           ...
       </androidx.constraintlayout.motion.widget.MotionLayout>
   </com.google.android.material.appbar.AppBarLayout>
  
   <androidx.core.widget.NestedScrollView
           ...
           motion:layout_behavior="@string/appbar_scrolling_view_behavior" >
           ...
   </androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

此布局使用 CoordinatorLayoutNestedScrollViewAppBarLayout 之间共享滚动信息。因此,当 NestedScrollView 向上滚动时,它会将相应变化情况告知 AppBarLayout。在 Android 上,您可以采用这种方式来实现类似的折叠式工具栏,即文字的滚动将与标头的收起“协调一致”。

@id/motion_layout 指向的运动场景与上一步中的运动场景类似。不过,我们移除了 OnSwipe 声明,以使其能够与 CoordinatorLayout 配合使用。

  1. 运行该应用,然后转到第 8 步。您会发现,当您滚动文字时,月亮不会移动。

第 2 步:让 MotionLayout 滚动

  1. 如需使 MotionLayout 视图在 NestedScrollView 滚动后立即滚动,请将 motion:minHeightmotion:layout_scrollFlags 添加到 MotionLayout

activity_step8.xml

<!-- Add minHeight and layout_scrollFlags to the MotionLayout -->

<androidx.constraintlayout.motion.widget.MotionLayout
       android:id="@+id/motion_layout"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       motion:layoutDescription="@xml/step8"
       motion:motionDebug="SHOW_PATH"
       android:minHeight="80dp"
       motion:layout_scrollFlags="scroll|enterAlways|snap|exitUntilCollapsed"  >
  1. 再次运行该应用,然后转到第 8 步。您会看到 MotionLayout 随着向上滚动而收起。不过,动画还无法基于滚动行为进行播放。

第 3 步:使用代码移动运动场景

  1. 打开 Step8Activity.kt。修改 coordinateMotion() 函数,以将滚动位置的变化情况告知 MotionLayout

Step8Activity.kt

// TODO: set progress of MotionLayout based on an AppBarLayout.OnOffsetChangedListener

private fun coordinateMotion() {
    val appBarLayout: AppBarLayout = findViewById(R.id.appbar_layout)
    val motionLayout: MotionLayout = findViewById(R.id.motion_layout)

    val listener = AppBarLayout.OnOffsetChangedListener { unused, verticalOffset ->
        val seekPosition = -verticalOffset / appBarLayout.totalScrollRange.toFloat()
        motionLayout.progress = seekPosition
    }

    appBarLayout.addOnOffsetChangedListener(listener)
}

以上代码会注册一个 OnOffsetChangedListener,每当用户使用当前滚动偏移量进行滚动时,系统都会调用它。

MotionLayout 支持通过设置进度属性来定位其过渡情况。如需在 verticalOffset 与百分比进度之间进行转换,请用总滚动范围除以相应值。

试试看

  1. 再次部署该应用,并运行第 8 步的动画。您会发现 MotionLayout 会根据滚动位置来播放动画。

ee5ce4d9e33a59ca.gif

您可以使用 MotionLayout 来构建自定义的动态折叠式工具栏动画。通过使用 KeyFrames 序列,您可以实现非常精彩的效果。

12. 恭喜

此 Codelab 介绍了 MotionLayout 的基本 API。

如需查看 MotionLayout 的更多实际应用示例,请参阅官方示例。请务必查看相关文档

了解详情

MotionLayout 还支持此 Codelab 中未涵盖的更多功能,例如 KeyCycle,(可让您使用重复的周期来控制路径或属性)和 KeyTimeCycle,(可让您根据时钟时间来添加动画)。如需查看这两项功能的示例,请参阅相关范例。

如需查看指向本课程中其他 Codelab 的链接,请参阅“使用 Kotlin 进行高级 Android 开发”Codelab 着陆页