概述

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

  • 持续时间:您可以指定动画的持续时间。默认长度为 300 毫秒。
  • 时间插值:您可以指定如何根据动画的当前经过时间计算属性值。
  • 重复次数和行为:您可以指定当动画到达持续时间结束时是否重复动画以及重复动画的次数。您还可以指定是否要反向播放动画。将其设置为反向播放动画,然后重复播放,直到达到重复次数。
  • 动画师集:您可以将动画分组为一起播放或按顺序播放或在指定延迟后播放的逻辑集。
  • 帧刷新延迟:您可以指定刷新动画帧的频率。默认设置为每 10 毫秒刷新一次,但应用程序刷新帧的速度最终取决于系统整体的繁忙程度以及系统服务底层计时器的速度。

    工作原理

    首先,让我们通过一个简单的示例来了解动画的工作原理。图 1 描绘了一个假设对象,该对象使用其x属性进行动画处理,该属性表示其在屏幕上的水平位置。动画的持续时间设置为 40 毫秒,移动距离为 40 像素。每 10 毫秒(默认帧刷新率),对象水平移动 10 个像素。在 40 毫秒结束时,动画停止,对象在水平位置 40 处结束。这是一个带有线性插值的动画示例,意味着对象以恒定速度移动。
    image.png
    图 1.线性动画示例
    您还可以指定动画以具有非线性插值。图 2 说明了一个在动画开始时加速并在动画结束时减速的假设对象。对象仍然在 40 毫秒内移动了 40 个像素,但是是非线性的。一开始,这个动画加速到中间点,然后从中间点减速直到动画结束。如图 2 所示,动画开始和结束时经过的距离小于中间。
    image.png
    图 2.非线性动画示例
    让我们详细看看属性动画系统的重要组件如何计算如上图所示的动画。图 3 描述了主要类如何相互协作。
    image.png
    图 3.如何计算动画

    API 概述

    Animator - 动画

    表 1. Animators
班级 描述
ValueAnimator 主要直接用于指定属性
ObjectAnimator 主要直接用于对象的指定属性
AnimatorSet 将动画组合在一起的机制

Evaluators - 估值器

评估器告诉属性动画系统如何计算给定属性的值。它们获取 Animator 类提供的时间数据、动画的开始和结束值,并根据这些数据计算属性的动画值。

表 2. Evaluators

类/接口 描述
IntEvaluator 计算int属性值的默认评估器。
FloatEvaluator 计算float属性值的默认评估器。
ArgbEvaluator 用于计算以十六进制值表示的颜色属性值的默认评估器。
TypeEvaluator 用于自定义估值器

Interpolators - 插值器

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

表 3. Interpolators

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

使用 ValueAnimator 制作动画

