属性动画概览

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

属性动画系统是一个强大的框架,几乎可以为任何事物添加动画。您可以定义动画来随时间改变任何对象属性,无论它是否绘制到屏幕上。属性动画在指定的时间长度内改变属性(对象中的一个字段)的值。要为某个事物添加动画,您需要指定要为其添加动画的对象属性,例如对象在屏幕上的位置、动画时长以及要在哪些值之间进行动画处理。

属性动画系统允许您定义动画的以下特性

  • 时长:您可以指定动画的时长。默认时长为 300 毫秒。
  • 时间插值:您可以指定如何根据动画当前已过去的时间来计算属性值。
  • 重复次数和行为:您可以指定动画在到达时长末尾时是否重复以及重复次数。您还可以指定是否希望动画反向播放。设置为反向播放会使动画先正向播放,然后反向播放,重复进行,直到达到重复次数。
  • Animator set:您可以将动画分组到逻辑集中,使其可以同时播放、按顺序播放或在指定延迟后播放。
  • 帧刷新延迟:您可以指定动画的帧刷新频率。默认设置为每 10 毫秒刷新一次,但应用刷新帧的速度最终取决于系统的整体繁忙程度以及系统处理底层定时器的速度。

要查看属性动画的完整示例,请参阅 GitHub 上 CustomTransition 示例中的 ChangeColor 类。

属性动画的工作原理

首先,我们通过一个简单的示例来了解动画的工作原理。图 1 描绘了一个假设的对象,它通过其 x 属性进行动画处理,该属性代表其在屏幕上的水平位置。动画时长设置为 40 毫秒,移动距离为 40 像素。每 10 毫秒(这是默认的帧刷新率),对象水平移动 10 像素。在 40 毫秒结束时,动画停止,对象最终位于水平位置 40。这是线性插值动画的一个示例,意味着对象以恒定速度移动。

图 1. 线性动画示例

您还可以指定动画进行非线性插值。图 2 描绘了一个假设的对象,它在动画开始时加速,在动画结束时减速。对象仍然在 40 毫秒内移动 40 像素,但不是线性移动。在开始时,此动画加速直到中途点,然后从中途点减速直到动画结束。如图 2 所示,在动画开始和结束时移动的距离小于中间部分的距离。

图 2. 非线性动画示例

让我们详细了解一下属性动画系统的重要组件如何计算上述图示的动画。图 3 描绘了主要类如何协同工作。

图 3. 动画的计算方式

ValueAnimator 对象跟踪动画的计时,例如动画已运行的时长以及正在为其添加动画的属性的当前值。

ValueAnimator 封装了一个定义动画插值的 TimeInterpolator,以及一个定义如何计算正在进行动画处理的属性值的 TypeEvaluator。例如,在图 2 中,使用的 TimeInterpolator 将是 AccelerateDecelerateInterpolator,而 TypeEvaluator 将是 IntEvaluator

要开始动画,请创建 ValueAnimator 并为其提供要为其添加动画的属性的起始值和结束值,以及动画的时长。当您调用 start() 时,动画开始。在整个动画过程中,ValueAnimator 根据动画时长和已过去的时间计算一个介于 0 和 1 之间的已过去分数。已过去分数表示动画完成的时间百分比,0 表示 0%,1 表示 100%。例如,在图 1 中,t = 10 毫秒时的已过去分数为 0.25,因为总时长为 t = 40 毫秒。

ValueAnimator 完成已过去分数的计算后,它会调用当前设置的 TimeInterpolator 来计算一个插值分数。插值分数将已过去分数映射到一个新的分数,该分数考虑了设置的时间插值。例如,在图 2 中,由于动画缓慢加速,在 t = 10 毫秒时,插值分数(约 0.15)小于已过去分数(0.25)。在图 1 中,插值分数始终等于已过去分数。

当计算出插值分数后,ValueAnimator 会调用相应的 TypeEvaluator,根据插值分数、动画的起始值和结束值来计算正在进行动画处理的属性的值。例如,在图 2 中,t = 10 毫秒时的插值分数为 0.15,因此该时间点的属性值将为 0.15 × (40 - 0),即 6。

