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
先上一个例子,实现图片的渐变过程:
我们都知道,在使用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<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达到终点,先上效果图让大家比较直观了解:
所以我们可以很清楚的列出关系式:
那么在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<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就是在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的详解。
Android基础夯实--重温动画(四)之属性动画 ValueAnimator详解的更多相关文章
- Android基础夯实--重温动画(五)之属性动画 ObjectAnimator详解
只有一种真正的英雄主义 一.摘要 ObjectAnimator是ValueAnimator的子类,它和ValueAnimator一样,同样具有计算属性值的功能,但对比ValueAnimator,它会更 ...
- Android基础夯实--重温动画(二)之Frame Animation
心灵鸡汤:天下事有难易乎,为之,则难者亦易矣:不为,则易者亦难矣. 摘要 当你已经掌握了Tween Animation之后,再来看Frame Animation,你就会顿悟,喔,原来Frame Ani ...
- Android基础夯实--重温动画(三)之初识Property Animation
每个人都有一定的理想,这种理想决定着他的努力和判断的方向.就在这个意义上,我从来不把安逸和快乐看作生活目的的本身--这种伦理基础,我叫它猪栏的理想.--爱因斯坦 一.摘要 Property Anima ...
- Android基础夯实--重温动画(一)之Tween Animation
心灵鸡汤:真正成功的人生,不在于成就的大小,而在于你是否努力地去实现自我,喊出自己的声音,走出属于自己的道路. 摘要 不积跬步,无以至千里:不积小流,无以成江海.学习任何东西我们都离不开扎实的基础知识 ...
- 第三部分:Android 应用程序接口指南---第四节:动画和图形---第一章 属性动画及动画与图形概述
第1章 属性动画及动画与图形概述 Android提供了一系列强大的API来把动画加到UI元素中,以及绘制自定义的2D和3D图像中去.下面的几节将综述这些可用的API以及系统的功能,同时帮你做出最优的选 ...
- Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)
View 的绘制系列文章: Android View 的绘制流程之 Measure 过程详解 (一) Android View 绘制流程之 DecorView 与 ViewRootImpl 在上一篇 ...
- moviepy音视频剪辑:视频剪辑基类VideoClip的属性及方法详解
☞ ░ 前往老猿Python博文目录 ░ 一.概述 在<moviepy音视频剪辑:moviepy中的剪辑基类Clip详解>和<moviepy音视频剪辑:moviepy中的剪辑基类Cl ...
- VB6.0中WinSock控件属性和方法详解
原文链接:http://liweibird.blog.51cto.com/631764/653134 WinSock控件能够通过UDP协议(用户数据报协议)或TCP协议(数据传输协议)连接到远程的机器 ...
- “全栈2019”Java第九十四章:局部内部类详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
随机推荐
- HDU 3469 Catching the Thief (博弈 + DP递推)
Catching the Thief Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Other ...
- malloc内存分配
网上总结到的信息: (1) 静态分派:是在栈上分配,是由用户自己申请,是由操作系统自己释放的 动态分配:是由编译器分配,操作系统没有提供这样的机制,所以自己申请,必须自己删除! (2)你也要明确.栈的 ...
- HashMap与HashTable的区别?
HashMap和Hashtable的比较是Java面试中的常见问题,用来考验程序员是否能够正确使用集合类以及是否可以随机应变使用多种思路解决问题.HashMap的工作原理.ArrayList与Vect ...
- command 'gcc' failed with exit status 1
https://stackoverflow.com/questions/11094718/error-command-gcc-failed-with-exit-status-1-while-insta ...
- stl之vector的应用
这里主要是对vector容器的一些常见应用的总结.至于vector的构造函数及初始化能够參考http://blog.csdn.net/lsh_2013/article/details/21191289 ...
- (续)linux SD卡初始化---mmc_sd_init_card函数
mmc_sd_init_card剩下的关于UHS-I的分支结构. uhs-I的初始化流程图如图: 红线标出的部分是已经做了的事,与上一篇那个流程图是一致的,之后就是if分支中做的事. if分支中的函数 ...
- HDU - 2586 How far away ?(离线Tarjan算法)
1.给定一棵树,每条边都有一定的权值,q次询问,每次询问某两点间的距离. 2.这样就可以用LCA来解,首先找到u, v 两点的lca,然后计算一下距离值就可以了. 这里的计算方法是,记下根结点到任意一 ...
- poj中的一些线段树
poj2828 链接:http://poj.org/problem?id=2828 题解: 初始状态 首先是插入3 69 1,4结点有4个位置, 1,2结点有2个位置,小于3,因此放到1,4结点右孩子 ...
- pymemcache get start
Getting started! A comprehensive, fast, pure-Python memcached client library. Basic Usage from pymem ...
- [转]python的startswith()方法
描述 Python startswith() 方法用于检查字符串是否是以指定子字符串开头,如果是则返回 True,否则返回 False.如果参数 beg 和 end 指定值,则在指定范围内检查. 语法 ...