安卓的动画学习——属性动画。

720 阅读17分钟

属性动画与视图动画

官方介绍

视图动画

视图动画系统才能提供View对象添加动画效果的功能,因此,如果您想为非对象添加动画效果,则只需实现自己的代码即可实现。视图动画系统也存在一些限制,因为它只是公开对象的部分方面,您可以对视图的缩放和添加动画效果,但无法对背景颜色供您添加动画效果。

例如,如果您为一个例子添加了动画效果,可以在屏幕上移动移动事件会实际绘制你的位置,但并不能点击按钮的位置,因此必须通过实现自己的逻辑来处理这个。

属性动画

动画系统的属性就可以完全赋予这些属性,您还可以修改任何对象(视图和非视图)的任何属性,并且实际是对象。动画系统在执行动画方面也可以修改。例如强值。还可以定义大小范围,可以为动画的(例如)分配动画师,动画师的属性位置,多个动画的插曲和同步。

运行视图的设置需要的时间,需要用您的运行代码查看系统,然后使用您查看动画的方式。或者如果现有的所有代码都需要按照您需要的方式执行动画,则需要单独执行动画。在某些情况下,也可以在不同的情况下同时使用这个动画系统。

属性动画的使用

创建布局 && 为 ViewGroup 对象的布局更改添加动画效果

在 Activity 的布局 XML 文件中,或者Fragment的xml文件中, 针对您要启用动画的布局,将 android:animateLayoutChanges 属性设置为 true。 例如:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:animateLayoutChanges="true"
    tools:context=".ui.me.MeFragment">

</FrameLayout>

API 概览

表 1. Animator

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

表 2.  Evaluators

类/接口说明
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()。例如:

下面是使用 ofFloat()更改TextView位置的代码.

//从给一个变化的区间
ValueAnimator.ofFloat(0f, 100f).apply {
    //时间
    duration = 1000
    //开始执行
    start()
    //监听值的变化 如果你打印 updatedAnimation.animatedValue 那么控制台会输出0-100
    addUpdateListener { updatedAnimation ->
        //修改组件的位置
        textView.translationX = updatedAnimation.animatedValue as Float
        textView.translationY = updatedAnimation.animatedValue as Float
    }
    //监听开始结束
    addListener(object :AnimatorListenerAdapter(){
        override fun onAnimationStart(animation: Animator?) {
            super.onAnimationStart(animation)
                Log.i("动画开始", "")
            }
        override fun onAnimationEnd(animation: Animator?) {
            super.onAnimationEnd(animation)
                Log.i("动画结束", "")
            }
    })
}

代码编译后,控制台会输出 :

  • I/动画开始:
  • I/动画结束:

实际效果很流畅 这只是gif显示问题。

d7zjt-yx9af.gif

使用 ObjectAnimator 添加动画效果

ObjectAnimator 是上一部分中讨论的 ValueAnimator 的子类,它融合了 ValueAnimator 的计时引擎和值计算以及为目标对象的命名属性添加动画效果这一功能。这可以极大地简化为任何对象添加动画效果的过程,因为动画属性会自动更新,因此您也无需再实现 ValueAnimator.AnimatorUpdateListener 了。

实例化 ObjectAnimator 与 ValueAnimator 的过程类似,但您也可以指定对象和该对象属性的名称(以字符串形式),以及要在哪些值之间添加动画效果:

要使 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(float)

 ObjectAnimator.ofFloat(targetObject, "propName", 1f)
  • 根据您要添加动画效果的属性或对象,您可能需要对视图调用 invalidate() 方法,以强制屏幕使用添加动画效果之后的值重新绘制自身。您可以在 onAnimationUpdate() 回调中执行此操作。例如,如果为可绘制对象的颜色属性添加动画效果,则仅当该对象重新绘制自身时,屏幕才会刷新。视图的所有属性 setter(如 setAlpha() 和 setTranslationX())都会使视图失效,因此,在使用新值调用这些方法时,您无需使视图失效。如需详细了解监听器,请参阅动画监听器部分。

完整的代码如下

ObjectAnimator.ofFloat(textView, "translationX", 100f).apply {
    addListener(object :AnimatorListenerAdapter(){
        override fun onAnimationStart(animation: Animator?) {
            super.onAnimationStart(animation)
            Log.i("动画开始", "")
        }
        override fun onAnimationEnd(animation: Animator?) {
            super.onAnimationEnd(animation)
            Log.i("动画结束", "")
        }
    })
    duration = 1000
    start()
}

代码执行结束后, 实际效果很流畅 这只是gif显示问题。

a6z9i-bvvoz.gif

使用 AnimatorSet 编排多个动画

