动画(三)之Property Animation

 
 

一、摘要

Property Animation(属性动画)是一个非常强大的框架,它允许你让任何对象都实现动画效果。 因为不管一个对象是否出现屏幕中,你都可以随时去改变它的属性,而属性动画正是通过在某个时间点改变对象的属性实现动画效果的。Property Animation是在Android 3.0(API 11)之后推出的,以其具有高扩展性,解决了一些View Animation所不能解决的问题,所以,对于Android开发者来说,Property Animation是一个非常重要的知识点。

本文主要对ValueAnimator做介绍,如果大家有兴趣,可以继续阅读本动画系列其他相关文章,作者也在不断更新完善相关内容,希望大家可以指出有误之处。

Android基础夯实--重温动画(一)之Tween Animation

Android基础夯实--重温动画(二)之Frame Animation

Android基础夯实--重温动画(四)之属性动画 ValueAnimator详解

1.1 背景

由于Tween Animation(补间动画)只能实现简单的四种的动画(alpha、scale、rotate、translate),要想实现比较复杂的动画就难以满足需求,而Frame Animation只是改变了View对象绘制的背景,而没有改变View对象本身。所以当我们想使用View Animation实现一些特殊的动画效果时,就比较困难了。例如,当我们想改变一个控件的背景颜色时,视图动画并不能实现;当我们想设置一个按钮在位置转换之后,仍然保持点击事件,视图动画也不能实现。在这个背景之下,属性动画应运而生。

1.2 区别

说了这么多,那么视图动画和属性动画到底有什么区别呢?

首先,在直观上,属性动画是区别于视图动画的:

(1) 时间不一样: 视图动画是从API LEVEL 1就引入了;而属性动画是从API LEVEL 11之后才引入。

(2) 名字不一样: 视图动画的Tween Animation命名为xxxAnimation、Frame Animation命名为AnimationDrawable;而属性动画,则命名为xxxAnimator。

(3) 包名不一样: 视图动画的Tween Animation在包android.view.animation下,而Property Animation在包android.animation中。

其次,从根本上,我们可以总结出视图动画和属性动画的两个主要区别:

(1) 视图动画只能改变View的位置或者视觉效果,并不能改变其属性。例如:使用Tween Animation对Button实现位移变换后位置改变,但是点击Button最后停留位置时,并不能响应点击事件。怎么理解呢,举个例子:

视图动画不能改变其属性

由上图可知,我们给TextView设置了点击事件,当我们分别使用Tween Animation和Property Animation移动TextView时,当使用Tween Animation位移TextView后,它的点击事件无效,当使用Property Animation移动TextView后,它的点击事件仍然有效。由此可见,视图动画并不能改变View的属性,而属性动画可以。

(2) 视图动画作用对象只限制为View,而属性动画作用对象不限为View,而是任何对象。例如:属性动画可以改变颜色值而视图动画做不到。同样举个例子:

视图动画只作用于View

由上图可以看到,当我们需要对一个对象的颜色值进行改变时,视图动画并不能实现这个效果,上图是通过属性动画来实现的,由此可以推测出,视图动画只能对View起作用,而属性动画作用的不只是View,而是对象。

1.3 建议

虽然Property Animation的优点要多于View Animation,但是View Animaiton可以让我们花更少的时间和更少的代码去实现,所以如果View Animation已经足以满足我们的日常需要,那么我们就没必要使用Property Animation了,当然,如果我们都涉及到的话,同时使用View Animation和Property Animation可能是更有效的办法。

如果你想了解更权威的解释,可以查看官方文档:Property Animation

本文主要对Property Animation做介绍,如果大家有兴趣,可以继续阅读本动画系列其他相关文章:

Android基础夯实--重温动画(一)之Tween Animation

Android基础夯实--重温动画(二)之Frame Animation

二、工作原理

在讲述具体Property Animation相关API之前,我想先给大家讲一下属性动画是如何工作的。下面通过一个例子,这是Android开发指南上面的一个例子,我觉得非常好理解,这里就搬过来了。

首先,我们来看一个例子。如下图描述的是一个对象在它的x方向上进行水平运动的动画(规定右方向为正轴),当然我们可以对应成手机屏幕上的位置。这个动画的时长为40ms,对象在x正方向运动了40个像素,在每个10ms内,这个对象就往x正方向运动10个像素,在第40ms时,这个动画停在了x方向上的40像素,这是一个水平匀速运动的例子。

图1 匀速水平运动示例
图1 匀速水平运动示例

当然,我们也可以给动画定义一个具有不匀速插值器(Interpolation),是它运动过程为不匀速。 如下图也是一个对象的运动过程,但是它并不是匀速运动,而是开始加速,在结束前减速。这个对象仍然是在40秒内运动了40个像素的距离,但是这个过程是不匀速的,它从开始到中间位置进行了加速运动,在中间位置到结束位置则进行了减速运动。

图2 非匀速水平运动示例
图2 非匀速水平运动示例

从上面两个例子可以看到,当我们想要一个对象实现一定的动画效果时,我们可以通过对应的图,还有相关的数据,和相关数据伴随时间的变化来描述该动画过程,但是我们的属性动画的内部是如何像我们这样来描述自身的变化过程的呢?以ValueAnimator为例,我们来看一下属性动画的重要组成部分,如下图所示。

图3 属性动画的组成部分
图3 属性动画的组成部分

