宝剑锋从磨砺出,梅花香自苦寒来;千淘万漉虽辛苦,吹尽狂沙始到金; 长风破浪会有时,直挂云帆济沧海
欢迎大家想看更多关于Android基础夯实系列博文,请移步到我的博客:
Ryane's Blog
一、摘要
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
先上一个例子,实现图片的渐变过程:
我们都知道,在使用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方法被不断地执行,输出值不断由我们设置的初始值变化到我们设置的结束值,所以这个值的变化过程正是我们需要让控件变化的过程。
通过例子,我们可以大概总结使用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,我们可以轻松实现颜色渐变效果:
代码:
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,实现图片的放大和渐变过程,先看效果图:
首先我们看到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 {
// 属性动画封装了一个因子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达到终点,先上效果图让大家比较直观了解:
所以我们可以很清楚的列出关系式:
那么在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在属性动画中也是起着重要的一环。先看一张图:
我们可以看到,当Interpolator返回了当前进度滞后,Evaluator就会根据进度来计算出确定的值,以供监听器返回,所以我们就可以知道了,Evaluator其实就是一个根据我们需求而制作的一个计算器。
其实在上面的例子我已经简单地教大家自定义了一个Evaluator,在属性动画中,Android 也为我们提供了很多的Evaluator,例如IntEvaluator,FloatEvaluator等,我们可以先看一下IntEvaluator的底层实现:
public class IntEvaluator implements TypeEvaluator {
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就是在FloatEvalutor的基础之上加了200个像素,而我自定义的Evaluator也是修改了以下FloatEvaluator的代码:
class MyEvaluator implements TypeEvaluator {
@Override
public Float evaluate(float fraction, Float startValue, Float endValue) {
return startValue + fraction * (endValue - startValue) + 200;
}
}
这是FloatEvaluator的代码:
public class FloatEvaluator implements TypeEvaluator {
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的详解。