属性动画概览

试用 Compose 方式
Jetpack Compose 是推荐在 Android 设备上使用的界面工具包。了解如何在 Compose 中使用动画。

属性动画系统是一个稳健的框架,可让您为几乎任何内容添加动画效果。您可以定义一个动画,使其随着时间的推移而更改任何对象属性,无论其是否绘制到屏幕上。属性动画会在指定时长内更改属性(对象中的字段)值。如需为某个对象添加动画效果,您可以指定要添加动画效果的对象属性,例如对象在屏幕上的位置、要为其添加动画效果的时长,以及要在哪些值之间添加动画效果。

借助属性动画系统,您可以定义动画的以下特性:

  • 时长:您可以指定动画的时长。默认时长为 300 毫秒。
  • 时间插值:您可以指定如何根据动画的当前已播放时间来计算属性值。
  • 重复计数和行为:您可以指定是否在某个时长结束时让动画重复播放以及重复播放动画的次数。您还可以指定是否要反向播放动画。将其设置为反向播放时,动画会正向播放然后反向播放,直到达到重复次数。
  • Animator 集:您可以将动画分组到一起、按顺序或在指定延迟之后播放的逻辑集。
  • 帧刷新延迟:您可以指定动画帧的刷新频率。默认设置为每 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 中,使用的 TimeInterpolatorAccelerateDecelerateInterpolatorTypeEvaluatorIntEvaluator

如需启动动画,请创建一个 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 ms 时的插值分数为 0.15,因此该属性的值为 0.15 × (40 - 0),即 6。

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

视图动画系统提供仅为 View 对象添加动画效果的功能,因此,如果要为非 View 对象添加动画效果,您必须实现自己的代码来执行此操作。视图动画系统也受到限制,因为它仅公开要添加动画效果的 View 对象的几个方面(例如,视图的缩放和旋转,而不显示背景颜色)。

视图动画系统的另一个缺点是,它只会在绘制视图的位置进行修改,而不会修改实际的视图本身。例如,如果您为某个按钮添加了动画效果使其在屏幕上移动,该按钮会正确绘制,但点击该按钮的实际位置不会发生变化,因此您必须实现自己的逻辑来处理此事件。

使用属性动画系统时,可以完全消除这些限制,您可以为任何对象(视图和非视图)的任何属性添加动画效果,并且实际上修改了对象本身。属性动画系统在执行动画方面也更为强健。大体上讲,您可以为要添加动画效果的属性(例如颜色、位置或大小)分配 Animator,还可以定义动画的各个方面,例如多个 Animator 的插值和同步。

不过,视图动画系统的设置时间较短,需要编写的代码也较少。如果视图动画可以完成您需要执行的所有操作,或者现有代码已按照您期望的方式运行,则无需使用属性动画系统。如果出现用例,也可以针对不同情况同时使用这两种动画系统。

API 概览

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

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

表 1. Animator

说明
ValueAnimator 属性动画的主计时引擎,也会计算要添加动画效果的属性的值。它具有计算动画值的所有核心功能,包含每个动画的时间详细信息、有关动画是否重复的信息、接收更新事件的监听器以及设置要评估的自定义类型的功能。为属性添加动画效果分为两部分:计算添加动画效果之后的值,以及在要添加动画效果的对象和属性上设置这些值。ValueAnimator 不执行第二部分,因此您必须监听由 ValueAnimator 计算的值的更新,并使用自己的逻辑修改要添加动画效果的对象。如需了解详情,请参阅使用 ValueAnimator 添加动画效果部分。
ObjectAnimator ValueAnimator 的子类,用于设置目标对象和对象属性,以添加动画效果。此类在为动画计算新值时会相应地更新属性。大多数情况下,您都希望使用 ObjectAnimator,因为它可以大大简化为目标对象上的值添加动画效果的过程。不过,有时您需要直接使用 ValueAnimator,因为 ObjectAnimator 还有其他一些限制,例如要求目标对象上存在特定的访问器方法。
AnimatorSet 提供一种将动画分组在一起的机制,以便它们彼此相关运行。您可以将动画设置为一起播放、依序播放或在指定延迟时间后播放。如需了解详情,请参阅使用 Animator Set 编排多个动画部分。