属性动画与视图动画的区别

视图动画系统仅提供为 View 对象添加动画的功能,因此如果要为非 View 对象添加动画,则必须自己实现相应的代码。视图动画系统还受到限制,因为它只公开了 View 对象的一些方面以供动画处理,例如视图的缩放和旋转,但不支持背景颜色等。

视图动画系统的另一个缺点是它只修改了视图的绘制位置,而不是视图本身。例如,如果您将按钮动画化以使其在屏幕上移动,按钮会正确绘制,但您可以点击按钮的实际位置不会改变,因此您必须自己实现逻辑来处理这种情况。

借助属性动画系统,这些限制被完全消除,您可以为任何对象(视图和非视图)的任何属性添加动画,并且对象本身会实际修改。属性动画系统在执行动画的方式上也更加强大。从高层次上讲,您可以为要添加动画的属性(例如颜色、位置或大小)分配动画器,并可以定义动画的各个方面,例如插值和多个动画器的同步。

然而,视图动画系统设置所需的时间更少,编写的代码也更少。如果视图动画能够实现您的所有需求,或者您现有的代码已经按照您想要的方式工作,则无需使用属性动画系统。如果出现用例,针对不同情况使用两种动画系统也可能是合理的。

API 概览

您可以在 android.animation 中找到大多数属性动画系统的 API。由于视图动画系统已经在 android.view.animation 中定义了许多插值器,因此您也可以在属性动画系统中使用这些插值器。下表描述了属性动画系统的主要组件。

Animator 类提供了创建动画的基本结构。您通常不会直接使用此类,因为它只提供了必须扩展才能完全支持动画值的最少功能。以下子类扩展了 Animator

表 1. Animators

说明
ValueAnimator 属性动画的主要计时引擎,同时计算要进行动画处理的属性的值。它拥有计算动画值的所有核心功能,并包含每个动画的计时详情、动画是否重复的信息、接收更新事件的监听器以及设置自定义类型进行评估的能力。属性动画有两个部分:计算动画值以及在正在进行动画处理的对象和属性上设置这些值。ValueAnimator 不执行第二个部分,因此您必须侦听由 ValueAnimator 计算的值的更新,并使用自己的逻辑修改要进行动画处理的对象。有关详细信息,请参阅关于使用 ValueAnimator 进行动画处理的部分。
ObjectAnimator ValueAnimator 的子类,允许您设置目标对象和对象属性进行动画处理。此类在计算新的动画值时会相应地更新属性。您大多数时候都想使用 ObjectAnimator,因为它使得在目标对象上进行值动画处理的过程更加容易。然而,有时您希望直接使用 ValueAnimator,因为 ObjectAnimator 有一些额外的限制,例如要求目标对象上存在特定的访问器方法。
AnimatorSet 提供一种将动画分组在一起的机制,使其可以相互关联地运行。您可以设置动画同时播放、按顺序播放或在指定延迟后播放。有关详细信息,请参阅关于使用 Animator Set 编排多个动画的部分。

Evaluator 告诉属性动画系统如何计算给定属性的值。它们接收由 Animator 类提供的计时数据、动画的起始值和结束值,并基于这些数据计算属性的动画值。属性动画系统提供以下 Evaluator

表 2. Evaluator

类/接口 说明
IntEvaluator 用于计算 int 属性值的默认 Evaluator。
FloatEvaluator 用于计算 float 属性值的默认 Evaluator。
ArgbEvaluator 用于计算表示为十六进制值的颜色属性的默认 Evaluator。
TypeEvaluator 允许您创建自己的 Evaluator 的接口。如果要为不是 intfloat 或颜色的对象属性添加动画,则必须实现 TypeEvaluator 接口来指定如何计算对象属性的动画值。如果您希望以不同于默认行为的方式处理 intfloat 和颜色值,也可以为这些类型指定自定义的 TypeEvaluator。有关如何编写自定义 Evaluator 的详细信息,请参阅关于使用 TypeEvaluator 的部分。

