[动画]属性动画ObjectAnimator及ValueAnimator运用分析

前言: 本打算写一篇属性动画源码分析的文章,但担心纯粹的源码分析,很难得到大部人的认可,毕竟如果我写这样一篇文章,读者如果不是对着源码边读边看我的博客,很难理解我写的究竟是什么。还有原因则是因为:属性动画的源码,到fraction值变化和属性的变化间关系处,比较繁琐,因此最后写成了一篇“ 科普性 ”的文章。 一、先上一张图 (现在看不懂没关系,主要是把关系先摆在这)
[动画]属性动画ObjectAnimator及ValueAnimator运用分析
文章图片


二、再上一个动画GIF,看看ObjectAnimator属性动画的一些效果(从左到右依次:透明度、旋转、平移、拉升) [动画]属性动画ObjectAnimator及ValueAnimator运用分析
文章图片
[动画]属性动画ObjectAnimator及ValueAnimator运用分析
文章图片
[动画]属性动画ObjectAnimator及ValueAnimator运用分析
文章图片
[动画]属性动画ObjectAnimator及ValueAnimator运用分析
文章图片


看到这,会发现其实和补间动画没什么区别,补间动画也能实现这些功能。对补间动画不了解的朋友,可以参考我前一篇文章:补间动画
如果只是要达到上述四种简单效果,用补间动画完全足够了。代码比较简单,依次从左至右的顺序上代码:
1)透明度

//透明度 public void animTest02() { ObjectAnimator oAnim = ObjectAnimator.ofFloat(myview, "alpha", 1f, 0f, 1f); oAnim.setDuration(5000); oAnim.start(); }

2)旋转
//旋转 public void animTest03() { ObjectAnimator oAnim = ObjectAnimator.ofFloat(myview, "rotation", 0f, 360f); oAnim.setDuration(5000); oAnim.start(); }

3)平移

//平移 public void animTest04() { ObjectAnimator oAnim = ObjectAnimator.ofFloat(myview, "translationX", 0f, -500f, 0f); oAnim.setDuration(5000); oAnim.start(); }

4)拉升
//拉升 public void animTest05() { ObjectAnimator animator = ObjectAnimator.ofFloat(myview, "scaleY", 1f, 3f, 1f); animator.setDuration(5000); animator.start(); }

统一对上述四种动画做个解释,
例如:

ObjectAnimator animator = ObjectAnimator.ofFloat(myview, "scaleY", 1f, 3f, 1f);


利用AS穿透,看源码注释:(此处不涉及深刻探究,仅提供一种读不理解的代码参数的最佳解决方案)

/** * Constructs and returns an ObjectAnimator that animates between float values. A single * value implies that that value is the one being animated to. Two values imply starting * and ending values. More than two values imply a starting value, values to animate through * along the way, and an ending value (these values will be distributed evenly across * the duration of the animation). * * @param target The object whose property is to be animated. This object should * have a public method on it called setName(), where name is * the value of the propertyName parameter. * @param propertyName The name of the property being animated. * @param values A set of values that the animation will animate between over time. * @return An ObjectAnimator object that is set up to animate between the given values. */ public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) { ObjectAnimator anim = new ObjectAnimator(target, propertyName); anim.setFloatValues(values); return anim; }


翻译如下:
第一个参数myview:是动画操作的目标,而且目标必须有一个对外的公共方法:setXXX方法。(此处XXX就是下面的属性动画名)
第二个参数:属性动画名。
第三个参数:是一个动画变化的值的集合。比如当前1f, 3f, 1f(值按照当前的值转换成百分比计算,例如1f就是100%,3f就是300%)
举个例子说明第一条中的:必须有一个对外的公共方法:setXXX方法 比如我要对myview(动画操作目标)进行如下操作:

public void animTest06() { ObjectAnimator animator = ObjectAnimator.ofFloat(myview, "color", 1f, 3f, 1f); animator.setDuration(5000); animator.start(); }

目的:本意是,想改变颜色。
效果:点击启动该“动画”的时候,动画不会有任何效果,且logcat会提示这么一条信息:(意思是我的自定义view类的对象myview,当中并没有找到setColor方法)。
[动画]属性动画ObjectAnimator及ValueAnimator运用分析
文章图片


反过来看看之前我们设置的四个属性:alpha,rotation,translationX,scaleY。
按照推理去看看我们的自定义view的父类view是否包含了这些个setXXX方法。
做个总结: 操作属性动画,其实就是操作目标target的set+属性名的值的改变。
为了证明确实在target里面有这四个属性:alpha,rotation,translationX,scaleY。
参见view的源码:(其他几个就不再截图了,都有对应的setXXX方法)

[动画]属性动画ObjectAnimator及ValueAnimator运用分析
文章图片




