属性动画1:基础知识和ValueAnimator

参考博客和文章
[1] Developers>API Guides>Property Animation
[2] 安卓开发艺术探索
[3] 启舰: 自定义控件三部曲之动画篇(四)——ValueAnimator基本使用
[4] Carson_Ho:Android 属性动画:这是一篇很详细的 属性动画 总结&攻略
[5] 李海珍:插值器源码与曲线图
[6]pengkv:插值器效果图

一、概述


提到属性动画,发现自己之前只是限于简单的使用,对它只有很浅的认识。都知道,属性动画是补间动画的增强版,具有更强大的功能。我对它可是“倾慕”已久,发誓一定要“好好把握”!之前只有简单认识,现在开始深入了解。

说到Android的动画,Google Api Guides中分了三种,分别是:Property AnimationView Animation,和Drawable Animation。见下图:
这里写图片描述

其中Property Animation就是属性动画,View Animation是补间动画(Tween Animation),而Drawable Animation就是帧动画(Frame Animation)。

属性动画是Android 3.0(API11)才出现的。

现在是属性动画频段,所以先重温一下属性动画的基础知识。

二、属性动画基础


下面一段是Api Guides关于属性动画的描述。

1. 属性动画

prop_anim

献丑简单翻译一下:
属性动画体系是一个强大的框架,允许你对几乎任何东西(对象)进行动画。你可以定义一个动画,随着时间改变任何对象的属性,不管它是否在屏幕中绘制。属性动画在指定长度的时间内改变一个属性(对象的一个全局属性)的值。要对一些属性执行动画,你(需要)指定你想要对其进行动画的对象属性,例如,对象在屏幕中的位置,你想让动画执行多长时间,和你想在什么值之间执行动画。

属性动画可以设置以下参数:
Duration:动画持续时间。默认300毫秒。
Time interpolation:时间插值。可以指定插值器,确定属性值的计算方法。
Repeat count and behavior:重复次数和行为。可设定重复次数和重复方式(重新开始或翻转)。
Frame refresh delay:帧刷新时间间隔。指定每隔多长时间刷新一帧,默认10毫秒刷新一帧,但还是要取决于系统的占用和运行速度。
下面是属性动画的计算过程:
属性动画计算过程

属性动画内部封装了一个TimeInterpolator用于定义动画插值,一个TypeEvaluator用于确定如何计算属性值。再给定开始和结束的属性值,以及持续时间duration,调用start()方法,就可以启动属性动画了。在整个动画执行过程中,ValueAnimator根据动画总时长duration和动画已经执行的时间,计算出一个在0到1之间的时间流逝小数(elapsed fraction),这个小数(elapsed fraction)表示动画已经执行的时间百分比,0表示0%,1表示100%。

当ValueAnimator开始计算时间流逝小数(elapsed fraction),它就会调用时间插值器TimeInterpolator,计算出一个插值小数(interpolated fraction)。插值小数(interpolated fraction)是将时间插值(time interpolation)计算在内与时间流逝小数流(elapsed fraction)对应生成的一个小数。

计算了插值小数(interpolated fraction),ValueAnimator就会调用合适的TypeEvaluator,根据插值小数(interpolated fraction)、开始值和结束值计算出当前的属性值。
以上这些均是翻译自Api Guiders。

2. 属性动画(Property Animation)和补间动画(View Animation)的不同

属性动画与补间动画的不同之处在于以下几个方面:

·> 补间动画只能定义两个关键帧在“透明度”、“旋转”、“缩放”、“位移”4个方面的变化,但属性动画可以定义任何属性变化。

·> 补间动画只能对UI组件(View)执行动画,但属性动画几乎可以对任何对象执行动画(不管它是否会绘制(显示)在屏幕上)。

·> 补间动画还有一个缺点:它只改变绘图位置,不改变控件自身。比如,给一个按钮添加了一个划过屏幕的动画,动画会正确执行动画,但是实际可以点击的按钮位置没有变还在原来位置。而属性动画可以动态改变任何对象(View或非View)的属性值,而且对象是作了真正的改变。