ValueAnimator是属性动画最基础的一个类(我们暂且不深究它,反正它能帮助我们实现图2的先加速后减速的过程)。首先它在内部封装了非常重要的两个接口,第一个就是TimeInterpolator,另一个是TypeEvaluator。大家在心里必须先有个概念,在所有的属性动画里面,都拥有这两个东西。

TimeInterpolator是什么呢?非常简单,就是我们上面所说的插值器,简单来说就是描述对象加速度的一个东西,再简单一点来说就是描述速度变化的一个东西。

TypeEvaluator又是什么呢?大家可以理解为求值器,它是根据上面的插值器来计算对象属性具体值的这么一个东西。

ValueAnimator在执行之前,首先会把时间分为百分数,由0~1,如上图动画过程为40ms,那么在10ms时,时间因子为0.25,在40ms时,时间因子为1。

在计算完时间因子之后,ValueAnimator会调用TimeInterpolator来进行计算插值因子,在图2对应为速度,对应10ms时的速度我们知道会比20ms时的速度会低;而图1中,每一个时刻的速度都一样,所以TimeInterpolator大概是做这么一件事情。

在TimeInterpolator计算完了之后,那么我们的TypeEvaluator就要起作用了,因为上图对应的是对象的位置变化,所以TypeEvaluator为IntEvaluator。这个TypeEvaluator主要是根据TimeInterpolator提供的插值因子(速度),还有startPropertyValue(开始时间)和endPropertyValue(结束时间),计算出某个时刻的属性值(位移),如图2,假如t=10ms时刻,TimeInterpolator给我们返回值为0.15,那么这时在x方向上的位移为0.15 * (40 - 0) = 6。

而这种计算过程在动画执行时间(duration)内是不断重复的,因为ValueAnimator有一个叫做AnimatorUpdateListener的监听器,它会跟踪动画的每一个时刻,所以我们可以在里面进行不断的计算,通过getAnimatedValue()来获取最新值,直到动画结束。

一个属性动画的执行过程大概如上,相信大家已经对属性动画有了基本的了解,那么我们下面根据API来对属性动画进行详细讲解。

三、API概况

前面我们说到,属性动画机制所有相关的类都位于android.animation包之下,所以大家有需要可以到官方文档中进行查阅。下面几个表格是属性动画中常用的类,首先给大家大概介绍。

表格1. Animators

Class Description
ValueAnimator 针对值变化的Animator。
ObjectAnimator 针对Object变化的Animator。
AnimatorSet 运行一组Animator的集合。

Animator类作为属性动画的基类,它是一个抽象类,它提供了实现动画的基本架构,但是我们不能直接使用它,因为它只是提供了最基本的的实现动画的方法,只有让它的子类继承它并进行相应扩展之后,我们才会使用它实现动画。在属性动画中,Animator包括了ValueAnimator、ObjectAnimator和AnimatorSet三个子类,我们分别来介绍一下这三个类。

表格2. Evaluators

Class Description
TypeEvaluator 求值器接口,所有求值器必须实现该接口。
IntEvaluator 计算Int类型的求值器。
FloatEvaluator 计算Float类型的求值器。
ArgbEvaluator 计算颜色值类型的求值器。

API中为我们提供了求值器的接口TypeEvaluator,当然还要它的实现类,例如IntEvaluator、FloatEvaluator、ArgbEvaluator、 IntArrayEvaluator、FloatArrayEvaluator等,它们都是根据fraction因子来计算出对应的属性值,当然我们也可以自定义自己的Evaluator。

表格3. Interpolators

Class Description
TimeInterpolator Animator的插值器接口。

TimeInterpolator作为属性动画的插值器接口,我们都知道在View Animation中有很多插值器,例如AccelerateDecelerateInterpolator(前后减速,中间加速)、AccelerateInterpolator(先慢后加速)等插值器,在属性动画中我们同样可以使用这些插值器,这里就不详细列出来了,不了解的同学可以看我之前的文章:Android基础夯实--重温动画(一)之Tween Animation。当然,我们也可以自定义自己的Interpolator。

 
 
 
 

ValueAnimator

ValueAnimator,就是针对值的,也就是说ValueAnimator不会对控件进行任何操作,而是控制值的变化,然后我们监听这个值的变化过程,自己来控制控件的变化。什么意思呢?就像我们上面1.2中的例子,使用属性动画来控制TextView的位移,我们在初始化ValueAnimator时,会设置一个初始值和结束的值,例子我用这两个值来控制TextView在y轴上的位置,然后设置监听器,监听初始值变化到结束值的过程,在不断变化过程中,通过调用TextView的layout方法来不断更新TextView的位置,从而实现位移动画。

2.1 初识ValueAnimator

先上一个例子,实现图片的渐变过程:

Alpha

我们都知道,在使用Tween Animation时是非常容易实现的,使用AlphaAnimation就可以实现,假如我们用属性动画的话,怎么实现呢?也是非常简单,布局代码就不贴了,看看使用ValueAnimator如何简单快捷地实现渐变动画。

// 第一步,创建一个ValueAnimator。直接调用ValueAnimator.ofFloat来初始化,设置开始值和结束值
final ValueAnimator alphaAnimator = ValueAnimator.ofFloat(1, 0);
// 设置变化时长
alphaAnimator.setDuration(1000);
alphaAnimator.start();

