• 日常搜索
  • 百度一下
  • Google
  • 在线工具
  • 搜转载

使用MotionLayout为Android创建动画

由于其卓越的多功能性,该constraintLayout 小部件已成为 android 应用程序开发人员布局的“瑞士军刀”。然而,虽然可以向其内容添加复杂的动画,但可能非常耗时。这就是谷歌MotionLayout 在 I/O 2018 中引入这个小部件的原因。

该MotionLayout 小部件现在是 Android 支持库的一部分,它扩展了该 ConstraintLayout 小部件。它是一个独特的小部件,允许您仅使用 XML 以声明方式为其内容设置动画。此外,它还提供对其所有动画的细粒度控制。

在本教程中,我将向您展示如何将其添加到您的 Android Studio 项目中并使用它创建一些不同的动画。

先决条件

要学习本教程,您需要:

  • Android Studio 3.1.3 或更高版本

  • 运行 Android api级别 21 或更高版本的设备或模拟器

  • ConstraintLayout 对小部件的基本了解

1.添加依赖

为了能够MotionLayout 在您的 Android Studio 项目中使用该小部件,您必须将最新版本的 Constraint Layout 支持库作为 implementation 依赖项。此外,为了避免版本冲突,请确保包含 v7 appcompat 支持库的最新稳定版本的依赖项。

因此,将以下代码添加到app 模块的build.gradle 文件中:

implementation 'com.android.support:appcompat-v7:27.0.2'
implementation 'com.android.support.constraint:constraint-layout:2.0.0-alpha1'

2.定义布局

小MotionLayout 部件可以做小部件可以做的所有事情ConstraintLayout 。因此,您可以自由地将后者的任何实例替换为前者。但是,现在,我建议您创建一个新的布局 XML 文件并将MotionLayout 小部件作为根元素添加到其中。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.motion.MotionLayout
    xmlns:android="https://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"
    android:id="@+id/motion_container">

    <!-- More code here -->

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

在本教程中,我们将为小部件设置动画ImageView 。因此,将其添加为布局的第一个子项。

<ImageView
    android:id="@+id/actor"
    app:srcCompat="@color/colorAccent"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

您可以自由使用任何可绘制对象作为ImageView 小部件的来源。在上面的代码中,我使用了可绘制的颜色。

接下来,添加一个按钮,您可以按下该按钮来启动动画。以下代码向您展示了如何将其放置在布局的中心:

<Button
    android:id="@+id/button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Press Me"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    android:onClick="start"/>

此外,要监视动画的进度,SeekBar 请在布局中添加一个小部件并将其放置在按钮下方。就是这样:

<SeekBar
    android:layout_width="100dp"
    android:layout_height="wrap_content"
    app:layout_constraintTop_toBottomOf="@+id/button"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    android:layout_marginTop="10dp"
    android:id="@+id/seekbar"/>

最后,因为有一个与按钮相关联的点击事件处理程序,请确保在您的活动中定义它。

fun start(v: View) {
    // More code here        
}

3.创建运动场景

ImageView 您可能已经注意到,我们在定义布局时没有向小部件添加任何约束。那是因为我们会将它们添加到运动场景中。运动场景是一个 XML 文件,其中包含有关您要使用MotionLayout 小部件创建的动画的详细信息。

要创建新的运动场景,请创建一个 XML 资源文件并向其中添加一个 MotionScene 元素。

<?xml version="1.0" encoding="utf-8"?>
<MotionScene
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!-- More code here -->
    
</MotionScene>

运动场景包含ConstraintSet 指定必须在动画中不同点应用于小部件的约束的元素。运动场景文件通常包含两个约束集:一个用于动画的开始,一个用于结束。

以下代码向您展示了如何创建两个约束集,这将帮助 MotionLayout 小部件将ImageView 小部件从屏幕的右下角移动到左上角:

<ConstraintSet android:id="@+id/starting_set">
    <Constraint android:id="@+id/actor"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        android:layout_width="60dp"
        android:layout_height="60dp"
        />
</ConstraintSet>

<ConstraintSet android:id="@+id/ending_set">
    <Constraint android:id="@+id/actor"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        android:layout_width="60dp"
        android:layout_height="60dp"
        />
</ConstraintSet>

请注意,每个ConstraintSet 元素必须始终指定所需的位置和所需的大小。这很重要,因为它将覆盖任何先前设置的布局信息。

为了帮助MotionLayout 小部件了解必须应用约束集的顺序,您接下来必须创建一个Transition 元素。通过使用其直观的命名 constraintSetStart 和constraintSetEnd 属性,您可以指定必须首先应用哪个集合,最后应用哪个集合。该Transition 元素还允许您指定动画的持续时间。

<Transition
    android:id="@+id/my_transition"
    app:constraintSetStart="@+id/starting_set"
    app:constraintSetEnd="@+id/ending_set"
    app:duration="2000">
    
</Transition>

至此,运动场景完成。但是,MotionLayout 小部件仍然没有意识到这一点。所以回到布局 XML 文件,layoutDescription 为小部件添加一个属性,并将其值设置为运动场景文件的名称。

如果您的运动场景文件的名称是my_scene.xml,那么您的MotionLayout 小部件现在应该如下所示:

<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:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layoutDescription="@xml/my_scene"
    android:id="@+id/motion_container">

    ...

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

4.开始动画

当您运行应用程序时,MotionLayout 小部件将自动应用在 元素constraintSetStart 属性中指定的约束集。Transition因此,要启动动画,您需要做的就是调用transitionToEnd() 小部件的方法。以下代码必须添加到您在前面步骤中创建的单击事件处理程序中,它向您展示了如何:

motion_container.transitionToEnd()