·> 但是,补间动画的代码量少,写代码更省时间。

3. 属性动画API概述

在android.animation包下,可以找到大部分属性动画体系的API。因为在补间动画体系已经在 android.view.animation包下定义了许多插值器(interpolator ),这些插值器也可以用于属性动画。下面以表格表示属性动画体系的构成。

表1:Animator

描述
Animator 提供创建动画的最基本结构,一般不直接使用。以下都是它的子类。
ValueAnimator 属性动画主要的时间引擎,负责计算各个帧的属性值。它定义了计算属性动画值的所有核心功能,包括计算每一个动画的时间细节、动画是否重复的信息、接收更新事件的监听,以及设置自定义计算类型的能力。 动态计算属性由两部分组成:计算: 一,计算动画各个帧的相关属性值;二,设置这些值给执行动画的对象和属性。 ValueAnimator不执行第二部分,所以你必须监听由ValueAnimator计算的属性值的更新,并且用你自己的逻辑改变你想要执行动画的对象。
ObjectAnimator ValueAnimator的子类,允许你设置目标对象和对象的属性来执行动画,每当它计算出一个新的动画值,就会更新一次属性值。多数情况下,只需要ObjectAnimator,因为它比较简单。但有时候你需要使用ValueAnimator,因为ObjectAnimator会有一些局限。
AnimatorSet 是Animator的子类。可以将多个动画组合到使它们一起执行。你可以设置这些动画一起播放或者按次序播放或者延迟指定时间后播放。

 
计算器告诉属性动画体系如何计算给定属性的值。它们接收由Animator类提供的定时数据、动画的开始结束值,并根据这些数据计算属性的动画值。属性动画体系提供了以下几种计算器:

表2:Evaluator

类/接口 描述
IntEvaluator 用于计算int类型属性值的默认计算器
FloatEvaluator 用于计算float类型属性值的默认计算器
ArgbEvaluator 用于计算以十六进制表示的颜色属性值的默认计算器
TypeEvaluator 一个你可以用来(实现这个接口)创建自己的计算器的接口。如果你执行动画的对象属性不是一个int、float或color,你必须实现TypeEvaluator接口,指定如果计算对象属性的动画值。如果你想让处理int、float和color值的过程与默认(估值器的处理)方式不同,你也可以指定一个自定义的估值器(TypeEvaluator)来计算处理这些值。

 
时间插值器定义了动画中具体的值是如何作为一个时间函数被计算的。例如,你可以让动画在整个动画执行过程匀速执行,你也可以让动画随时间非线性执行,比如,在开始的时候加速结束的时候减速。表3列出了android.view.animation包下的插值器(interpolator),如果提供的插值器不能满足你的需求,就实现TimeInterpolator创建你自己的插值器。
 
表3:Interpolator

类/接口 描述
AccelerateDecelerateInterpolator 加速减速插值器。开始和结束的变化速度慢中间加速的。先慢后快再慢。
AccelerateInterpolator 加速插值器。开始的变化速度慢然后加速。先慢后快。
AnticipateInterpolator 后退插值器。开始向后然后快速向前。先向后再快速向前。
AnticipateOvershootInterpolator 后退过冲插值器。变化开始先后,然后快速向前,超过目标值,最终回到终值。向后,前冲,超出,返回。
BounceInterpolator 弹跳插值器。弹跳几次后回到最终位置。终点弹跳。
CycleInterpolator 正弦周期插值器。速率按正弦曲线周期性变化。正弦周期。
DecelerateInterpolator 减速插值器。变化速度开始快然后减速。先快后慢。
LinearInterpolator 线性插值器。匀速变化。匀速。
OvershootInterpolator 过冲插值器。快速向前,超过终值,再返回。前冲,超出,返回。
TimeInterpolator 时间插值器。一个接口,允许你实现自己的插值器。

 
[插值器源码与曲线图]:https://my.oschina.net/banxi/blog/135633
[插值器效果图]:http://blog.csdn.net/pengkv/article/details/50488171
 

4. 使用ValueAnimator进行动画