时间插值器定义了动画中的特定值如何作为时间的函数进行计算。例如,您可以指定动画在整个过程中线性发生,这意味着动画始终均匀移动,或者您可以指定动画使用非线性时间,例如在动画开始时加速并在结束时减速。表 3 描述了 android.view.animation 中包含的插值器。如果这些都不符合您的需求,请实现 TimeInterpolator 接口并创建自己的插值器。有关如何编写自定义插值器的详细信息,请参阅使用插值器

表 3. 插值器

类/接口 说明
AccelerateDecelerateInterpolator 一种插值器,其变化速率开始和结束时缓慢,但在中间加速。
AccelerateInterpolator 一种插值器,其变化速率开始时缓慢,然后加速。
AnticipateInterpolator 一种插值器,其变化开始时向后,然后向前甩出。
AnticipateOvershootInterpolator 一种插值器,其变化开始时向后,向前甩出并超过目标值,然后最终返回到最终值。
BounceInterpolator 一种插值器,其变化在结束时反弹。
CycleInterpolator 一种插值器,其动画重复指定的循环次数。
DecelerateInterpolator 一种插值器,其变化速率开始时迅速,然后减速。
LinearInterpolator 一种插值器,其变化速率是恒定的。
OvershootInterpolator 一种插值器,其变化向前甩出并超过最后一个值,然后返回。
TimeInterpolator 允许您实现自己的插值器的接口。

使用 ValueAnimator 进行动画处理

ValueAnimator 类允许您通过指定一组 intfloat 或颜色值来在动画持续时间内为某种类型的值添加动画。您可以通过调用其工厂方法之一来获取 ValueAnimatorofInt()ofFloat()ofObject()。例如:

Kotlin

ValueAnimator.ofFloat(0f, 100f).apply {
    duration = 1000
    start()
}

Java

ValueAnimator animation = ValueAnimator.ofFloat(0f, 100f);
animation.setDuration(1000);
animation.start();

在此代码中,当 start() 方法运行时,ValueAnimator 开始计算动画的值,范围在 0 到 100 之间,时长为 1000 毫秒。

您还可以通过执行以下操作来指定要为其添加动画的自定义类型

Kotlin

ValueAnimator.ofObject(MyTypeEvaluator(), startPropertyValue, endPropertyValue).apply {
    duration = 1000
    start()
}

Java

ValueAnimator animation = ValueAnimator.ofObject(new MyTypeEvaluator(), startPropertyValue, endPropertyValue);
animation.setDuration(1000);
animation.start();

在此代码中,当 start() 方法运行时,ValueAnimator 开始使用 MyTypeEvaluator 提供的逻辑计算动画的值,范围在 startPropertyValueendPropertyValue 之间,时长为 1000 毫秒。

您可以通过向 ValueAnimator 对象添加 AnimatorUpdateListener 来使用动画的值,如以下代码所示

Kotlin

ValueAnimator.ofObject(...).apply {
    ...
    addUpdateListener { updatedAnimation ->
        // You can use the animated value in a property that uses the
        // same type as the animation. In this case, you can use the
        // float value in the translationX property.
        textView.translationX = updatedAnimation.animatedValue as Float
    }
    ...
}

Java

animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator updatedAnimation) {
        // You can use the animated value in a property that uses the
        // same type as the animation. In this case, you can use the
        // float value in the translationX property.
        float animatedValue = (float)updatedAnimation.getAnimatedValue();
        textView.setTranslationX(animatedValue);
    }
});

onAnimationUpdate() 方法中,您可以访问更新后的动画值,并将其用于某个视图的属性。有关监听器的详细信息,请参阅关于动画监听器的部分。

使用 ObjectAnimator 进行动画处理

ObjectAnimatorValueAnimator 的子类(在上一节中讨论过),它结合了 ValueAnimator 的计时引擎和值计算功能,以及为目标对象的命名属性添加动画的能力。这使得为任何对象添加动画变得容易得多,因为您不再需要实现 ValueAnimator.AnimatorUpdateListener,因为动画属性会自动更新。

实例化 ObjectAnimatorValueAnimator 类似,但您还需要指定对象及其属性的名称(作为 String)以及要在其之间进行动画处理的值

Kotlin