// 第二步,ValueAnimator设置监听器
alphaAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        // 我们来检查一下这个方法会调用多少次
        Log.i("TAG", "curValue is " + animation.getAnimatedValue());
        // 在ValueAnimator变化的过程中更新控件的透明度
        mBinding.image.setAlpha((float)alphaAnimator.getAnimatedValue());
    }
});

而我们在监听器内设置的Log的结果如下,我们可以看到onAnimationUpdate方法被不断地执行,输出值不断由我们设置的初始值变化到我们设置的结束值,所以这个值的变化过程正是我们需要让控件变化的过程。

Log结果

通过例子,我们可以大概总结使用ValueAnimator的两个主要过程:

(1). 初始化ValueAnimator,并设置初始值和结束值,还有动画的时间,然后start。

(2). 给ValueAnimator设置监听器,通过getAnimatedValue()拿到变化值,然后我们手动更新控件的变化。

2.2 深入了解ValueAnimator

由于ValueAnimator里面的方法确实不少,所以我们从上面的例子入手,从常用到不常用地讲解ValueAnimator的API,毕竟只要我们掌握了最常用的知识点之后,在我们需要时再去深入了解不常用的知识点,我觉得是个最有效率的学习方式。

由上面的Demo代码的第一步我们可以看到,首先我们需要获取到一个ValueAnimator实例,按照我们的常规思维,我们都会通过new一个对象出来,以代码为例,我们是通过ofFloat方法来获取一个实例对象,那么我们的第一个疑问就是关于构造函数的,到底ValueAnimator有没有构造函数呢?如果有,为什么不通过构造函数来初始化呢?

答案是有的,至于为什么,我们一探究竟。

2.2.1构造函数

  • ValueAnimator():创建一个ValueAnimator对象。

ValueAnimator确实有它的构造函数,但是官方文档不建议我们直接使用它,因为在内部实现的时候才会用到它。之所以不需要用到它,是因为API给我们封装了一系列的的方法来获取实例对象。

2.2.2实例化对象的方法

  • ValueAnimator ofInt (int... values):返回一个int型变化的ValueAnimator。
  • ValueAnimator ofFloat (float... values):返回一个float型变化的ValueAnimator。
  • ValueAnimator ofObject (TypeEvaluator evaluator, Object... values):返回一个object型变化的ValueAnimator。
  • ValueAnimator ofArgb (int... values):返回一个颜色值变化的ValueAnimator,API LEVEL 21引入。
  • ValueAnimator ofPropertyValuesHolder(PropertyValuesHolder... values):返回一个PropertyValuesHolder型变化的ValueAnimator,在ObjectAnimator再详说。

为什么我们需要通过这些方法来实例化对象呢?这是因为这些方法内部都对实例化对象进行了封装,我们以ofInt为例看一下它的内部实现,它内部其实还是通过new的方式来实例化,然后通过设置一些属性,然后返回这个ValueAnimator对象。

public static ValueAnimator ofInt(int... values) {
    ValueAnimator anim = new ValueAnimator();
    anim.setIntValues(values);
    return anim;
}

ofArgb的使用

在ValueAnimator中的ofArgb()可以帮助我们实现颜色的渐变效果,Google在API LEVEL 21之后增加了这个方法ofArgb()。通过这个方法我们更容易地实现颜色演变,通过ofArgb和ArgbEvaluator,我们可以轻松实现颜色渐变效果:

ofInt

代码:

ValueAnimator animator = ValueAnimator.ofInt(0xffff00ff, 0xffffff00, 0xffff00ff);
animator.setEvaluator(new ArgbEvaluator());
animator.setDuration(3000);
animator.start();
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        mBinding.image.setBackgroundColor((Integer) animation.getAnimatedValue());
    }
});

ofObject的使用

ofObject方法是什么意思呢?我们都知道ofInt和ofFloat都是针对Int值和Float值的变化,但是,我们只能控制一个值的变化,但是当我们需要实现多值变化时,它们就不再满足我们的需求。例如我们需要同时实现位移、透明度变化等动画,这里需要设置两个属性值的变化,所以如果我们只有一个初始值是不行的,因为两个属性的初始值和结束值不一样,那么我们就可以将两个属性值封装到一个对象里面,那么初始值的object和结束值的object就可以包含两个属性不同的初始值和结束值了。

下面是一个对ValueAnimator ofObject (TypeEvaluator evaluator, Object... values)方法具体使用的小Demo,实现图片的放大和渐变过程,先看效果图:

image

首先我们看到ofObject的参数里面有一个TypeEvaluator和一个Object型可变参数,一般传入一个初始值和结束值,首先TypeEvaluator就是一个计算值的工具,API提供有现成的(下面详说),也可以自己实现(这里为了给大家知道是个什么东西,就自己实现);然后Obejct型,我们自己写一个类代替Obejct型。

因为我们有两个动画,包括放大和透明度变化,我们定义一个叫ValueObject的类,里面就包含两个属性,代码也非常简单:

class ValueObject {
    float alphaValue;   //透明度的值
    float scaleValue;   //伸缩变化的值

    public ValueObject(float alphaValue, float scaleValue) {
        this.alphaValue = alphaValue;
        this.scaleValue = scaleValue;
    }
}

然后,我们就需要自定义TypeEvaluator了,因为TypeEvaluator是一个接口,我们就写一个名叫MyEvaluator的类,它实现了TypeEvaluator的接口,传入我们的值类型为ValueObject,然后重写evaluate方法(TypeEvaluator接口只有这个方法需要实现),代码也很简单:

class MyEvaluator implements TypeEvaluator<ValueObject> {

    // 属性动画封装了一个因子fraction,我们设置动画时需要setDuration(xxxx),例如时间为1000ms,那么当到达100ms时,fraction就为0.1
    // fraction也就是当前时间占总时间的百分比,startValue和endValue就是我们传入的初始值和结束值
    @Override
    public ValueObject evaluate(float fraction, ValueObject startValue, ValueObject endValue) {
        // 计算某个时刻的alpha值和scale值。类似速度公式Vt = V0 + at
        float nowAlphaValue = startValue.alphaValue + (endValue.alphaValue - startValue.alphaValue) * fraction;
        float nowScaleValue = startValue.scaleValue + (endValue.scaleValue - startValue.scaleValue) * fraction;
        return new ValueObject(nowAlphaValue, nowScaleValue);
    }
}

这两个类我们都实现了,那么动画就很简单了:

public void objectAnimation() {
    // 初始alpha值为1,scale值为1
    ValueObject startObjectVal = new ValueObject(1f, 1f);
    // 结束alpha值为0,scale值为2,相当于透明度变为0,尺寸放大到2倍
    ValueObject endObjectVal = new ValueObject(0f, 2f);
    MyEvaluator myEvaluator = new MyEvaluator();

    final ValueAnimator animator = ValueAnimator.ofObject(myEvaluator, startObjectVal, endObjectVal);
    animator.setDuration(3000);
    animator.start();

    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            mBinding.image.setAlpha(((ValueObject) animation.getAnimatedValue()).alphaValue);
            mBinding.image.setScaleType(ImageView.ScaleType.CENTER);
            mBinding.image.setScaleX(((ValueObject) animation.getAnimatedValue()).scaleValue);
            mBinding.image.setScaleY(((ValueObject) animation.getAnimatedValue()).scaleValue);
        }
    });
}

2.2.3常用方法

  • void addUpdateListener(ValueAnimator.AnimatorUpdateListener listener):添加值变化监听器。主要监听值变化,实现动画。
  • void addUpdateListener(AnimatorUpdateListener listener):添加动画状态监听器。重写动画开始、结束、取消、重复四个方法,监听不同状态。
  • void cancel (): 取消动画。
  • void end ():让动画到达最后一帧。
  • void start():开始动画。
  • void pause():暂停动画。
  • void resume():继续动画。
  • void reverse ():反向播放动画。
  • boolean isRunning():是否在运行中。
  • boolean isStarted():是否已经开始。

2.2.4属性相关的方法

  • void setCurrentFraction(float fraction):设置当前时间因子。即时间到达的百分比。

  • float getAnimatedFraction():获取当前时间因子。即时间到达的百分比。

  • void setCurrentPlayTime (long playTime):设置当前的时间,取值为0-duration,单位毫秒。

  • long getCurrentPlayTime ():获取当前的时间,单位毫秒。

  • ValueAnimator setDuration (long duration):设置动画总时长,单位毫秒。

  • long getDuration ():获取动画总时长,单位毫秒。

  • void setFrameDelay (long frameDelay):设置每一帧之间间隔多少毫秒。

  • long getFrameDelay ():获取每一帧之间间隔多少毫秒。

  • void setInterpolator (TimeInterpolator value):设置动画的Interpolator,和View Animation的Interpolator通用。

  • TimeInterpolator getInterpolator ():获取当前使用的插值器。

  • void setRepeatCount(int value):设置重复次数。

  • int getRepeatCount():获取重复次数。

  • void setRepeatMode(int value):设置重复模式。有RESTART和REVERSE两种。

  • int getRepeatMode():获取重复模式。

  • void setStartDelay(long startDelay):设置开始前延迟毫秒数。

  • long getStartDelay():获取开始前延迟毫秒数。

  • void getAnimatedValue():获取计算出来的当前属性值。

  • getAnimatedValue(String propertyName):获取计算出来的当前某个属性的值。

  • void setEvaluator(TypeEvaluator value):设置求值器。

  • void setFloatValues(float... values):设置Float型变化值,一般设置初始值和结束值,当然你也可以设置中间值,因为这是一个可变参数,长度可变。

  • void setIntValues(int... values):设置Int型变化值,一般设置初始值和结束值,当然你也可以设置中间值,因为这是一个可变参数,长度可变。

  • setObjectValues(Object... values):设置Object型变化值,一般设置初始值和结束值,当然你也可以设置中间值,因为这是一个可变参数,长度可变。

2.2.5监听器

ValueAnimator有两个监听器,一个是AnimatorListener,一个AnimatorUpdateListener,通过代码我们查看它们的区别。 AnimatorListener主要是用来监听动画不同状态的监听器,从代码中我们可以看到它有四种不同的状态,当我们需要在不同状态中进行不同操作时,我们可以实现这个监听器。AnimatorUpdateListener是监听ValueAnimaitor的值不断变化的过程,通常使用这个监听器更新控件状态,实现动画过程。