ValueAnimator通过不断控制 的变化,再不断 手动 赋给对象的属性,从而实现动画效果。

ValueAnimator类允许你在一个动画的持续过程中通过指定一组intfloat,或者color值对某种类型的值进行动画计算。获取一个ValueAnimator可以通过调用它的其中一个工厂方法:ofInt()ofFloat() 或者ofObject()。例如:

ValueAnimator animation = ValueAnimator.ofFloat(0f, 100f);
animation.setDuration(1000);
animation.start();

在这段代码总,当start()方法执行时,ValueAnimator开始在1000ms时间内,从0带100计算动画值。
你也可以制定一个自定义类型来动画计算,如下

ValueAnimator animation = ValueAnimator.ofObject(new MyTypeEvaluator(), startPropertyValue, endPropertyValue);
animation.setDuration(1000);
animation.start();

上面代码中,当start()方法开始执行时,ValueAnimator开始在1000ms动画持续时间内,通过MyTypeEvaluator提供的逻辑在开始属性值和结束属性值之间计算动画值。

你可以通过给ValueAnimator添加一个AnimatorUpdateListener来使用动画值,如下面代码所示:

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()方法中你可以访问更新后的动画值并且把它用于你的一个控件的属性上。

5. 使用ObjectAnimator进行动画

ObjectAnimatorValueAnimator的一个子类,结合ValueAnimator 的时间引擎和值计算,能够动画计算一个目标对象的属性。这使得对一些对象进行动画计算更简单,你不需要再实现ValueAnimator.AnimatorUpdateListener,因为进行动画计算的属性会自动更新。
实例化ObjectAnimator与ValueAnimator类似,但是,你也要指定对象和对象的属性名,以及开始值和结束值。如下

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.getPropName(float)targetObject.getPropName(float) 两个方法。

    ObjectAnimator.ofFloat(targetObject, "propName", 1f)
  • 根据你要进行动画计算的属性或对象,你可能需要随着属性值的更新对一个View调用invalidate()方法强制屏幕绘制它。你可以在onAnimationUpdate()回调中调用invalidate()方法。例如,对Drawable对象的颜色属性进行动画计算,当对象重绘自己时就会引起屏幕更新。View所有的属性的setter方法,例如setAlpha()setTranslationX()都对View作了适当的重绘,所以当你调用这些方法赋新值的时候,你不需要对View进行重绘。

6. 用AnimatorSet编排多种动画

很多情况下,我们想根据另一个动画的开始或者结束播放一个动画。安卓系统允许你将多个动画一起装进AnimatorSet,以便你能够指定是否同时,或者按顺序,或者经过一个指定的延时来启动这些动画。你也可以让AnimatorSet对象相互嵌套。

下面的示例代码取自 Bouncing Balls 案例,它以以下方式执行下面的Animator对象:
1. 执行弹跳动画(bounceAnim)。
2. 同时执行压扁动画1(squashAnim1),压扁动画。2(squashAnim2),伸展动画1(stretchAnim1),和伸展动画2(stretchAnim2 )。
3. 执行回弹动画(bounceBackAnim)。
4. 执行逐渐消逝动画(fadeAnim)。

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

7. 动画监听

你可以用以下监听器监听动画执行期间的重要事件。

  • Animator.AnimatorListener

    • onAnimationStart():当动画开始时调用。
    • onAnimationEnd():当动画结束时调用。
    • onAnimationRepeat() :当动画重复时调用。
    • onAnimationCancel():当动画取消时调用。一个被取消的动画也会调用onAnimationEnd(),不管它们如何结束。
  • ValueAnimator.AnimatorUpdateListener

    • onAnimationUpdate():在动画的每一帧调用。我们可以监听这个事件来使用动画执行期间ValueAnimator产生的计算值。要使用这个值,拿到传进事件的ValueAnimator对象使用getAnimatedValue()方法来获得当前的动画值。如果你是用ValueAnimator,需要实现这个监听。
      根据正在进行动画的属性或对象,你可能需要使用新的动画值,调用控件(View)的invalidate()强制屏幕去重绘这个控件(View)。例如,对Drawable对象的颜色属性进行动画计算,当对象重绘自己时就会引起屏幕更新。View所有的属性的setter方法,例如setAlpha()setTranslationX()都对View作了适当的重绘,所以当你调用这些方法赋新值的时候,你不需要对View进行重绘。