ObjectAnimator.ofFloat(textView, "translationX", 100f).apply {
    duration = 1000
    start()
}

Java

ObjectAnimator animation = ObjectAnimator.ofFloat(textView, "translationX", 100f);
animation.setDuration(1000);
animation.start();

要使 ObjectAnimator 正确更新属性,您必须执行以下操作

  • 您正在为其添加动画的对象属性必须具有 set<PropertyName>() 形式的 setter 函数(采用驼峰式命名法)。由于 ObjectAnimator 在动画过程中自动更新属性,因此它必须能够使用此 setter 方法访问该属性。例如,如果属性名称为 foo,则需要有一个 setFoo() 方法。如果此 setter 方法不存在,您有三个选项
    • 如果您有权限,请将 setter 方法添加到类中。
    • 使用一个您有权限修改的包装类,并让该包装类通过有效的 setter 方法接收值并将其转发给原始对象。
    • 改为使用 ValueAnimator
  • 如果您在某个 ObjectAnimator 工厂方法中为 values... 参数仅指定一个值,则假定该值为动画的结束值。因此,您正在为其添加动画的对象属性必须具有用于获取动画起始值的 getter 函数。getter 函数必须采用 get<PropertyName>() 形式。例如,如果属性名称为 foo,则需要有一个 getFoo() 方法。
  • 您正在为其添加动画的属性的 getter(如果需要)和 setter 方法必须与您指定给 ObjectAnimator 的起始值和结束值采用相同的类型。例如,如果您构建以下 ObjectAnimator,则必须有 targetObject.setPropName(float)targetObject.getPropName()
    ObjectAnimator.ofFloat(targetObject, "propName", 1f)
  • 取决于您正在为其添加动画的属性或对象,您可能需要在视图上调用 invalidate() 方法,以强制屏幕的该区域使用新的动画值重新绘制。您可以在 onAnimationUpdate() 回调中执行此操作。例如,为 Drawable 对象的颜色属性添加动画仅在该对象重新绘制自身时才会导致屏幕更新。视图上的所有属性 setter(例如 setAlpha()setTranslationX())都会正确地使视图失效,因此在使用新值调用这些方法时,您无需使视图失效。有关监听器的详细信息,请参阅关于动画监听器的部分。

使用 AnimatorSet 编排多个动画

在许多情况下,您希望播放依赖于另一个动画何时开始或结束的动画。Android 系统允许您将动画捆绑到 AnimatorSet 中,以便您可以指定动画是同时开始、按顺序开始还是在指定的延迟后开始。您还可以将 AnimatorSet 对象相互嵌套。

以下代码片段以下列方式播放以下 Animator 对象

  1. 播放 bounceAnim
  2. 同时播放 squashAnim1squashAnim2stretchAnim1stretchAnim2
  3. 播放 bounceBackAnim
  4. 播放 fadeAnim

Kotlin

val bouncer = AnimatorSet().apply {
    play(bounceAnim).before(squashAnim1)
    play(squashAnim1).with(squashAnim2)
    play(squashAnim1).with(stretchAnim1)
    play(squashAnim1).with(stretchAnim2)
    play(bounceBackAnim).after(stretchAnim2)
}
val fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f).apply {
    duration = 250
}
AnimatorSet().apply {
    play(bouncer).before(fadeAnim)
    start()
}

Java

AnimatorSet bouncer = new AnimatorSet();
bouncer.play(bounceAnim).before(squashAnim1);
bouncer.play(squashAnim1).with(squashAnim2);
bouncer.play(squashAnim1).with(stretchAnim1);
bouncer.play(squashAnim1).with(stretchAnim2);
bouncer.play(bounceBackAnim).after(stretchAnim2);
ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
fadeAnim.setDuration(250);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(bouncer).before(fadeAnim);
animatorSet.start();

动画监听器

您可以使用下述监听器来侦听动画持续期间的重要事件。

如果您不想实现 Animator.AnimatorListener 接口的所有方法,可以扩展 AnimatorListenerAdapter 类,而不是实现 Animator.AnimatorListener 接口。AnimatorListenerAdapter 类提供了方法的空实现,您可以选择覆盖这些方法。