// AnimatorListener主要是用来监听动画不同状态的监听器
animator.addListener(new Animator.AnimatorListener() {
    @Override
    public void onAnimationStart(Animator animation) {
        Log.i("TAG", "start");
    }

    @Override
    public void onAnimationEnd(Animator animation) {
        Log.i("TAG", "end");
    }

    @Override
    public void onAnimationCancel(Animator animation) {
        Log.i("TAG", "cancel");
    }

    @Override
    public void onAnimationRepeat(Animator animation) {
        Log.i("TAG", "repeat");
    }
});
// AnimatorUpdateListener是监听ValueAnimaitor的值不断变化的过程
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        Log.i("TAG", "curVal:" + animation.getAnimatedValue());
    }
});

三、Interpolator

Interpolator,译名插值器,在我的意思里就是加速器,即这是一个改变我们动画速率的一个工具,可以实现加速、减速、匀速等这些特效。我们在Android基础夯实--重温动画(一)之Tween Animation第五部分讲了Android提供给我们使用的插值器,其实属性动画和视图动画是共用一套Interpolator的。在上面我们讲到,在属性动画中,我们可以通过setInterpolator (TimeInterpolator value)来给我们的动画增加一个插值器,传入参数是TimeInterpolator,通过查阅API,我们可以知道,TimeInterpolator是一个接口。我们再来看看它和我们常用的插值器的关系。

我们常用的插值器,如AccelerateDecelerateInterpolator,AccelerateInterpolator, AnticipateInterpolator,AnticipateOvershootInterpolator等,它们的父类是BaseInterpolator。

而BaseInterpolator是实现了Interpolator,而Interpolator则是继承TimeInterpolator接口。所以究其根源,我们常用的插值器和属性动画使用的TimeInterpolator其实是同一个东西。

既然了解了它们是同一个东西,那么我们就需要了解怎么来实现一个自己的Interpolator了,一般我们只要继承BaseInterpolator,并实现它的getInterpolation(float input)方法就行了。

举个例子,Android提供给我们的LinearInterpolator(这是一个匀速插值器)中,它的getInterpolation是这样的:

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

首先我们看一下参数input是什么,input表示当前动画的进度,它的取值范围是0-1,0代表起点,1代表终点,随着动画的播放,input从0到1逐渐变大;而返回值就是指当前的实际进度,听起来有点拗口,我们可以这么想,例如本来当input为0.1的时候,我们返回值如果大于0.1,那么就说明我们从0到0.1这个阶段是一个加速阶段,如果小于0.1,就说明这是一个减速过程。可以看到LinearInterpolator是直接把input返回,可以知道这是一个匀速的过程。

再来看看AccelerateDecelerateInterpolator,这是开始和结束速度慢,中间部分加速。我们来看一下它的getInterpolation函数:

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

可以看到这就是一个余弦公式,因为0-1这个时间内,刚开始和结束前这两个部分斜率是比较低的,所以速度会比较慢,但是中间部分斜率明显变大,所以中间部分呈现加速状态。

余弦公式

经过这两个例子,我们大概知道,当我们需要实现一个Interpolator时,只需要继承BaseInterpolator,并实现它的getInterpolation(float input)方法就行了,举个例子:实现一个0-0.25秒内到达3/4,0.25-0.75秒内从3/4退回1/4,最后0.25秒内从1/4达到终点,先上效果图让大家比较直观了解:

Demo

所以我们可以很清楚的列出关系式:

关系式

那么在getInterpolation中,对应根据input列出算法:

算法

那么代码也自然出来了:

class MyInterpolator extends BaseInterpolator {
    @Override
    public float getInterpolation(float input) {
        if (input <= 0.25) {
            return 3 * input;
        } else if (input <= 0.75) {
            return (1 - input);
        } else {
            return 3 * input - 2;
        }
    }
}

四、Evaluator

Evaluator在属性动画中也是起着重要的一环。先看一张图:

Evaluator

我们可以看到,当Interpolator返回了当前进度滞后,Evaluator就会根据进度来计算出确定的值,以供监听器返回,所以我们就可以知道了,Evaluator其实就是一个根据我们需求而制作的一个计算器。

其实在上面的例子我已经简单地教大家自定义了一个Evaluator,在属性动画中,Android 也为我们提供了很多的Evaluator,例如IntEvaluator,FloatEvaluator等,我们可以先看一下IntEvaluator的底层实现:

public class IntEvaluator implements TypeEvaluator<Integer> {

    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int)(startInt + fraction * (endValue - startInt));
    }
}

代码非常的简单,只是重写了一个evaluate方法,在返回值中是一条公式,就是根据开始值和结束值,当前进度,计算结果,并返回,这条公式也是非常简单,这里就不详说了。但是实际开发中,有时候原生的Evaluator不适合我们使用的时候,我们就需要自定义一个Evaluator,正如我上面的例子中用到的,当我们使用了自定义的Object作为初始值和结束值时,我们就需要定义一个自己的Evaluator。下面举一个为了自定义而自定义的Evaluator:

Evaluator

由图可知,自定义的Evaluator就是在FloatEvalutor的基础之上加了200个像素,而我自定义的Evaluator也是修改了以下FloatEvaluator的代码:

class MyEvaluator implements TypeEvaluator<Float> {

    @Override
    public Float evaluate(float fraction, Float startValue, Float endValue) {
        return startValue + fraction * (endValue - startValue) + 200;
    }
}

这是FloatEvaluator的代码:

public class FloatEvaluator implements TypeEvaluator<Number> {
    public Float evaluate(float fraction, Number startValue, Number endValue) {
        float startFloat = startValue.floatValue();
        return startFloat + fraction * (endValue.floatValue() - startFloat);
    }
}

