属性动画概述

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

属性动画系统是一个强大的框架,允许您为几乎任何内容设置动画。您可以定义一个动画,随着时间的推移更改任何对象属性,无论它是否绘制到屏幕上。属性动画会随着指定的时间长度更改属性(对象中的字段)的值。要为某个内容设置动画,您需要指定要设置动画的对象属性(例如屏幕上对象的位置)、动画持续时间以及要设置动画的值范围。

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

  • 持续时间:您可以指定动画的持续时间。默认长度为 300 毫秒。
  • 时间插值:您可以指定属性值的计算方式,作为动画当前经过时间的函数。
  • 重复次数和行为:您可以指定动画在到达持续时间结束时是否重复以及重复多少次。您还可以指定是否希望动画反向播放。将其设置为反向将使动画反复向前然后向后播放,直到达到重复次数。
  • 动画集:您可以将动画组合成逻辑集,这些逻辑集可以一起播放或按顺序播放或在指定的延迟后播放。
  • 帧刷新延迟:您可以指定动画帧的刷新频率。默认设置为每 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 将是 AccelerateDecelerateInterpolatorTypeEvaluator 将是 IntEvaluator

要启动动画,请创建一个 ValueAnimator 并为要设置动画的属性提供起始值和结束值,以及动画持续时间。当您调用 start() 时,动画开始。在整个动画过程中,ValueAnimator 根据动画持续时间和经过的时间计算 0 到 1 之间的经过分数。经过分数表示动画完成的百分比,0 表示 0%,1 表示 100%。例如,在图 1 中,t = 10 毫秒时的经过分数将为 .25,因为总持续时间为 t = 40 毫秒。

ValueAnimator 完成经过分数的计算后,它会调用当前设置的 TimeInterpolator 来计算插值分数。插值分数将经过分数映射到一个新分数,该分数考虑了设置的时间插值。例如,在图 2 中,因为动画缓慢加速,所以插值分数(约为 .15)在 t = 10 毫秒时小于经过分数 (.25)。在图 1 中,插值分数始终与经过分数相同。

计算出插值分数后,ValueAnimator 会调用相应的 TypeEvaluator,根据插值分数、起始值和动画的结束值计算要设置动画的属性的值。例如,在图 2 中,t = 10 毫秒时的插值分数为 .15,因此此时属性的值将为 .15 × (40 - 0),即 6。

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

视图动画系统仅提供为 View 对象设置动画的功能,因此,如果您想为非 View 对象设置动画,则必须实现自己的代码来执行此操作。视图动画系统也受到限制,因为它仅公开 View 对象的某些方面来设置动画,例如 View 的缩放和旋转,但不包括背景颜色。

视图动画系统的另一个缺点是它只修改了 View 的绘制位置,而不是实际的 View 本身。例如,如果您为按钮设置动画以使其在屏幕上移动,则按钮会正确绘制,但您实际单击按钮的位置不会更改,因此您必须实现自己的逻辑来处理此问题。

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

但是,视图动画系统设置起来需要更少的时间,并且编写代码也更少。如果视图动画完成了您需要完成的所有操作,或者如果您现有的代码已经按您想要的方式工作,则无需使用属性动画系统。如果出现用例,也可能需要为不同的情况同时使用这两个动画系统。

API 概述

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

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

表 1. 动画器

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

评估器告诉属性动画系统如何为给定属性计算值。它们采用 Animator 类提供的计时数据、动画的开始值和结束值,并根据此数据计算属性的动画值。属性动画系统提供了以下评估器

表 2. 评估器

类/接口 描述
IntEvaluator 计算 int 属性值的默认评估器。
FloatEvaluator 计算 float 属性值的默认评估器。
ArgbEvaluator 计算以十六进制值表示的颜色属性值的默认评估器。
TypeEvaluator 一个接口,允许您创建自己的评估器。如果您正在对不是 intfloat 或颜色的对象属性进行动画处理,则必须实现 TypeEvaluator 接口以指定如何计算对象属性的动画值。如果您希望以不同于默认行为的方式处理这些类型,也可以为 intfloat 和颜色值指定自定义 TypeEvaluator。有关如何编写自定义评估器的更多信息,请参阅关于 使用 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 开始计算动画的值,从 startPropertyValueendPropertyValue,使用 MyTypeEvaluator 提供的逻辑,持续时间为 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,因为动画属性会自动更新。