例如,以下代码片段为 onAnimationEnd() 回调创建了一个 AnimatorListenerAdapter

Kotlin

ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f).apply {
    duration = 250
    addListener(object : AnimatorListenerAdapter() {
        override fun onAnimationEnd(animation: Animator) {
            balls.remove((animation as ObjectAnimator).target)
        }
    })
}

Java

ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
fadeAnim.setDuration(250);
fadeAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
    balls.remove(((ObjectAnimator)animation).getTarget());
}

为 ViewGroup 对象布局变化添加动画

属性动画系统提供了为 ViewGroup 对象的变化添加动画的功能,并提供了一种简便的方式来为 View 对象本身添加动画。

您可以使用 LayoutTransition 类为 ViewGroup 中的布局变化添加动画。当您向 ViewGroup 添加或从中移除视图,或当您使用 VISIBLEINVISIBLEGONE 调用视图的 setVisibility() 方法时,ViewGroup 中的视图可以经历出现和消失动画。当您添加或移除视图时,ViewGroup 中的其余视图也可以动画移动到新位置。您可以通过调用 setAnimator() 并传入带有以下 LayoutTransition 常量之一的 Animator 对象,在 LayoutTransition 对象中定义以下动画

  • APPEARING - 指示容器中正在出现的项目上运行的动画的标志。
  • CHANGE_APPEARING - 指示由于容器中出现新项目而发生变化的项目上运行的动画的标志。
  • DISAPPEARING - 指示容器中正在消失的项目上运行的动画的标志。
  • CHANGE_DISAPPEARING - 指示由于容器中有项目消失而发生变化的项目上运行的动画的标志。

您可以为这四种类型的事件定义自己的自定义动画,以自定义布局过渡的外观,或者仅告知动画系统使用默认动画。

要将 ViewGroup 的 android:animateLayoutchanges 属性设置为 true,请执行以下操作

<LinearLayout
    android:orientation="vertical"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:id="@+id/verticalContainer"
    android:animateLayoutChanges="true" />

将此属性设置为 true 会自动为添加到或从 ViewGroup 中移除的视图以及 ViewGroup 中剩余的视图添加动画。

使用 StateListAnimator 为视图状态变化添加动画

StateListAnimator 类允许您定义在视图状态变化时运行的动画器。此对象充当 Animator 对象的包装器,只要指定的视图状态(例如“按下”或“聚焦”)发生变化,就会调用该动画。

StateListAnimator 可以在 XML 资源中定义,其根元素为 <selector>,子元素为 <item>,每个 <item> 指定由 StateListAnimator 类定义的不同的视图状态。每个 <item> 都包含属性动画集的定义。

例如,以下文件创建一个状态列表动画器,在视图按下时更改视图的 x 和 y 比例

res/xml/animate_scale.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- the pressed state; increase x and y size to 150% -->
    <item android:state_pressed="true">
        <set>
            <objectAnimator android:propertyName="scaleX"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueTo="1.5"
                android:valueType="floatType"/>
            <objectAnimator android:propertyName="scaleY"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueTo="1.5"
                android:valueType="floatType"/>
        </set>
    </item>
    <!-- the default, non-pressed state; set x and y size to 100% -->
    <item android:state_pressed="false">
        <set>
            <objectAnimator android:propertyName="scaleX"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueTo="1"
                android:valueType="floatType"/>
            <objectAnimator android:propertyName="scaleY"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueTo="1"
                android:valueType="floatType"/>
        </set>
    </item>
</selector>

要将状态列表动画器附加到视图,请按如下所示添加 android:stateListAnimator 属性

<Button android:stateListAnimator="@xml/animate_scale"
        ... />

现在,当此按钮的状态发生变化时,将使用 animate_scale.xml 中定义的动画。

或者,改为在代码中将状态列表动画器分配给视图,请使用 AnimatorInflater.loadStateListAnimator() 方法,并使用 View.setStateListAnimator() 方法将动画器分配给您的视图。

或者,您可以选择不在状态变化之间为视图属性添加动画,而是使用 AnimatedStateListDrawable 在状态变化之间播放可绘制动画。Android 5.0 中的一些系统微件默认使用这些动画。以下示例展示了如何将 AnimatedStateListDrawable 定义为 XML 资源