所以到这里大家也可以大概了解了怎么自定义Evaluator,非常的简单,实现TypeEvaluator接口,并传入一个类型,也就是初始值和结束值的类型,然后重写evaluate方法,根据当前进度fraction来计算当前的返回值即可。

五、 总结

总体来说,ValueAnimator并不会很难,只要我们掌握了Animator的初始化、初始值、结束值、fraction、Evaluator、监听器的概念,那么我们基本掌握了ValueAnimator的使用,当然,伴随着我们的重复使用、加深理解,当然我们离熟悉掌握ValueAnimator也不远了。当然Animator中除了ValueAnimator以外,还有ObjectAnimator,这也是一个非常重要的概念,下一篇,我给大家带来ObjectAnimator的详解。

 
    

概述

在上节我们知道了在属性动画中,ValueAnimator是通过监听值的变化,然后实现控件的动画播放。在代码过程中,是通过设置初始值、结束值和动画时间,然后通过加速器返回当前的进度的,再经过Evaluator根据进度计算出具体的值,然后我们在监听器里面不断监听拿到这个值,然后修改控件的属性值,从而实现动画。

ObjectAnimator作为ValueAnimator的子类,所以ValueAnimator的很多方法,在ObjectAnimator中也能使用,但是ObjectAnimator覆写了父类的几个方法,如ofInt(),ofFloat(),ofArgb()等。它和ValueAnimator同样也是首先设置初始值、结束值和动画时长,但是同时也绑定了目标控件和属性然后通过加速器返回当前的进度的,再经过Evaluator根据进度计算出具体的值,最后根据属性拼接set函数并反射调用,并将当前值作为参数传入,实现动画。

对比这两个Animator,ObjectAnimator是对ValueAnimator的再封装,它的封装帮助我们避免了使用Listener的麻烦,更加精简了代码,使开发者可以更加专注动画的逻辑代码。

2.1 差异

以下通过Demo来对比ObjectAnimator和ValueAnimator的区别,我们同样使用ObjectAnimator和ValueAnimator实现同样的效果,控件水平方向上的不断左右移动,最后返回原点(如下图)。

Demo

通过上一节的学习,我们可以轻松写出ValueAnimator实现的代码:

ValueAnimator animator = ValueAnimator.ofFloat(0, 300, -100, 200, -50, 0);
animator.setDuration(2000);
animator.start();
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        mBinding.image.setTranslationX((Float) animation.getAnimatedValue());
    }
});

在ObjectAnimator中,我们可以用更简单的代码来实现:

ObjectAnimator animator = ObjectAnimator.ofFloat(mBinding.image, "translationX", 0, 300, -100, 200, -50, 0);
animator.setDuration(2000);
animator.start();

通过对比,我们可以看到,ObjectAnimator在实现动画上的代码上会更加简洁,但是实现的效果都是一样的,之所以是这样,是因为ObjectAnimator覆写了ValueAnimator 的ofFloat方法,并对其进行了封装。所以,我们看到ObjectAnimator实现代码上都减少了Listener代码的编写。

大家第一次看到ObjectAnimator可能看到代码比较陌生,上面的一段代码什么意思呢?首先我们看到第一个参数,就是我们动画的目标控件,也就是这个动画要让哪个控件来实现,我这里是DATABinding的一个写法,其实就是我们findViewById得到的一个view,非常的简单;第二个参数是动画要实现的效果,我这里是translationX,即水平x方向上的位移;第三个参数为可变参数,即动画变化过程的系列值,跟ValueAnimator是一样的意思。

参数

2.2 propertyName

在2.1的介绍过后,大家可能还会有疑问,对于上面ofFloat的第二个参数是一个字符串常量,这个字符串常量我们是怎么获取的呢?其实,在ObejctAnimator中,无论ofFloat,ofInt,ofArgb等方法,都有一个叫做propertyName的参数,也就是我们上面对应的第二个参数,这个参数是一个字符串,之所以动画会实现该字符串的效果是因为ObjectAnimator通过反射机制,找到了ImageView中的setTranslationX()这个方法,然后每个十几ms就调用这个方法,并把我们的变化的值传到里面去,从而实现动画效果。

所以到这里大家可以知道,当我们需要用ObjectAnimator实现一个控件的动画效果时,我们首先需要做的就是在这个控件中找到对应的setXXX()驼峰式写法的方法,只有控件拥有相应的setXXX()方法, 我们传入的propertyName参数才起到作用。那么这时候就有同学会问,类似Demo中的“translationX”,它对应的方法是setTranslationX(),那我们应该传入“TranslationX”还是“translationX”,其实都是可以的,因为它内部封装在使用反射机制调用方法时,涵盖了两种写法,所以第一个字母可以大小写,但是后面的字母必须全部对应大小写。

其次还要同学会疑问,我们怎么知道在实例化ObjectAnimator时应该通过ofFloat还是ofInt还是其他呢?其实跟我们的ValueAnimator一样,我们调用哪个方法来实例化都是要考虑我们要改变的控件哪个属性的。例如,我们上面的例子是改变水平方向上的位移,那么ObjectAnimator最终是调用控件的setTranslationX(float translationX),我们可以看到传入参数是float型,那么毫无疑问,我们这里需要使用ofFloat了,其它以此类推。下面给大家举例一些常用的propertyName。

