属性动画概述

尝试 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 中,由于动画缓慢加速,因此在 t = 10 毫秒时,插值分数(约为 .15)小于经过分数 .25。在图 1 中,插值分数始终与经过分数相同。

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

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

视图动画系统仅提供动画化 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 提供了一种将动画组合在一起的机制,以便它们彼此关联地运行。您可以设置动画一起播放、顺序播放或在指定的延迟后播放。有关详细信息,请参阅关于使用动画集编排多个动画的部分。

评估器告诉属性动画系统如何为给定属性计算值。它们采用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();

在此代码中,ValueAnimatorstart()方法运行时开始计算动画的值,在 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();

在此代码中,ValueAnimatorstart()方法运行时开始计算动画的值,在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类,而不是实现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中的布局更改设置动画。当您将View添加到ViewGroup或从ViewGroup中移除它们,或者当您使用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参数接收插值后的比例,因此在计算动画值时,不必考虑插值器。

使用插值器

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

动画系统中的插值器接收来自动画器的比例,该比例表示动画的经过时间。插值器会修改此比例,使其与它旨在提供的动画类型相符。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 .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对象的绘制方式来变换View对象。这是在每个View的容器中处理的,因为View本身没有可操作的属性。这导致了View被动画化,但View对象本身没有发生变化。这导致了诸如对象仍然存在于其原始位置等行为,即使它绘制在屏幕上的不同位置也是如此。在Android 3.0中,添加了新的属性以及相应的getter和setter方法来消除此缺点。

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

  • translationXtranslationY:这些属性控制View相对于其布局容器设置的左上角坐标的偏移位置。
  • rotationrotationXrotationY:这些属性控制围绕枢轴点的二维(rotation属性)和三维旋转。
  • scaleXscaleY:这些属性控制围绕其枢轴点的View的二维缩放。
  • 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对象、单个ObjectAnimator对象和ViewPropertyAnimator的差异。

多个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 的工作将添加到渲染管道的动画阶段。您可以通过启用 **分析 GPU 渲染** 并监控动画阶段来了解您的动画是否会影响应用程序的性能。有关更多信息,请参阅分析 GPU 渲染演练