例如我想完成TextView右移100dp再下移100dp,然后再消失掉。

var animator1 = ObjectAnimator.ofFloat(textView, "translationX", 100f)
var animator2 = ObjectAnimator.ofFloat(textView, "translationY", 100f)
val bouncer = AnimatorSet().apply {
    duration = 1000
    //执行完animator1后,执行animator2
    play(animator1).before(animator2)
}
val fadeAnim = ObjectAnimator.ofFloat(textView, "alpha", 1f, 0f).apply {
    duration = 250
}
AnimatorSet().apply {
    //执行完bouncer后,执行fadeAnim
    play(bouncer).before(fadeAnim)
    start()
}

编译之后效果如下。 26bat-0jm7f.gif

动画监听器

监听器上面其实就已经用到了。

您可以使用下述监听器来监听动画播放期间的重要事件。

  • Animator.AnimatorListener

    • onAnimationStart() - 在动画开始播放时调用。
    • onAnimationEnd() - 在动画结束播放时调用。
    • onAnimationRepeat() - 在动画重复播放时调用。
    • onAnimationCancel() - 在动画取消播放时调用。取消的动画也会调用 onAnimationEnd(),无论它们以何种方式结束。
  • ValueAnimator.AnimatorUpdateListener

    • onAnimationUpdate() - 对动画的每一帧调用。监听此事件即可使用 ValueAnimator 在动画播放期间生成的计算值。要使用该值,请查询传递到事件中的 ValueAnimator 对象,以使用 getAnimatedValue() 方法获取当前添加动画效果之后的值。如果使用了 ValueAnimator,则必须实现此监听器。

      根据您要添加动画效果的属性或对象,您可能需要对视图调用 invalidate(),以强制屏幕上的相应区域使用添加动画效果之后的新值重新绘制自身。例如,如果为可绘制对象的颜色属性添加动画效果,则仅当该对象重新绘制自身时,屏幕才会刷新。视图的所有属性 setter(如 setAlpha() 和 setTranslationX())都会使视图失效,因此,在使用新值调用这些方法时,您无需使视图失效。

如果您不一定需要实现 Animator.AnimatorListener 接口的所有方法,则可以扩展 AnimatorListenerAdapter 类,而非实现 接口。AnimatorListenerAdapter 类提供了方法的空实现,可供您选择替换。

        ValueAnimator.ofFloat(0f, 100f).apply {
            duration = 1000
            start()
            addListener(object :AnimatorListenerAdapter(){
                override fun onAnimationStart(animation: Animator?) {
                    super.onAnimationStart(animation)
                    Log.i("动画开始", "")
                }
                override fun onAnimationEnd(animation: Animator?) {
                    super.onAnimationEnd(animation)
                    Log.i("动画结束", "")

                }

            })
        }

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

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

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

例如,以下文件创建了一个状态列表 Animator,可在按下后更改视图的 x 和 y 比例: res/drawable/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="@drawable/animate_scale"
            ... />
    

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

使用 TypeEvaluator

如果要为 Android 系统无法识别的类型添加动画效果,则可以通过实现 TypeEvaluator 接口来创建您自己的评估程序。Android 系统可以识别的类型为 intfloat 或颜色,分别由 IntEvaluatorFloatEvaluator 和 ArgbEvaluator 类型评估程序提供支持。

TypeEvaluator 接口中只有一种要实现的方法,那就是 evaluate() 方法。这样,您使用的 Animator 就会在动画的当前点为添加动画效果之后的属性返回适当的值。FloatEvaluator 类演示了如何做到这一点:

简单的来说就是使用自己定义的数据来完成动画的过程。

下面是PointEvaluator示例。

//实现一个自己的 TypeEvaluator
private class PointEvaluator : TypeEvaluator<Any> {
    override fun evaluate(fraction: Float, startValue: Any, endValue: Any): Any {
        var startPoint:Point = startValue as Point
        var endPoint:Point = endValue as Point
        var x:Float  = startPoint.x + fraction * (endPoint.x - startPoint.x);
        var y:Float = startPoint.y + fraction * (endPoint.y - startPoint.y);
        var point:Point  = Point(x.toInt(), y.toInt());
        return point;
    }
}

使用:

ValueAnimator.ofObject(PointEvaluator(), Point(0, 0), Point(100, 200)).apply {
    duration = 1000
    start()
    addUpdateListener{ updatedAnimation ->
        var tempP =  updatedAnimation.getAnimatedValue() as Point
        textView.translationX = tempP.x.toFloat()
        textView.translationY = tempP.y.toFloat()
    }
}

使用插值器

没看懂,也没找到实际应用场景。下面是官方文档内容:

插值器指定了如何根据时间计算动画中的特定值。例如,您可以指定动画在整个动画中以线性方式播放,即动画在整个播放期间匀速移动;也可以指定动画使用非线性时间,例如动画在开始后加速并在结束前减速。

动画系统中的插值器会接收来自 Animator 的分数,该分数表示动画的已播放时间。插值器会修改此分数,使其与要提供的动画类型保持一致。Android 系统在 android.view.animation package 中提供了一组常用的插值器。如果这些插值器都不能满足您的需求,您可以实现 TimeInterpolator 接口并创建您自己的插值器。

以下示例对比了默认插值器 AccelerateDecelerateInterpolator 和 LinearInterpolator 计算插值分数的方式。LinearInterpolator 对已完成动画分数没有任何影响。AccelerateDecelerateInterpolator 会在动画开始后加速,并在动画结束前减速。以下方法定义了这些插值器的逻辑:

AccelerateDecelerateInterpolator

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

LinearInterpolator

    override fun getInterpolation(input: Float): Float = input

下表表示这些插值器为时长 1000ms 的动画计算的近似值:

已完成毫秒数已完成动画分数/插值分数(线性)插值分数(加速/减速)
000
2000.20.1
4000.40.345
6000.60.8
8000.80.9
100011

如上表所示,LinearInterpolator 以相同的速度更改值,每 200ms 变化 0.2。AccelerateDecelerateInterpolator 在 200ms 到 600ms 之间更改值的速度会快于 LinearInterpolator,在 600ms 到 1000ms 之间会慢一些。

指定关键帧

方法 作用就是用来制定动画的过程(速度)

val kf0 = Keyframe.ofFloat(0f, 0f)
val kf1 = Keyframe.ofFloat(.3f, 200f)
val kf2 = Keyframe.ofFloat(1f, 400f)
val pvhRotation = PropertyValuesHolder.ofKeyframe("translationX", kf0, kf1, kf2)
ObjectAnimator.ofPropertyValuesHolder(textView, pvhRotation).apply {
    duration = 5000
    start()
}

为视图添加动画效果

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

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

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

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

ObjectAnimator.ofFloat(textView, "rotation", 0f, 360f).apply {
    duration = 1300
    start()
}

使用 ViewPropertyAnimator 添加动画效果

ViewPropertyAnimator 有助于使用单个底层 Animator 对象轻松为 View 的多个属性并行添加动画效果。它的行为方式与 ObjectAnimator 非常相似,因为它会修改视图属性的实际值,但在同时为多个属性添加动画效果时,它更为高效。此外,使用 ViewPropertyAnimator 的代码更加简洁,也更易读。以下代码段展示了在同时为视图的 x 和 y 属性添加动画效果时,使用多个 ObjectAnimator 对象、使用单个 ObjectAnimator 对象以及使用 ViewPropertyAnimator 的区别。

多个 ObjectAnimator 对象

  val animX = ObjectAnimator.ofFloat(myView, "x", 50f)
    val animY = ObjectAnimator.ofFloat(myView, "y", 100f)
    AnimatorSet().apply {
        playTogether(animX, animY)
        start()
    }
    

一个 ObjectAnimator

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

ViewPropertyAnimator (非常的方便)

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

如需详细了解 ViewPropertyAnimator,请参阅相应的 Android 开发者博文。(需要翻墙)

在 XML 中声明动画

属性动画系统支持您使用 XML 声明属性动画,而不是以编程方式进行声明。通过在 XML 中定义动画,您可以轻松地在多个 Activity 中重复使用动画,还能更轻松地修改动画序列。

为了将使用新属性动画 API 的动画文件与使用旧版视图动画框架的动画文件区分开来,从 Android 3.1 开始,您应将属性动画的 XML 文件保存到 res/animator/ 目录中。

以下属性动画类具有带相应 XML 标签的 XML 声明支持:

  • ValueAnimator - <animator>
  • ObjectAnimator - <objectAnimator>
  • AnimatorSet - <set>

要查找 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 的所有子项设置一个目标对象,这是系统提供的便捷方式。以下代码展示了如何做到这一点:

    (AnimatorInflater.loadAnimator(myContext, R.animator.property_animator) as AnimatorSet).apply {
        setTarget(myObject)
        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、获取更新后的动画值并在某个视图的属性中使用它,如下面的代码所示:

    (AnimatorInflater.loadAnimator(this, R.animator.animator) as ValueAnimator).apply {
        addUpdateListener { updatedAnimation ->
            textView.translationX = updatedAnimation.animatedValue as Float
        }

        start()
    }
    

如需了解定义属性动画的 XML 语法,请参阅动画资源

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

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