2.2.1 Scale

View中关于伸缩变化(Scale)有以下两个方法:

  • public void setScaleX(float scaleX):X方向上伸缩。
  • public void setScaleY(float scaleY):Y方向上伸缩。
Scale

所以对应代码为:

ObjectAnimator animator = ObjectAnimator.ofFloat(mBinding.image, "scaleX", 0f, 1.5f, 2f, 1.5f, 0f, 0.5f, 0.2f, 1f);
mBinding.image.setScaleType(ImageView.ScaleType.CENTER_CROP);
animator.setDuration(2000);
animator.start();
View view = new View(MyObjectAnimator.this);
ObjectAnimator animator = ObjectAnimator.ofFloat(mBinding.image, "scaleY", 0f, 1.5f, 2f, 1.5f, 0f, 0.5f, 0.2f, 1f);
animator.setDuration(2000);
mBinding.image.setScaleType(ImageView.ScaleType.CENTER_CROP);
animator.start();

2.2.2 Translation

View中关于位置变化(Translation)有以下两个方法:

  • public void setTranslationX(float translationX):X轴上位移。
  • public void setTranslationY(float translationY):Y轴上位移。
  • public void setTranslationZ(float translationZ):设置阴影。
Translation

所以对应代码为:

ObjectAnimator animator = ObjectAnimator.ofFloat(mBinding.image, "translationX", 0, 100, -100, 0);
mBinding.image.setScaleType(ImageView.ScaleType.CENTER_CROP);
animator.setDuration(2000);
animator.start();
ObjectAnimator animator = ObjectAnimator.ofFloat(mBinding.image, "translationY", 0, 100, -100, 0);
animator.setDuration(2000);
mBinding.image.setScaleType(ImageView.ScaleType.CENTER_CROP);
animator.start();

2.2.3 Alpha

View中关于透明度变化(Alpha)有以下方法:

  • public void setAlpha(float alpha);
Alpha

所以对应代码为:

ObjectAnimator animator = ObjectAnimator.ofFloat(mBinding.image, "alpha", 0, 0.5f, 1.0f);
mBinding.image.setScaleType(ImageView.ScaleType.CENTER_CROP);
animator.setDuration(2000);
animator.start();

2.2.4 Rotation

View中关于角度变化(Rotation)有以下方法:

  • public void setRotation(float rotation):关于Z轴旋转。
  • public void setRotationX(float rotationX):关于X轴旋转。
  • public void setRotationY(float rotationY):关于Y轴旋转。
Rotation

所以对应代码为:

ObjectAnimator animator = ObjectAnimator.ofFloat(mBinding.image, "rotation", 0, 180, 0, -180, 0);
mBinding.image.setScaleType(ImageView.ScaleType.CENTER_CROP);
animator.setDuration(3000);
animator.start();
ObjectAnimator animator = ObjectAnimator.ofFloat(mBinding.image, "rotationX", 0, 180, 0, -180, 0);
animator.setDuration(3000);
mBinding.image.setScaleType(ImageView.ScaleType.CENTER_CROP);
animator.start();
ObjectAnimator animator = ObjectAnimator.ofFloat(mBinding.image, "rotationY", 0, 180, 0, -180, 0);
animator.setDuration(3000);
mBinding.image.setScaleType(ImageView.ScaleType.CENTER_CROP);
animator.start();

2.2.5 图片变化

View中关于图片的其中一个方法是setBackgroundResource,同样我们也可以通过ObjectAnimator来实现一个图片变化的效果:

  • public void setBackgroundResource(@DrawableRes int resid)
图片变化

对应代码为:

ObjectAnimator animator = ObjectAnimator.ofInt(mBinding.image, "backgroundResource", R.drawable.a1, R.drawable.a2, R.drawable.a3);
animator.setDuration(2000);
animator.start();

其实还有很多set方法可以可以实现动画效果,这里就不再一一列举,大家有兴趣可以自己去深入研究,你会发现ObjectAnimator可以非常简单快捷地实现动画效果。

2.3 实例化ObjectAnimator方法

在ValueAnimator中,我们知道实例化并不是通过new一个对象出来,而是通过ofInt,ofFloat,ofObject等方法。在ObjectAnimator中同样如此,因为ofInt,ofFloat,ofObject等方法的内部帮我们封装了实例化过程,所以我们可以直接调用来拿到一个实例化的对象。在ObjectAnimator中,大概有以下几种实例化方法:

2.3.1 ofInt()

通过ofInt()来实例化对象,那么属性值必须为int型,通常我们通过ofInt可以实现很多动画,例如实现颜色渐变等;ofInt()也有几个重载函数,这里介绍其中一个:

  • ofInt(Object target, String propertyName, int... values):对目标对象T的property属性值进行改变。

例如颜色值的变化。

ofInt
ObjectAnimator animator = ObjectAnimator.ofInt(mBinding.image, "backgroundColor",  0xffff00ff, 0xffffff00, 0xffff00ff);
animator.setEvaluator(new ArgbEvaluator());
animator.setDuration(4000);
animator.start();

2.3.2 ofFloat()

ofFloat()来实例化对象,那么属性值必须为float型,通常我们通过ofFloat可以实现很多动画,例如实现位置变化等;ofFloat()也有几个重载函数,这里介绍其中一个:

  • ObjectAnimator ofFloat (Object target, String xPropertyName, String yPropertyName, Path path)::对目标对象T的property属性值进行改变。

