属性动画概览

试用 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 ms 时的已完成分数将为 0.25,因为总时长为 t = 40 ms。

ValueAnimator 计算完已完成分数后,它会调用当前设置的 TimeInterpolator 来计算插值分数。插值分数会将已完成动画分数映射到新的分数,该分数会将设置的时间插值考虑在内。例如,在图 2 中,由于动画缓慢加速,t = 10 ms 时的插值分数(约 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 集编排多个动画部分。

评估器会告知属性动画系统如何计算给定属性的值。它们会获取 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 会开始计算 1,000 毫秒内介于 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 提供的逻辑计算 1,000 毫秒内介于 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() 回调中执行此操作。例如,如果为可绘制对象的颜色属性添加动画效果,则只有在该对象重新绘制自身时,屏幕才会更新。视图的所有属性 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 对象的更改添加动画效果的功能,也提供了为视图对象本身添加动画效果的简单方法。

您可以使用 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 中添加或移除的视图以及 ViewGroup 中其余的视图添加动画效果。

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

借助 StateListAnimator 类,您可以定义在视图状态更改时运行的 Animator。此对象充当 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 毫秒到 1,000 毫秒之间变慢。

指定关键帧

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:这些是简单的实用属性,用于描述 View 在其容器中的最终位置,即左侧值和顶部值以及 translationX 和 translationY 值的总和。
  • alpha:表示视图的 Alpha 透明度。此值默认为 1(不透明),值为 0 表示完全透明度(不可见)。

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

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 渲染模式分析演示