三、ObjectAnimator属性动画的运用: 开始的时候说过,也就是说如果仅仅对上述四个属性(alpha,rotation,translation,scale)改变,那么百分之95的情况,用补间动画就够了,google为何还要费劲心思写出属性动画来?
主要原因就是不够用。比如上一个例子中说的要对颜色进行动画操作。如果用补间动画就没办法处理了。
解决方案:
之前其实已经说过一个引子。图片如下:
[动画]属性动画ObjectAnimator及ValueAnimator运用分析
文章图片


既然说我们没有setColor方法,是不是只需要在自定义view里面加上setColor方法就能对颜色操作了?
答案:(见文章最末尾注释一)翻译源码2:最佳的属性动画的调用setter方法,用float或者int类型,为动画属性名,写一个返回值为空的setter方法,将导致代码采取这些约束的情况下的优化路径。其他属性类型和返回类型也将工作,但在处理请求由于正常的反射机制将有更多的开销。
还是原来的例子:
在其中加入属性:
private int color;

然后写对应的setter方法:(此处set方法的参数,即我们传递的改变的值,如1f,3f,1f,且必须对应,例如ObjectAnimator.ofFloat对应float f,ObjectAnimator.ofInt对应int型
public void setColor(float f) { this.setBackgroundColor(Color.RED); //此处一般应写为当前view因外界float f参数改变,而改变的状态,如长度,颜色,数值等等 }

按照此种方式设置后,发现点击后,设置的目标view变红。和我们预设的动画一样。(效果如下)
[动画]属性动画ObjectAnimator及ValueAnimator运用分析
文章图片



四、ValueAnimator简介 首先还是看图,之前我已经将ObjectAnimator讲完了,其实大部分功能已经可以实现了。现在讲讲父类ValueAnimator的使用。

[动画]属性动画ObjectAnimator及ValueAnimator运用分析
文章图片

先看看一段测试代码:( 测试代码一 )

ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); anim.setDuration(300); anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { final float f = (float) animation.getAnimatedValue(); Log.d("Tag", f + ""); } }); anim.start();

打印出的log如下图:
[动画]属性动画ObjectAnimator及ValueAnimator运用分析
文章图片

可能这个图并不直观,看变化速率。于是我自己手写了一个自定义view用来描点:
onDraw关键代码如下:(大概意思就是通过x轴间距不变(即x轴每次加上10dp),y轴位置乘以100取整描点(即y轴取上面logcat的数据*100取整数部分))

@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (mdata =https://www.it610.com/article/= null) { return; } //根据数据绘制ui for (int i = 0; i < mdata.size(); i++) { x = x + dip2px(context, 10); y = (int) (mdata.get(i) * 10000 / 100); canvas.drawCircle(x, dip2px(context, y), 5, p); } }

效果图如下:
[动画]属性动画ObjectAnimator及ValueAnimator运用分析
文章图片


变化率由此可见:先缓慢再加速再减速。
五、ValueAnimator运用 根据上图来看,如果只是数值改变,似乎并没什么用。谁也不会喜欢看到的动画只是一连串的数字在变。
但,我们可以猜想:
1)提出猜想:
属性动画,顾名思义就是根据动画的属性(属性动画名,也就是前面我一直说的)改变而达到展示动画的效果。
而属性如何改变,按照程序的思想,肯定是因为数值的改变而设置不同值而达到改变属性值的方式。


2)ValueAnimator的运法:
下图标红其实就是用法总结,将ValueAnimator对象的两个方法成对使用即可。
[动画]属性动画ObjectAnimator及ValueAnimator运用分析
文章图片


假如需要实现下图的效果:
[动画]属性动画ObjectAnimator及ValueAnimator运用分析
文章图片


代码如下:

ValueAnimator valueAnimator = new ValueAnimator(); //实例化 valueAnimator.setDuration(5000); //设置动画时长 valueAnimator.setTarget(id_ball); //设置操作动画的目标 valueAnimator.setObjectValues(new Point(0,0)); //设置动画的初始化值,不设置会崩溃。对当前实例其实设置多少,并没太大区别 valueAnimator.setEvaluator(new TypeEvaluator() { @Override public Point evaluate(float fraction, Object startValue, Object endValue) { Log.d("fraction",fraction+""); //打印出fraction变化 Point point = new Point(); point.setX(200*fraction*3); point.setY(0.5f * 200 * (fraction * 3) * (fraction * 3)); return point; //每次fraction取得一个新的值,会获取一个新的point对象返回 } }); valueAnimator.start(); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { Point point = (Point) animation.getAnimatedValue(); //取得上面TypeEvaluator返回的对象 id_ball.setX(point.getX()); //重设位置 id_ball.setY(point.getY()); //重设位置 } });