<!-- res/drawable/myanimstatedrawable.xml -->
<animated-selector
    xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- provide a different drawable for each state-->
    <item android:id="@+id/pressed" android:drawable="@drawable/drawableP"
        android:state_pressed="true"/>
    <item android:id="@+id/focused" android:drawable="@drawable/drawableF"
        android:state_focused="true"/>
    <item android:id="@id/default"
        android:drawable="@drawable/drawableD"/>

    <!-- specify a transition -->
    <transition android:fromId="@+id/default" android:toId="@+id/pressed">
        <animation-list>
            <item android:duration="15" android:drawable="@drawable/dt1"/>
            <item android:duration="15" android:drawable="@drawable/dt2"/>
            ...
        </animation-list>
    </transition>
    ...
</animated-selector>

使用 TypeEvaluator

如果您想为 Android 系统未知类型添加动画,可以通过实现 TypeEvaluator 接口创建自己的 Evaluator。Android 系统已知的类型包括 intfloat 或颜色,这些类型由 IntEvaluatorFloatEvaluatorArgbEvaluator 类型 Evaluator 支持。

TypeEvaluator 接口中只有一个方法需要实现,即 evaluate() 方法。这允许您使用的动画器在动画当前点返回动画属性的适当值。FloatEvaluator 类演示了如何执行此操作

Kotlin

private class FloatEvaluator : TypeEvaluator<Any> {

    override fun evaluate(fraction: Float, startValue: Any, endValue: Any): Any {
        return (startValue as Number).toFloat().let { startFloat ->
            startFloat + fraction * ((endValue as Number).toFloat() - startFloat)
        }
    }

}

Java

public class FloatEvaluator implements TypeEvaluator {

    public Object evaluate(float fraction, Object startValue, Object endValue) {
        float startFloat = ((Number) startValue).floatValue();
        return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);
    }
}

注意:ValueAnimator(或 ObjectAnimator)运行时,它会计算动画的当前已过去分数(一个介于 0 和 1 之间的值),然后根据您使用的插值器计算其插值版本。插值分数是您的 TypeEvaluator 通过 fraction 参数接收到的值,因此在计算动画值时不必考虑插值器。

使用插值器

插值器定义了动画中的特定值如何作为时间的函数进行计算。例如,您可以指定动画在整个过程中线性发生,这意味着动画始终均匀移动,或者您可以指定动画使用非线性时间,例如在动画开始或结束时使用加速或减速。

动画系统中的插值器从动画器接收表示动画已过去时间的比例。插值器修改此比例以符合其旨在提供的动画类型。Android 系统在 android.view.animation package 中提供了一组常用的插值器。如果这些都不符合您的需求,您可以实现 TimeInterpolator 接口并创建自己的插值器。有关如何编写自定义插值器的详细信息,请参阅使用插值器

举例来说,下面比较了默认插值器 AccelerateDecelerateInterpolatorLinearInterpolator 如何计算插值分数。LinearInterpolator 对已过去分数没有影响。AccelerateDecelerateInterpolator 在动画开始时加速,在动画结束时减速。以下方法定义了这些插值器的逻辑

AccelerateDecelerateInterpolator

Kotlin

override fun getInterpolation(input: Float): Float =
        (Math.cos((input + 1) * Math.PI) / 2.0f).toFloat() + 0.5f

Java

@Override
public float getInterpolation(float input) {
    return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}

LinearInterpolator

Kotlin

override fun getInterpolation(input: Float): Float = input

Java

@Override
public float getInterpolation(float input) {
    return input;
}

下表表示这些插值器对一个持续 1000 毫秒的动画计算出的近似值

已过毫秒 已过去分数/插值分数(线性) 插值分数(加速/减速)
0 0 0
200 .2 .1
400 .4 .345
600 .6 .654
800 .8 .9
1000 1 1

如表所示,LinearInterpolator 以相同的速度改变值,每过去 200 毫秒变化 0.2。AccelerateDecelerateInterpolator 在 200 毫秒到 600 毫秒之间改变值的速度快于 LinearInterpolator,而在 600 毫秒到 1000 毫秒之间改变值的速度较慢。