评估器会告知属性动画系统如何计算给定属性的值。它们会获取 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 或颜色值,在动画播放期间为某些类型的值添加动画效果。您可以通过调用 ValueAnimator 的某一工厂方法来获取它:ofInt()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 会开始计算时长为 1000 毫秒的动画值(介于 0 和 100 之间)。

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

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 提供的逻辑计算时长为 1000 毫秒的动画值,该值介于 startPropertyValueendPropertyValue 之间。

您可以通过向 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 类似,但您也可以指定对象、该对象属性的名称(作为字符串),以及要在其间添加动画效果的值:

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() 回调中执行此操作。例如,为可绘制对象的颜色属性添加动画效果只会在该对象重新绘制自身时导致屏幕更新。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 对象的更改添加动画效果的功能,以及为视图对象本身添加动画效果的简单方法。

您可以使用 LayoutTransition 类为 ViewGroup 中的布局更改添加动画效果。当您向 ViewGroup 添加视图或从中移除视图时,或者当您使用 VISIBLEINVISIBLEGONE 调用视图的 setVisibility() 方法时,这些视图可能会经历出现和消失动画。当您添加或移除 View 时,ViewGroup 中的其余 View 也可以以动画形式移动到新位置。您可以通过调用 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 添加或移除的 View 以及 ViewGroup 中的其余 View 添加动画效果。

使用 StateListAnimator 为视图状态更改添加动画效果

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

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

例如,以下文件会创建一个状态列表 Animator,该动画会在用户按下视图时更改视图的 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>

如需将状态列表 Animator 附加到视图,请添加 android:stateListAnimator 属性,如下所示:

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

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

或者,如需改为在代码中将状态列表 Animator 分配给视图,请使用 AnimatorInflater.loadStateListAnimator() 方法,然后使用 View.setStateListAnimator() 方法将 Animator 分配给您的视图。

或者,您可以使用 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() 方法。这样,您使用的 Animator 就可以在动画的当前点上为动画属性返回适当的值。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 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 0.2 0.1
400 0.4 0.345
600 0.6 0.8
800 0.8 0.9
1000 1 1

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

指定关键帧

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

要实例化 Keyframe 对象,您必须使用工厂方法之一(ofInt()ofFloat()ofObject())来获取相应类型的 Keyframe。然后,调用 ofKeyframe() 工厂方法来获取 PropertyValuesHolder 对象。获取对象后,您可以通过传入 PropertyValuesHolder 对象和要添加动画效果的对象来获取 Animator。以下代码段演示了如何做到这一点:

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);

为视图添加动画效果

属性动画系统支持为视图对象添加简化的动画,与视图动画系统相比,它具有一些优势。视图动画系统通过改变视图对象的绘制方式来转换这些对象。这是在每个视图的容器中处理的,因为视图本身没有可操控的属性。这会导致视图在表面上添加了动画效果,但视图对象本身没有任何变化。这会导致出现这样的行为,例如,即使某个对象绘制在屏幕上的其他位置,它仍位于其原始位置。Android 3.0 中添加了新属性以及相应的 getter 和 setter 方法,以消除此缺陷。

属性动画系统可以通过更改视图对象中的实际属性,为屏幕上的视图添加动画效果。此外,每当其属性发生更改时,View 还会自动调用 invalidate() 方法来刷新屏幕。View 类中有利于属性动画的新属性包括:

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

要为 View 对象的属性(如其颜色或旋转值)添加动画效果,您只需创建一个属性 Animator 并指定要添加动画效果的 View 属性。例如:

Kotlin

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

Java

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

如需详细了解如何创建 Animator,请参阅有关使用 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 中定义动画,您可以在多个 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 语法,请参阅动画资源

对界面性能的潜在影响

更新界面的 Animator 会使动画运行的每一帧都进行额外的渲染工作。因此,使用资源密集型动画可能会对应用的性能产生负面影响。

为界面添加动画效果所需的工作已添加到渲染管道的动画阶段。您可以通过启用 GPU 渲染模式分析并监控动画阶段来了解动画是否会影响应用的性能。如需了解详情,请参阅 GPU 渲染模式分析演示