该类允许您通过指定一组 int 、float 或 color 值来制作动画,从而 ValueAnimator 在动画期间为某种类型的值制作动画。您可以通过调用其工厂方法之一来获得。
例如:[ofInt()](https://developer.android.google.cn/reference/android/animation/ValueAnimator#ofInt(int...))/[ofFloat()](https://developer.android.google.cn/reference/android/animation/ValueAnimator#ofFloat(float...))/[ofObject()](https://developer.android.google.cn/reference/android/animation/ValueAnimator#ofObject(android.animation.TypeEvaluator,%20java.lang.Object...))

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

在此代码中,当方法运行 ValueAnimator 时,开始计算动画的值,介于 0 和 100 之间,持续时间为 1000 毫秒。[start()](https://developer.android.google.cn/reference/android/animation/ValueAnimator#start())启动动画
您还可以通过执行以下操作来指定要设置动画的自定义类型:

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

在此代码中,当方法运行时,ValueAnimator 开始计算动画的值,在1000 毫秒的持续时间内startPropertyValue并endPropertyValue使用由提供的逻辑。
AnimatorUpdateListener 您可以通过向对象添加 来使用动画的值 ValueAnimator,如以下代码所示:

  1. ValueAnimator.ofObject(...).apply {
  2. ...
  3. addUpdateListener { updatedAnimation ->
  4. // You can use the animated value in a property that uses the
  5. // same type as the animation. In this case, you can use the
  6. // float value in the translationX property.
  7. textView.translationX = updatedAnimation.animatedValue as Float
  8. }
  9. ...
  10. }

在该onAnimationUpdate()) 方法中,您可以访问更新的动画值并在您的视图之一的属性中使用它。有关侦听器的更多信息,请参阅有关 动画侦听器的部分。

使用 ObjectAnimator 制作动画

ObjectAnimator是(在上一节中讨论过)的子类,它结合ValueAnimator了时间引擎和值计算以及ValueAnimator为目标对象的命名属性设置动画的能力。这使得动画任何对象变得更加容易,因为您不再需要实现ValueAnimator.AnimatorUpdateListener,因为动画属性会自动更新。
实例化 anObjectAnimator类似于 a ValueAnimator,但您还可以指定对象和该对象属性的名称(作为字符串)以及要在它们之间设置动画的值:

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

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

  • 您正在制作动画的对象属性必须具有形式为 set(). 因为ObjectAnimator 在动画期间会自动更新属性,所以它必须能够使用这个 setter 方法访问属性。例如,如果属性名称是foo,则需要有一个setFoo()方法。如果此 setter 方法不存在,您有三个选项:
    • 如果您有权这样做,请将 setter 方法添加到类中。
    • 使用您有权更改的包装器类,并让该包装器使用有效的 setter 方法接收值并将其转发给原始对象。
    • 改为使用ValueAnimator
  • 如果您values…在其中一种工厂方法中只为参数指定一个值ObjectAnimator,则假定它是动画的结束值。因此,您要制作动画的对象属性必须有一个 getter 函数,用于获取动画的起始值。getter 函数必须采用get(). 例如,如果属性名称是 foo,则需要有一个getFoo()方法。
  • 您正在制作动画的属性的 getter(如果需要)和 setter 方法必须在与您指定的起始值和结束值相同的类型上运行ObjectAnimator。例如,您必须具有 targetObject.setPropName(float)并且targetObject.getPropName() 如果您构造以下内容ObjectAnimator:ObjectAnimator.ofFloat(targetObject, “propName”, 1f)

  • 根据您正在制作动画的属性或对象,您可能需要调用invalidate())View 上的方法以强制屏幕使用更新的动画值重新绘制自身。您在 onAnimationUpdate()) 回调中执行此操作。例如,为 Drawable 对象的 color 属性设置动画只会在该对象重绘自身时更新屏幕。View 上的所有属性设置器,例如 正确setAlpha())地setTranslationX()) 使 View 无效,因此在使用新值调用这些方法时不需要使 View 无效。有关侦听器的更多信息,请参阅有关 动画侦听器的部分。

    使用 AnimatorSet 编排多个动画

    在许多情况下,您希望播放取决于另一个动画何时开始或结束的动画。Android 系统允许您将动画捆绑到一个AnimatorSet. 您还可以将AnimatorSet对象相互嵌套。
    以下代码片段Animator 以下列方式播放以下对象:

  1. 播放bounceAnim。
  2. 同时播放squashAnim1, squashAnim2,stretchAnim1和 stretchAnim2。
  3. 播放bounceBackAnim。
  4. 播放fadeAnim。
    1. val bouncer = AnimatorSet().apply {
    2. play(bounceAnim).before(squashAnim1)
    3. play(squashAnim1).with(squashAnim2)
    4. play(squashAnim1).with(stretchAnim1)
    5. play(squashAnim1).with(stretchAnim2)
    6. play(bounceBackAnim).after(stretchAnim2)
    7. }
    8. val fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f).apply {
    9. duration = 250
    10. }
    11. AnimatorSet().apply {
    12. play(bouncer).before(fadeAnim)
    13. start()
    14. }

    动画监听器

    您可以使用下面描述的侦听器在动画持续时间内侦听重要事件。

如果不想实现接口的所有方法,可以扩展AnimatorListenerAdapter类而不是实现 接口。该类提供了您可以选择覆盖的方法的空实现。Animator.AnimatorListenerAnimator.AnimatorListenerAnimatorListenerAdapter
例如,以下代码片段AnimatorListenerAdapteronAnimationEnd()) 回调创建了一个:

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

使用类型评估器