指定关键帧

Keyframe 对象由一个时间/值对组成,允许您在动画的特定时间定义特定状态。每个关键帧也可以拥有自己的插值器,以控制动画在前一个关键帧时间和当前关键帧时间间隔内的行为。

要实例化 Keyframe 对象,您必须使用工厂方法之一:ofInt()ofFloat()ofObject() 来获取适当类型的 Keyframe。然后调用 ofKeyframe() 工厂方法来获取 PropertyValuesHolder 对象。获得该对象后,可以通过传入 PropertyValuesHolder 对象和要添加动画的对象来获取动画器。以下代码片段演示了如何执行此操作

Kotlin

val kf0 = Keyframe.ofFloat(0f, 0f)
val kf1 = Keyframe.ofFloat(.5f, 360f)
val kf2 = Keyframe.ofFloat(1f, 0f)
val pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2)
ObjectAnimator.ofPropertyValuesHolder(target, pvhRotation).apply {
    duration = 5000
}

Java

Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
Keyframe kf1 = Keyframe.ofFloat(.5f, 360f);
Keyframe kf2 = Keyframe.ofFloat(1f, 0f);
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2);
ObjectAnimator rotationAnim = ObjectAnimator.ofPropertyValuesHolder(target, pvhRotation);
rotationAnim.setDuration(5000);

为视图添加动画

属性动画系统可以简化 View 对象的动画处理,并提供了相对于视图动画系统的一些优势。视图动画系统通过改变视图的绘制方式来转换 View 对象。这在每个视图的容器中处理,因为视图本身没有可操作的属性。这导致视图被动画化,但视图对象本身没有发生变化。这导致了一些行为,例如对象仍然存在于其原始位置,尽管它绘制在屏幕上的不同位置。在 Android 3.0 中,添加了新的属性以及相应的 getter 和 setter 方法来消除此缺点。

属性动画系统通过更改 View 对象中的实际属性来为屏幕上的视图添加动画。此外,视图在属性更改时也会自动调用 invalidate() 方法来刷新屏幕。View 类中促进属性动画的新属性有

  • translationXtranslationY:这些属性控制视图相对于其布局容器设置的左侧和顶部坐标的增量位置。
  • rotationrotationXrotationY:这些属性控制视图绕其枢轴点进行 2D(rotation 属性)和 3D 旋转。
  • scaleXscaleY:这些属性控制视图绕其枢轴点进行 2D 缩放。
  • pivotXpivotY:这些属性控制枢轴点的位置,旋转和缩放变换都围绕该点进行。默认情况下,枢轴点位于对象的中心。
  • xy:这些是简单的实用属性,用于描述视图在其容器中的最终位置,是左侧和顶部值以及 translationX 和 translationY 值的总和。
  • alpha:表示视图的 alpha 透明度。此值默认为 1(不透明),值为 0 表示完全透明(不可见)。

要为视图对象的属性(例如其颜色或旋转值)添加动画,只需创建属性动画器并指定要为其添加动画的视图属性即可。例如

Kotlin

ObjectAnimator.ofFloat(myView, "rotation", 0f, 360f)

Java

ObjectAnimator.ofFloat(myView, "rotation", 0f, 360f);

有关创建动画器的详细信息,请参阅关于使用 ValueAnimatorObjectAnimator 进行动画处理的部分。

使用 ViewPropertyAnimator 进行动画处理

ViewPropertyAnimator 提供了一种简单的方法,可以使用单个底层 Animator 对象并行地为视图的多个属性添加动画。它的行为很像 ObjectAnimator,因为它修改了视图属性的实际值,但在同时为许多属性添加动画时效率更高。此外,使用 ViewPropertyAnimator 的代码更加简洁易读。以下代码片段展示了在同时为视图的 xy 属性添加动画时,使用多个 ObjectAnimator 对象、单个 ObjectAnimatorViewPropertyAnimator 的区别。

多个 ObjectAnimator 对象

Kotlin