注释写的很清楚:
总结一下:
1)设置动画的目标,在addUpdateListener中将对这个目标进行多次(fraction次数)刷新属性(属性例如,位置,颜色等等)
2)设置动画的初始化值。不设置会崩溃,动画开始的时候,会报空指针异常。
3)setEvaluator返回的对象——》addUpdateListener实用的对象 对应
4)fraction也满足上面的折线图的变化,其实涉及插值器。(由于本例5000ms,所以描点会比较密集)

fraction如下:(太多截图不全)
[动画]属性动画ObjectAnimator及ValueAnimator运用分析
文章图片

描点如下:(忽略宽度,请关注走势)
[动画]属性动画ObjectAnimator及ValueAnimator运用分析
文章图片







注释一: 源码1:
参考ObjectAnimator源码

public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) { ObjectAnimator anim = new ObjectAnimator(target, propertyName); anim.setFloatValues(values); return anim; }

其中第2行代码调用了构造方法实力化一个ObjectAnimator对象 :

private ObjectAnimator(Object target, String propertyName) { setTarget(target); setPropertyName(propertyName); }

里面嵌套两个方法:
setTarget(target);
setPropertyName(propertyName);

PS:一般遇到源码开分支这种情况我就开始画图了,毕竟几个圈下来源代码跟下来,估计都能忘记自己最开始是要做什么才看的源代码。
源码2:setTarget(target)源码:(翻译一下注释吧:设置动画的目标object对象的属性,如果动画已经开始,则结束)

/** * Sets the target object whose property will be animated by this animation. If the * animator has been started, it will be canceled. * * @param target The object being animated */ @Override public void setTarget(@Nullable Object target) { final Object oldTarget = getTarget(); if (oldTarget != target) { if (isStarted()) { cancel(); } mTarget = target == null ? null : new WeakReference(target); // New target should cause re-initialization prior to starting mInitialized = false; } }
其他情况都不看了,注释说得比较清楚了。从14行,15行看:

mTarget = target == null ? null : new WeakReference(target);
mInitialized = false;

做了两件事:
1)新建了一个弱引用对象target,给mTarget赋值了(弱引用回收机制这些java的知识,不打算讲,只需要知道实例化了一个对象即可)
2)使mInitialized = false。
仅仅只是赋值的两个操作。第一条分支结束。如下图:
[动画]属性动画ObjectAnimator及ValueAnimator运用分析
文章图片



源码3:setPropertyName(propertyName)(核心方法):
翻译1:设置动画的属性名。这个属性名用来倒出一个setter方法(例如setXXX,xxx是属性名,用来设置动画的变化值),例如,一个属性名foo,将调用一个在目标对象上的方法setFoo。如果valuefrom(也就是最开始我们说的1f,3f,1f这些float值)或valueto是null,则调用getter方法(例如getxxx,xxx是属性名)用来推导。
翻译2:最佳的属性动画的调用setter方法,用float或者int类型,为动画属性名,写一个返回值为空的setter方法,将导致代码采取这些约束的情况下的优化路径。其他属性类型和返回类型也将工作,但在处理请求由于正常的反射机制将有更多的开销。

/** * Sets the name of the property that will be animated. This name is used to derive * a setter function that will be called to set animated values. * For example, a property name of foo will result * in a call to the function setFoo() on the target object. If either * valueFrom or valueTo is null, then a getter function will * also be derived and called.(见翻译1) * * For best performance of the mechanism that calls the setter function determined by the * name of the property being animated, use float or int typed values, * and make the setter function for those properties have a void return value. This * will cause the code to take an optimized path for these constrained circumstances. Other * property types and return types will work, but will have more overhead in processing * the requests due to normal reflection mechanisms.
(见翻译2) * * Note that the setter function derived from this property name * must take the same parameter type as the * valueFrom and valueTo properties, otherwise the call to * the setter function will fail.
* * If this ObjectAnimator has been set up to animate several properties together, * using more than one PropertyValuesHolder objects, then setting the propertyName simply * sets the propertyName in the first of those PropertyValuesHolder objects.
* * @param propertyName The name of the property being animated. Should not be null. */ public void setPropertyName(@NonNull String propertyName) { // mValues could be null if this is being constructed piecemeal. Just record the // propertyName to be used later when setValues() is called if so. if (mValues != null) { PropertyValuesHolder valuesHolder = mValues[0]; String oldName = valuesHolder.getPropertyName(); valuesHolder.setPropertyName(propertyName); mValuesMap.remove(oldName); mValuesMap.put(propertyName, valuesHolder); } mPropertyName = propertyName; // New property/values/target should cause re-initialization prior to starting mInitialized = false; }



注释二:属性动画ValueAnimator源码图:( 纯手绘,仅供参考 )
[动画]属性动画ObjectAnimator及ValueAnimator运用分析
文章图片




























【[动画]属性动画ObjectAnimator及ValueAnimator运用分析】

    推荐阅读