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

宝剑锋从磨砺出,梅花香自苦寒来;千淘万漉虽辛苦,吹尽狂沙始到金; 长风破浪会有时,直挂云帆济沧海

一、摘要

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

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

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

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

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

Android基础夯实--重温动画(三)之初识Property Animation

二、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的详解。

posted @ 2018-01-05 15:09  RyaneLee  阅读(6232)  评论(0编辑  收藏  举报