val animX = ObjectAnimator.ofFloat(myView, "x", 50f)
val animY = ObjectAnimator.ofFloat(myView, "y", 100f)
AnimatorSet().apply {
    playTogether(animX, animY)
    start()
}

Java

ObjectAnimator animX = ObjectAnimator.ofFloat(myView, "x", 50f);
ObjectAnimator animY = ObjectAnimator.ofFloat(myView, "y", 100f);
AnimatorSet animSetXY = new AnimatorSet();
animSetXY.playTogether(animX, animY);
animSetXY.start();

单个 ObjectAnimator

Kotlin

val pvhX = PropertyValuesHolder.ofFloat("x", 50f)
val pvhY = PropertyValuesHolder.ofFloat("y", 100f)
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvhY).start()

Java

PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvhY).start();

ViewPropertyAnimator

Kotlin

myView.animate().x(50f).y(100f)

Java

myView.animate().x(50f).y(100f);

有关 ViewPropertyAnimator 的更详细信息,请参阅相应的 Android Developers 博客文章

在 XML 中声明动画

属性动画系统允许您使用 XML 声明属性动画,而不是以编程方式进行。通过在 XML 中定义动画,您可以轻松地在多个 activity 中重用动画,并更轻松地编辑动画序列。

为了区分使用新属性动画 API 的动画文件与使用旧版视图动画框架的动画文件,从 Android 3.1 开始,应将属性动画的 XML 文件保存在 res/animator/ 目录中。

以下属性动画类支持使用以下 XML 标签进行 XML 声明

要查找可以在 XML 声明中使用的属性,请参阅动画资源。以下示例按顺序播放两组对象动画,其中第一个嵌套集同时播放两个对象动画

<set android:ordering="sequentially">
    <set>
        <objectAnimator
            android:propertyName="x"
            android:duration="500"
            android:valueTo="400"
            android:valueType="intType"/>
        <objectAnimator
            android:propertyName="y"
            android:duration="500"
            android:valueTo="300"
            android:valueType="intType"/>
    </set>
    <objectAnimator
        android:propertyName="alpha"
        android:duration="500"
        android:valueTo="1f"/>
</set>

为了运行此动画,您必须在代码中将 XML 资源填充到 AnimatorSet 对象中,然后在启动动画集之前为所有动画设置目标对象。调用 setTarget() 会方便地为 AnimatorSet 的所有子项设置单个目标对象。以下代码展示了如何执行此操作

Kotlin

(AnimatorInflater.loadAnimator(myContext, R.animator.property_animator) as AnimatorSet).apply {
    setTarget(myObject)
    start()
}

Java

AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,
    R.animator.property_animator);
set.setTarget(myObject);
set.start();

您也可以在 XML 中声明 ValueAnimator,如以下示例所示

<animator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:valueType="floatType"
    android:valueFrom="0f"
    android:valueTo="-100f" />

要在代码中使用之前的 ValueAnimator,您必须填充该对象,添加一个 AnimatorUpdateListener,获取更新后的动画值,并将其用于某个视图的属性,如以下代码所示

Kotlin

(AnimatorInflater.loadAnimator(this, R.animator.animator) as ValueAnimator).apply {
    addUpdateListener { updatedAnimation ->
        textView.translationX = updatedAnimation.animatedValue as Float
    }

    start()
}

Java

ValueAnimator xmlAnimator = (ValueAnimator) AnimatorInflater.loadAnimator(this,
        R.animator.animator);
xmlAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator updatedAnimation) {
        float animatedValue = (float)updatedAnimation.getAnimatedValue();
        textView.setTranslationX(animatedValue);
    }
});

xmlAnimator.start();

有关定义属性动画的 XML 语法的详细信息,请参阅动画资源

对 UI 性能的潜在影响

更新 UI 的动画器会为动画运行的每一帧增加额外的渲染工作。因此,使用资源密集型动画可能会对您的应用性能产生负面影响。

对 UI 进行动画处理所需的工作会添加到渲染管道的动画阶段。您可以通过启用 Profile GPU Rendering(GPU 渲染配置文件)并监控动画阶段来了解您的动画是否会影响应用性能。有关详细信息,请参阅 GPU 渲染配置文件演练