如果你不想实现Animator.AnimatorListener接口的所有方法,你可以继承AnimatorListenerAdapter类代替实现Animator.AnimatorListener接口,AnimatorListenerAdapter类提供了接口所有方法的空实现,你可以有选择的去重写。
例如,API demos的Bouncing Balls案例中创建了一个AnimatorListenerAdapter只作了onAnimationEnd() 的回调:

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

7. 对ViewGroup的布局改变执行动画

属性动画系统提供了动画改变ViewGroup对象的功能,还提供了一个简便方式让View对象执行动画。

你可以使用LayoutTransition(布局切换)类对ViewGroup内的布局改变执行动画。ViewGroup内的控件(Views)会经过一个出现动画和消失动画,当你把它们添加到ViewGroup或从中移除,或者当你调用控件的setVisibility()方法设置VISIBLEINVISIBLEGONE。当你添加或移除控件时,ViewGroup中其余的控件也会动画到它们新的位置。你可以调用setAnimator()方法传入一个Animator对象和一个以下LayoutTransition的常量,给LayoutTransition对象指定下面的动画。

  • APPEARING:一个标示,表示子控件上正在容器内出现时运行的动画。
  • CHANGE_APPEARING:一个标示,表示子控件由于一个新的子控件正在容器中的出现而变化时运行的动画。
  • DISAPPEARING:一个标示,表示子控件上正从容器内消失时运行的动画。
  • CHANGE_DISAPPEARING:一个标示,表示子控件由于一个子控件正在容器中的消失而变化时运行的动画。

你可以为这四种类型的事件自定义你自己的动画,或者使用系统默认的动画。

为ViewGroup设置默认的布局转换非常简单,你只需要在xml布局文件中设置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,就会自动为容器中添加的,移除的,以及剩下的子控件执行动画。

8. 使用一个类型计算器

我们知道,Android系统的计算器类型有int,float,和color三种类型,分别由IntEvaluatorFloatEvaluator,和ArgbEvaluator三种类型的计算器支持。如果想使用一个Android系统不知道的类型的计算器来进行动画,你可以通过实现TypeEvaluator来创建自己的计算器。

你只需要实现TypeEvaluator的evaluate()方法。这使你使用的属性动画能够在当前动画点为你的动画属性返回一个合适的值。

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接收的浮点参数,所以当计算动画值时,你不必把插值器计算在内。

9. 使用一个插值器

插值器定义了动画中的具体值是如何作为一个时间函数被计算出来的。例如,你可以指定动画在整个过程中以线性执行,这就意味着在整个执行时间内,动画均匀地进行,或者你也可以指定动画使用非线性插值器,例如,动画的开始加速,结束减速。

在动画体系中插值器从Animator获得一个小数,这个小数代表动画的时间流逝。插值器修改这个小数以便与它想要提供的动画的类型一致。Android系统在Android.view.animation包下提供了一套插值器。如果这些插值器没有一个能满足你的需要,你可以实现TimeInterpolator接口创建你自己的插值器。

例如,以下比较了默认插值器中加速减速插值器(AccelerateDecelerateInterpolator)和线性插值器(LinearInterpolator)如何计算插值分数。线性插值器对时间流逝分数无影响。加速减速插值器则在动画开始时加速,动画结束时减速。下面的方法定义了这两个插值器的逻辑:

AccelerateDecelerateInterpolator

public float getInterpolation(float input) {
    return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}

LinearInterpolator

public float getInterpolation(float input) {
    return input;
}

下面的表格展示了动画持续1000ms这两个插值器计算出的近似值。

ms elapsed Elapsed fraction/Interpolated fraction (Linear) Interpolated fraction (Accelerate/Decelerate)
0 0 0
200 .2 .1
400 .4 .345
600 .6 .8
800 .8 .9
1000 1 1