实例化 ObjectAnimator 类似于 ValueAnimator,但您还指定了对象以及该对象属性的名称(作为字符串)以及要进行动画处理的值

Kotlin

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

Java

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

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

  • 您正在对其进行动画处理的对象属性必须具有一个 setter 函数(采用驼峰式命名法),形式为 set<PropertyName>()。因为 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)
    
  • 根据您正在对其进行动画处理的属性或对象,您可能需要在 View 上调用 invalidate() 方法以强制屏幕使用更新的动画值重新绘制自身。您可以在 onAnimationUpdate() 回调中执行此操作。例如,对 Drawable 对象的颜色属性进行动画处理仅在该对象重新绘制自身时才会导致屏幕更新。View 上的所有属性 setter,例如 setAlpha()setTranslationX() 会正确地使 View 失效,因此在使用新值调用这些方法时,您无需使 View 失效。有关侦听器的更多信息,请参阅关于 动画侦听器 的部分。

使用 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 类而不是实现该接口。 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 中的布局更改设置动画。当您将 View 添加到 ViewGroup 或从 ViewGroup 中移除 View,或者当您使用 VISIBLEINVISIBLEGONE 调用 View 的 setVisibility() 方法时,ViewGroup 内部的 View 可以经历出现和消失动画。当您添加或移除 View 时,ViewGroup 中剩余的 View 也可以动画到其新位置。您可以通过调用 setAnimator() 并传入一个 Animator 对象以及以下 LayoutTransition 常量之一来定义 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 中移除的 View 以及 ViewGroup 中剩余的 View 设置动画。

使用 StateListAnimator 设置动画视图状态更改

StateListAnimator 类允许您定义在视图状态更改时运行的动画。此对象充当 Animator 对象的包装器,在指定视图状态(例如“pressed”或“focused”)更改时调用该动画。

StateListAnimator 可以使用 XML 资源定义,其根元素为 <selector>,子元素为 <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 接口来创建自己的评估器。Android 系统已知的类型为 intfloat 或颜色,它们分别由 IntEvaluatorFloatEvaluatorArgbEvaluator 类型评估器支持。

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 参数接收到的分数,因此在计算动画值时,您无需考虑插值器。

使用插值器

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

动画系统中的插值器接收来自 Animator 的一个分数,该分数表示动画的经过时间。插值器修改此分数以与它旨在提供的动画类型相一致。Android 系统在 android.view.animation 包 中提供了一组常见的插值器。如果这些都不满足您的需求,您可以实现 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 .8
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 对象的绘制方式来转换它们。这是在每个 View 的容器中处理的,因为 View 本身没有要操作的属性。这导致 View 被动画化,但没有导致 View 对象本身发生任何变化。这导致了诸如对象仍然存在于其原始位置的行为,即使它绘制在屏幕上的不同位置。在 Android 3.0 中,添加了新的属性以及相应的 getter 和 setter 方法来消除此缺点。

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

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

要动画化 View 对象的属性(例如其颜色或旋转值),您只需创建一个属性动画器并指定要动画化的 View 属性即可。例如

Kotlin

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

Java

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

有关创建动画器的更多信息,请参阅有关使用 ValueAnimatorObjectAnimator 进行动画制作的部分。

使用 ViewPropertyAnimator 进行动画制作

ViewPropertyAnimator 提供了一种简单的方法,可以使用单个底层 Animator 对象并行动画化 View 的多个属性。它的行为类似于 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 开发人员 博文

在 XML 中声明动画

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

为了区分使用新的属性动画 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** 并监视动画阶段来确定动画是否会影响应用程序的性能。有关更多信息,请参阅 Profile GPU 渲染演练