此时,如果您运行应用程序并按下按钮,您应该能够看到 ImageView 小部件在屏幕上平滑移动。

使用MotionLayout为Android创建动画  第1张

5.处理动画事件

通过将TransitionListener 对象附加到MotionLayout 小部件,您可以密切监视动画的进度。

motion_container.setTransitionListener(
    object: MotionLayout.TransitionListener {
        // More code here        
    }
)

该TransitionListener 接口有两个抽象方法,Android Studio 会自动为它们生成存根。

onTransitionCompleted()当从一个约束集到另一个约束集的转换完成时调用该方法。ImageView 现在,让我们通过调用其中的transitionToStart() 方法来使用它来重置小部件的约束 。

override fun onTransitionCompleted(motionLayout: MotionLayout?,
                                   currentId: Int) {
    if(currentId == R.id.ending_set) {
        // Return to original constraint set
        motion_container.transitionToStart()
    }
}

onTransitionChange() 每次动画进度发生变化时都会调用该方法。因此,进度是一个介于 0 和 1 之间的浮点数。以下代码向您展示了如何根据 动画的进度更新:SeekBar

override fun onTransitionChange(motionLayout: MotionLayout?,
                                startId: Int,
                                endId: Int,
                                progress: Float) {
    seekbar.progress = ceil(progress * 100).toInt()
}

继续并再次运行该应用程序,现在可以看到两个动画。

使用MotionLayout为Android创建动画  第2张

6.创建关键帧

在我们的动画中,ImageView 小部件沿着一条看起来像直线的路径移动。这是因为MotionLayout 小部件只有两个可以使用的点:起点,位于屏幕的右下角,以及终点,位于屏幕的左上角。如果你想改变路径的形状,你必须提供一些中间点,它们位于起点和终点之间。为此,您必须创建新的关键帧。

但是,在开始创建关键帧之前,您必须将一个KeyFrameSet 元素添加到 Transition 运动场景的元素中。在新元素内部,您可以自由创建任意数量的关键帧。

<KeyFrameSet android:id="@+id/my_keys">
    <!-- More code here -->
</KeyFrameSet>

该MotionLayout 小部件支持许多不同类型的关键帧。在本教程中,我们将只使用两种类型:Keyposition 框架和KeyCycle 框架。

KeyPosition 框架是帮助您改变路径形状的框架。创建它们时,请确保提供目标小部件的 ID、时间轴上的位置(可以是 0 到 100 之间的任意数字)以及指定为百分比的所需 X 或 Y 坐标。坐标可以相对于实际的 X 或 Y 轴,也可以相对于路径本身。

以下代码向您展示了如何创建两个关键帧来强制 ImageView 小部件遵循避免与按钮和搜索栏发生冲突的路径:

<KeyPosition
    app:target="@+id/actor"
    app:framePosition="30"
    app:type="deltaRelative"
    app:percentX="0.85" />
    
<KeyPosition
    app:target="@+id/actor"
    app:framePosition="60"
    app:type="deltaRelative"
    app:percentX="1" />

如果您现在运行该应用程序,您应该会看到如下所示的动画:

使用MotionLayout为Android创建动画  第3张

当然,您可以随意添加更多关键帧。例如,通过在时间线的末尾添加以下关键帧,您可以使ImageView 小部件遵循更加波浪形的路径:

<KeyPosition
    app:target="@+id/actor"
    app:framePosition="80"
    app:type="deltaRelative"
    app:percentX="0.5" />

通过将KeyCycle 帧与帧一起使用KeyPosition ,您可以为动画添加振荡。在创建它时,您必须再次提供目标小部件的 ID、沿时间线的位置以及必须来回摆动的所需属性值。此外,您必须通过提供详细信息(例如要使用的波形和波形周期)来配置振荡器。

下面的代码创建了一个KeyCycle 使用正弦波振荡器周期性地将ImageView 小部件旋转 50 度的框架:

<KeyCycle
    app:target="@+id/actor"
    app:framePosition="30"
    android:rotation="50"
    app:waveShape="sin"
    app:wavePeriod="1" />

再次运行应用程序时,您应该会看到如下所示的动画:

使用MotionLayout为Android创建动画  第4张

7.使动画小部件具有交互性

一直以来,您一直在按下一个按钮来启动动画。然而,这样的按钮并不总是必要的,因为MotionLayout 小部件允许您直接将触摸事件处理程序附加到正在动画的小部件上。目前,它支持点击和滑动事件。 

例如,您可以在运动场景 的元素中添加以下以小部件OnClick 为目标的 元素,以使按钮变得多余:ImageViewTransition

<OnClick
    app:target="@+id/actor"
    app:mode="transitionToEnd"/>

同样,您可以使用一个OnSwipe 元素来允许用户 ImageView 在屏幕上手动拖动小部件。创建元素时,您必须确保提供正确的拖动方向以及应充当拖动手柄的小部件一侧。

<OnSwipe
    app:touchAnchorId="@+id/actor"
    app:touchAnchorSide="top"
    app:dragDirection="dragUp" />

如果您再次运行该应用程序,您现在应该能够拖动ImageView 小部件。

使用MotionLayout为Android创建动画  第5张

结论

您现在知道如何使用MotionLayout 小部件快速将复杂的交互式动画添加到您的 Android 应用程序中。只要避免嵌套视图,您就可以确保动画在大多数设备上运行时不会出现任何延迟或抖动。


文章目录
  • 先决条件
  • 1.添加依赖
  • 2.定义布局
  • 3.创建运动场景
  • 4.开始动画
  • 5.处理动画事件
  • 6.创建关键帧
  • 7.使动画小部件具有交互性
  • 结论