如表所示,线性插值器每过200ms以相同的速度0.2对值进行改变。加速减速插值器在200ms到600ms对值的改变比线性插值器更快,在600ms到1000ms对值的改变比线性插值器慢。

10. 指定关键帧

一个Keyframe(关键帧)对象由一个time/value对(时间/值对)组成,允许你在动画的一个特定的时间确定一个特定的状态。每一个关键帧也可以拥有自己的插值器控制动画前一个关键帧到这一个关键帧时间的间隔内的运行状态。

要实例化一个关键帧(KeyFrame)对象,你必须使用ofInt(),ofFloat(),或ofObject()其中一个工厂方法,获得适当类型的KeyFrame。然后你需要调用ofKeyFrame()工厂方法获得一个PropertyValuesHolder对象。一旦你有了这个对象,你就可以通过传入这个PropertyValuesHolder对象和要执行动画的对象,获得一个animator,下面的代码片段展示了如何指定关键帧:

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(5000ms);

11. 使用ViewPropertyAnimator执行动画

ViewPropertyAnimator提供了一个简单的方式用一个底层的Animator对象,对View的几个属性并行的执行动画。它很像ObjectAnimator,因为它修改控件属性的实际值,但一次性对许多属性执行动画时会更有效率。另外,使用ViewPropertyAnimator的代码更简洁易读。下面的代码片段展示了当同时对View的x和y属性执行动画时,使用多重ObjectAnimator对象,单个ObjectAnimator,和ViewPropertyAnimator的不同。

Multiple ObjectAnimator objects (多重ObjectAnimator对象)

ObjectAnimator animX = ObjectAnimator.ofFloat(myView, "x", 50f);
ObjectAnimator animY = ObjectAnimator.ofFloat(myView, "y", 100f);
AnimatorSet animSetXY = new AnimatorSet();
animSetXY.playTogether(animX, animY);
animSetXY.start();

One ObjectAnimator (单个ObjectAnimator)

PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvyY).start();

ViewPropertyAnimator(控件属性动画)

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

12. 在xml中声明属性动画

属性动画系统允许你用XML声明属性动画代替以编程方式。通过在XML中定义动画,你可以轻松地在多个Activity中重用你的动画,并更容易地编辑动画序列。

为了区分旧的view animation框架和新的属性动画API的动画文件,从Android3.1开始,你应该保存属性动画的XML文件到res/animator/目录下。
下面的属性动画类可以用相应的标签支持XML声明:

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

为了找出你可以在XML声明中使用哪些属性,请看Animation Resouces(动画资源)。
下面的案例顺序播放了两组ObjectAnimator,第一个是嵌套的动画集合,一起播放了两个ObjectAnimator:

<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的所有子动画设置一个目标对象,如下所示:

AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,
    R.anim.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监听,获得最新的动画值,把他用于你的控件的一个属性上面,如下所示:

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

三、ValueAnimator

3.1 ValueAnimator简述和常用方法

3.1.1 简述

下面是属性动画的继承关系和层级结构:
public class ValueAnimator
extends Animator

java.lang.Object
    ↳ android.animation.Animator
        ↳ android.animation.ValueAnimator

已知直接子类
    ObjectAnimator,TimeAnimator

属性动画是Animator的子类,是ObjectAnimator的父类,是属性动画系统中最核心的一个类。

ValueAnimator为动画的运行提供了一个简单的时间引擎,这个时间引擎计算动画值并设置给目标控件。

所有的动画都使用一个定时脉冲。它运行在自定义的handler中,以确保属性更改发生在UI线程上。

默认情况下,ValueAnimator使用非线性插值器,通过AccelerateDecelerateInterpolator类,使动画在开始时加速结束时减速。我们可以通过setInterpolator(TimeInterpolator)来设置插值器。

Animator既可以用代码创建,也可以用资源文件创建。下面是一个ValueAnimator资源文件的例子:

<animator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:valueFrom="1"
    android:valueTo="0"
    android:valueType="floatType"
    android:repeatCount="1"
    android:repeatMode="reverse"/>