TypeEvaluator如果你想对 Android 系统未知的类型进行动画处理,你可以通过实现接口来创建自己的评估器。Android 系统已知的类型是int、float或颜色,它们由IntEvaluatorFloatEvaluatorArgbEvaluator类型评估器支持。
TypeEvaluator 接口中只有一种方法可以实现,evaluate())方法。这允许您使用的动画师在动画的当前点为您的动画属性返回适当的值。该类FloatEvaluator演示了如何执行此操作:

  1. private class FloatEvaluator : TypeEvaluator<Any> {
  2. override fun evaluate(fraction: Float, startValue: Any, endValue: Any): Any {
  3. return (startValue as Number).toFloat().let { startFloat ->
  4. startFloat + fraction * ((endValue as Number).toFloat() - startFloat)
  5. }
  6. }
  7. }

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

使用插值器

插值器定义如何将动画中的特定值计算为时间的函数。例如,您可以指定动画在整个动画中线性发生,这意味着动画在整个时间均匀移动,或者您可以指定动画使用非线性时间,例如,在开始或结束时使用加速或减速动画片。
动画系统中的插值器从 Animators 接收一个分数,表示动画的经过时间。插值器修改这个分数以符合它旨在提供的动画类型。Android 系统在android.view.animation package. 如果这些都不适合您的需求,您可以实现TimeInterpolator接口并创建自己的接口。
例如,下面将如何比较默认插值器AccelerateDecelerateInterpolatorLinearInterpolator计算插值分数。对经过的LinearInterpolator分数没有影响。加速进入AccelerateDecelerateInterpolator动画并减速离开。以下方法定义了这些插值器的逻辑:
加速减速插值器

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

线性插值器

  1. override fun getInterpolation(input: Float): Float = 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 值的变化速度快于 LinearInterpolator 200ms 和 600ms 之间,而慢于 600ms 和 1000ms 之间。

指定关键帧

对象由Keyframe时间/值对组成,可让您在动画的特定时间定义特定状态。每个关键帧也可以有自己的插值器来控制动画在前一个关键帧的时间和这个关键帧的时间之间的间隔内的行为。
要实例化一个Keyframe对象,您必须使用工厂方法之一,ofInt()),ofFloat()), 或ofObject())来获得适当的 类型Keyframe。然后调用ofKeyframe())工厂方法来获取PropertyValuesHolder对象。获得对象后,您可以通过传入PropertyValuesHolder对象和要设置动画的对象来获得动画师。以下代码片段演示了如何执行此操作:

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

动画视图

属性动画系统允许 View 对象的流线型动画,并提供了一些优于 View 动画系统的优点。视图动画系统通过改变视图对象的绘制方式来转换它们。这是在每个 View 的容器中处理的,因为 View 本身没有要操作的属性。这导致 View 被动画化,但没有导致 View 对象本身发生变化。这导致了诸如对象仍然存在于其原始位置之类的行为,即使它是在屏幕上的不同位置绘制的。在 Android 3.0 中,添加了新的属性以及相应的 getter 和 setter 方法来消除这个缺点。
属性动画系统可以通过更改 View 对象中的实际属性来为屏幕上的 View 设置动画。此外,Views 还会invalidate()) 在其属性发生变化时自动调用该方法来刷新屏幕。View类中促进属性动画的新属性是:

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

要对 View 对象的属性(例如颜色或旋转值)进行动画处理,您需要做的就是创建属性动画器并指定要动画化的 View 属性。例如:

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

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

使用 ViewPropertyAnimator 制作动画

ViewPropertyAnimator提供了一种简单的方法来View使用单个底层Animator 对象并行动画 a的多个属性。它的行为很像ObjectAnimator,因为它修改了视图属性的实际值,但在同时为多个属性设置动画时效率更高。此外,使用的代码ViewPropertyAnimator更加简洁易读。以下代码片段显示了使用多个 ObjectAnimator对象、单个 ObjectAnimator以及ViewPropertyAnimator同时为视图的xandy属性设置动画时的差异。
多个 ObjectAnimator 对象

  1. val animX = ObjectAnimator.ofFloat(myView, "x", 50f)
  2. val animY = ObjectAnimator.ofFloat(myView, "y", 100f)
  3. AnimatorSet().apply {
  4. playTogether(animX, animY)
  5. start()
  6. }

一个对象动画师

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

ViewPropertyAnimator

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

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