例如实现一个贝塞尔曲线:

ofFloat
Path path = new Path();
path.quadTo(800, 200, 800, 800);
ObjectAnimator animator = ObjectAnimator.ofFloat(mBinding.image, "x", "y", path);
animator.setDuration(4000);
animator.start();

2.3.3 ofArgb()

我们在ValueAnimator中已经提到了ofArgb()可以帮助我们实现颜色的渐变效果,这里同样是可以通过ofArgb()来实现动画效果。上面我们已经在ofInt里面实现了颜色渐变,但是代码稍多,所以Google在API LEVEL 21之后增加了这个方法ofArgb()。通过这个方法我们更容易地实现颜色演变,因为它里面封装了对ArgbEvaluator的使用,实现2.3.1的效果,大家可以对比一下代码:

ofInt
ObjectAnimator animator = ObjectAnimator.ofArgb(mBinding.image, "BackgroundColor", 0xffff00ff, 0xffffff00, 0xffff00ff);
animator.setDuration(4000);
animator.start();

2.3.4 ofPropertyValuesHolder()

认真的同学都会发现,在ValueAnimator和ObjectAnimator中,都有一个实例化方法,就是ofPropertyValuesHolder()方法,由于在ObjectAnimator中使用更为广泛,所以这里以ObjectAnimator的ofPropertyValuesHolder为例子,当大家懂了之后,那么大家对ValueAnimator的ofPropertyValuesHolder也应该理解了。

在ObjectAnimator中,我们可以通过

  • ObjectAnimator ofPropertyValuesHolder (Object target,
    PropertyValuesHolder... values)

来实例化一个ObjectAnimator。

我们可以看到,它和我们其他的实例化方法差不多,都需要设置一个target(目标控件),还有一组PropertyValuesHolder类型的值,但是不需要设置属性,target我们知道了,是要实现动画的控件,那么PropertyValuesHolder是什么呢?我们来看一下官方文档:

This class holds information about a property and the values that that property should take on during an animation. PropertyValuesHolder objects can be used to create animations with ValueAnimator or ObjectAnimator that operate on several different properties in parallel.

什么意思呢?

这是一个包含一个属性信息的类,并且它的值应该用到一个动画里面。PropertyValuesHolder对象可以配合ValueAnimator和ObjectAnimator来实现不同属性的并行的动画。

听起来有点别扭,也就是说,当我们需要实现一个包含多种属性的同时播放的动画时,我们就可以使用ofPropertyValuesHolder来实例化一个Animator,当然,拥有一个属性时也是可以的,为什么这么说?ofFloat()的内部实现其实就是将传进来的参数封装成PropertyValuesHolder实例来保存动画状态。所以可见PropertyValuesHolder是多么有用了吧。

在PropertyValuesHolder这个类里面,同样也有ofInt(),ofFloat(),ofKeyframe()等方法来实例化,举个例子:

PropertyValuesHolder

代码也非常简单:

PropertyValuesHolder rotationHolder = PropertyValuesHolder.ofFloat("Rotation", 90, -90, 45, -45, 60, -60);
PropertyValuesHolder colorHolder = PropertyValuesHolder.ofInt("BackgroundColor", 0xff55aa11, 0xff115633, 0xff123344, 0xffaabbcc);
PropertyValuesHolder scaleXHolder = PropertyValuesHolder.ofFloat("ScaleX", 1f, 1.1f, 1.2f, 1.5f, 1.8f, 1.5f, 1.2f, 1.1f, 1);
PropertyValuesHolder scaleYHolder = PropertyValuesHolder.ofFloat("ScaleY", 1f, 1.1f, 1.2f, 1.5f, 1.8f, 1.5f, 1.2f, 1.1f, 1);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(mBinding.image, rotationHolder, colorHolder, scaleXHolder, scaleYHolder);
animator.setDuration(3500);
animator.setInterpolator(new AccelerateInterpolator());
animator.start();

通过代码我们可以知道,通过ofPropertyValuesHolder来实例化,其实就是将不同动画效果分配到一个个PropertyValuesHolder中去,然后把多个不同的PropertyValuesHolder对象在初始化时传入,最终实现多个效果并行播放。

2.3.4 其他实例化方法

在ObjectAnimator中提供了非常丰富的实例化方法,除了以上三个之外,在API LEVEL 21之后,Google推出了更多的实例化方法,例如:

  • ofMultiFloat()
  • ofMultiInt()
  • ofObject()
  • ofPropertyValuesHolder()

在上面三个(2.3.1-2.3.3)不足以解决我们需求的时候,我们可以到官方文档参考这三个比较新的实例化方法,它们也是为了简化操作而进行了更高度的封装,所以这也有助于帮助我们用更少的代码来实现动画。

总结

ObjectAnimator作为ValueAnimator的子类,在代码上对ValueAnimator进行了进一步的封装,使我们在日常使用中更加简单,但是正是因为封装,使得我们在一些特殊情况下使用ObjectAnimator使用上还是有一定的局限性,所以在大家掌握了ValueAnimator和ObjectAnimator的基本使用后,还需要自己通过写小Demo来加深和进阶使用,这样在我们用到时方能得心应手。


猜你喜欢

转载自blog.csdn.net/u014748504/article/details/79237529