ValueAnimator在XML资源文件中使用<animator>标签表示。从API23开始,它也能够与ProperityValuesHolderKeyFrame资源标签结合创建一个多步骤动画。注意你可以在整个动画持续期间,为每一个关键帧指定明确的分数值(0~1)来决定什么时候动画应该到达那个值。或者你可以把那个分数去掉,那么动画帧将在总时间内平均非配:

<animator xmlns:android="http://schemas.android.com/apk/res/android"
          android:duration="1000"
          android:repeatCount="1"
          android:repeatMode="reverse">
    <propertyValuesHolder>
        <keyframe android:fraction="0" android:value="1"/>
        <keyframe android:fraction=".2" android:value=".4"/>
        <keyframe android:fraction="1" android:value="0"/>
    </propertyValuesHolder>
</animator>

3.1.2 常用方法

ValueAnimator常用公共方法

返回值 方法描述
void addUpdateListener(ValueAnimator.AnimatorUpdateListener listener)
添加一个动画更新监听到监听器结合中,这个监听在整个动画执行期间不断发送更新事件。
void cancel()
取消动画。
ValueAnimator clone()
创建并返回此对象的副本。
void end()
结束动画。
float getAnimatedFraction()
返回当前动画小数,这个小数是在动画最新帧更新中使用的时间流逝/插值分数(小数)。
Object getAnimatedValue()
当只有一个属性进行动画时,被这个ValueAnimator计算的最新值。
Object getAnimatedValue(String propertyName)
通过属性名获得被这个ValueAnimator计算的最新值。
static ValueAnimator ofArgb(int… values)
构造并返回一个在颜色值之间进行动画的ValueAnimator。
static ValueAnimator ofFloat(float… values)
构造并返回一个在浮点值之间进行动画的ValueAnimator。
static ValueAnimator ofInt(int… values)
构造并返回一个在整型值之间进行动画的ValueAnimator。
static ValueAnimator ofObject(TypeEvaluator evaluator, Object… values)
构造并返回一个在对象之间进行动画的ValueAnimator。
static ValueAnimator ofPropertyValuesHolder(PropertyValuesHolder… values)
构造并返回一个在PropertyValuesHolder对象指定的值之间进行动画的ValueAnimator。
void removeAllUpdateListeners()
从集合中删除所有侦听此动画的帧更新的侦听器。
void removeUpdateListener(ValueAnimator.AnimatorUpdateListener listener)
从集合中删除一个侦听此动画的帧更新的侦听器。
ValueAnimator setDuration(long duration)
设置动画的长度。
void setEvaluator(TypeEvaluator value)
设置类型计算器在计算此动画的动画值时使用。
void setInterpolator(TimeInterpolator value)
设置一个时间插值器用于对时间流逝分数(或小数,elapsed fraction)计算。
void setRepeatCount(int vlaue)
设置动画重复次数。
void setRepeatMode(int value)
规定动画到达终点时应该怎么做。从新开始或反转。
void setStartDelay(long startDelay)
设置调用start()方法后动画的延迟时间,单位是ms。
void start()
启动动画。

3.1.3 为ValueAnimator添加监听

ValueAnimator实现动画的原理是:通过不断控制 值 的变化,再不断 手动 赋给对象的属性,从而实现动画效果。正如它的名字一样,ValueAnimator是针对值的动画,需要我们监听值的更新,再手动赋值给对象属性。
要想将值的改变作用到对象的属性上面,需要通过它的addUpdateListener()方法为ValueAnimator添加一个动画更新监听器(ValueAnimator.AnimatorUpdateListener)。

作一个简单的实验,通过ValueAnimator的一个工厂方法ofInt()创建一个属性动画,设置好开始值和结束值,以及持续时间,添加动画更新监听(ValueAnimator.AnimatorUpdateListener),从上面ValueAnimator常用公共方法我们知道,获得动画更新值,要通过它的getAnimatedValue()方法。我们知道属性动画默认情况下10ms刷新一帧,我们设置动画时长为400ms,开始值为0,结束值为100,在动画更新监听(AnimatorUpdateListener)的回调方法onAnimationUpdate中,我们打印以下动画执行期间动画的更新值。如下代码所示:

ValueAnimator animator = ValueAnimator.ofInt(0,100);
        animator.setDuration(400);

        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int animatedValue = (int) animation.getAnimatedValue();
                Log.i("AnimatorUpdateListener", "onAnimationUpdate: animatedValue = " + animatedValue);
            }
        });

        animator.start();

打印结果如下图所示:
onAnimationUpdate

从上面的log打印我们发现:

  1. 动画值并不是均匀变化的,开始和结束时变化慢,中间变化快。正如前面ValueAnimator的简述中所提到的:默认情况下,ValueAnimator使用非线性插值器,通过AccelerateDecelerateInterpolator类,使动画在开始时加速结束时减速。所以说,ValueAnimator默认使用AccelerateDecelerateInterpolator插值器(加速减速插值器)。

  2. 打印条数少于40条。理想情况下,时长400ms,刷新频率10ms一帧的话应该是40条的。文章最开始我们提到过属性动画默认10毫秒刷新一帧,但还是要取决于系统的占用和运行速度

3.2 案例

先做一个简单的demo。demo的效果是一个背景不断渐变的TextView。使用ValueAnimator做的动画。先看效果图。

va_gradient_bg

下面的代码就是使用ValueAnimator实现了TextView背景不断变化的效果:

mBgGradientAnimator = ValueAnimator.ofInt(Color.RED, Color.BLUE).setDuration(2000);
mBgGradientAnimator.setEvaluator(new ArgbEvaluator());
mBgGradientAnimator.setRepeatCount(ValueAnimator.INFINITE);
mBgGradientAnimator.setRepeatMode(ValueAnimator.REVERSE);

mBgGradientAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        int color = (int) animation.getAnimatedValue();
        mBgGradientTextView.setBackground(new ColorDrawable(color));
        int a = (color >> 24) & 0xFF;
        int r = (color >> 16) & 0xFF;
        int g = (color >> 8) & 0xFF;
        int b = color & 0xFF;
        int textColor = a << 24
                | (0xFF - r) << 16
                | (0xFF - g) << 8
                | (0xFF - b);
        mBgGradientTextView.setTextColor(textColor);
    }
});

mBgGradientAnimator.start();

代码中使用ValueAnimator的一个工厂方法ofInt()获得了一个ValueAnimator实例,代码ValueAnimator.ofInt(Color.RED, Color.BLUE).setDuration(2000)所示,在调用ofInt()方法时,我们传入了一个开始颜色值和结束颜色值,然后设置了持续时间为2000ms。

由于我们要对颜色进行动画计算,我们需要一个可以对颜色进行计算的计算器ArgbEvaluator,通过调用setEvaluator(new ArgbEvaluator()),我们设置了颜色计算器。如果我们不使用颜色计算器,会发现效果与我们的预期差别很大,颜色虽然变化,但不是连续变化,不断的闪动。所以这个计算器是必须的。

接着我们又设置了重复模式为翻转(ValueAnimator.REVERSE),重复次数为无限次(ValueAnimator.INFINITE)。

现在我们调用start()方法是不是会达到我们的预期效果呢?显然不是。从上面的基础知识我们知道,ValueAnimator顾名思义,它是一个值的动画,通过开始值,结束值,持续时间,计算器、插值器共同作用的得到开始值与结束值之间连续的动画值。我们到这里只是完成了值的计算和生成,但是没有作用到我们的控件上。那么我们就需要通过方法addUpdateListener(),添加一个监听ValueAnimator.AnimatorUpdateListener
在这个监听的回调方法中,我们通过方法(int) animation.getAnimatedValue()强转获得颜色值,把这个颜色值设为TextView的背景颜色,就可以实现我们想要的效果了。

上面我们提到了颜色插值器ArgbEvaluator,它到底是怎么计算生成颜色值的呢?看一下它的源码可以解惑:

/**
 * This evaluator can be used to perform type interpolation between integer
 * values that represent ARGB colors.
 */
public class ArgbEvaluator implements TypeEvaluator {
    
    
    private static final ArgbEvaluator sInstance = new ArgbEvaluator();

    /**
     * Returns an instance of <code>ArgbEvaluator</code> that may be used in
     * {@link ValueAnimator#setEvaluator(TypeEvaluator)}. The same instance may
     * be used in multiple <code>Animator</code>s because it holds no state.
     * @return An instance of <code>ArgbEvalutor</code>.
     *
     * @hide
     */
    public static ArgbEvaluator getInstance() {
        return sInstance;
    }

    /**
     * This function returns the calculated in-between value for a color
     * given integers that represent the start and end values in the four
     * bytes of the 32-bit int. Each channel is separately linearly interpolated
     * and the resulting calculated values are recombined into the return value.
     *
     * @param fraction The fraction from the starting to the ending values
     * @param startValue A 32-bit int value representing colors in the
     * separate bytes of the parameter
     * @param endValue A 32-bit int value representing colors in the
     * separate bytes of the parameter
     * @return A value that is calculated to be the linearly interpolated
     * result, derived by separating the start and end values into separate
     * color channels and interpolating each one separately, recombining the
     * resulting values in the same way.
     */
    public Object evaluate(float fraction, Object startValue, Object endValue) {
        int startInt = (Integer) startValue;
        int startA = (startInt >> 24) & 0xff;
        int startR = (startInt >> 16) & 0xff;
        int startG = (startInt >> 8) & 0xff;
        int startB = startInt & 0xff;

        int endInt = (Integer) endValue;
        int endA = (endInt >> 24) & 0xff;
        int endR = (endInt >> 16) & 0xff;
        int endG = (endInt >> 8) & 0xff;
        int endB = endInt & 0xff;

        return (int)((startA + (int)(fraction * (endA - startA))) << 24) |
                (int)((startR + (int)(fraction * (endR - startR))) << 16) |
                (int)((startG + (int)(fraction * (endG - startG))) << 8) |
                (int)((startB + (int)(fraction * (endB - startB))));
    }
}

从源码的evaluate方法中我们找到了颜色的计算逻辑。我们常用的颜色使用一个十六进制的int数字表示,比如,0xFFFF0000表示红色,0xFF00FF00表示绿色,0xFF0000FF表示蓝色。0x表示十六进制,后面有8位十六进制,每两位为一组分别代表透明、红、绿、蓝。一位十六进制可以用四位二进制表示,两位十六进制代表8位二进制,也就是1字节,8位十六进制代表4字节。而我们知道一个int类型数据占4个字节,正好与此相符。

从源码中我们看到,要取出一个颜色的透明值,就要右移24位(6个十六进制位)在与0xFF进行&位运算;要取出红色部分原色,右移16位&上0xFF;取出绿色,右移8位&上0xFF;取出蓝色,直接&上0xFF。比如0xFFFFA480,取出四个色值分别为,0xFF(透明), 0xFF(红),0xA4(绿),0x80(蓝)。

源码中分别获得开始颜色和结束颜色的四个色值,每个色值都是从开始色值和结束色值之间变化,比如红色部分,通过上面的位运算,获得startR和endR,通过计算(int)((startR + (int)(fraction * (endR - startR))),获得一个介于startR和endR之间的色值(可能超出两者范围,由fraction而定)。拿到了每一种色值,再通过左移(<<)和或(|)运算,把四个色值拼成一个完整的颜色。我们拿到这个颜色就可以设置给TextView的背景了。

借助于上面的原理,我们把字体颜色也做成不断改变的。同样需要分离颜色,计算颜色,合并颜色三部。这里想达到的效果是,背景颜色如果是0xFF9980F0,字体颜色就是0xFF[0xFF - 0x99][0xFF - 0x80][0xFF - 0xF0],如上代码所示。这样,TextView的背景颜色和字体颜色都会随着时间不断变化了。

源码地址:https://github.com/wangzhengyangNo1/CustomViewDemos.git

猜你喜欢

转载自blog.csdn.net/wangxiaocheng16/article/details/73951049