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

前言:

本打算写一篇属性动画源码分析的文章,但担心纯粹的源码分析,很难得到大部人的认可,毕竟如果我写这样一篇文章,读者如果不是对着源码边读边看我的博客,很难理解我写的究竟是什么。还有原因则是因为:属性动画的源码,到fraction值变化和属性的变化间关系处,比较繁琐,因此最后写成了一篇“ 科普性 ”的文章。

一、先上一张图

(现在看不懂没关系,主要是把关系先摆在这)

[动画]属性动画ObjectAnimator及ValueAnimator运用分析_第1张图片

二、再上一个动画GIF,看看ObjectAnimator属性动画的一些效果(从左到右依次:透明度、旋转、平移、拉升)

[动画]属性动画ObjectAnimator及ValueAnimator运用分析_第2张图片[动画]属性动画ObjectAnimator及ValueAnimator运用分析_第3张图片[动画]属性动画ObjectAnimator及ValueAnimator运用分析_第4张图片[动画]属性动画ObjectAnimator及ValueAnimator运用分析_第5张图片

看到这,会发现其实和补间动画没什么区别,补间动画也能实现这些功能。对补间动画不了解的朋友,可以参考我前一篇文章:补间动画

如果只是要达到上述四种简单效果,用补间动画完全足够了。代码比较简单,依次从左至右的顺序上代码:

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方法)。


反过来看看之前我们设置的四个属性:alpha,rotation,translationX,scaleY。

按照推理去看看我们的自定义view的父类view是否包含了这些个setXXX方法。

做个总结:

操作属性动画,其实就是操作目标target的set+属性名的值的改变。

为了证明确实在target里面有这四个属性:alpha,rotation,translationX,scaleY。

参见view的源码:(其他几个就不再截图了,都有对应的setXXX方法)

[动画]属性动画ObjectAnimator及ValueAnimator运用分析_第6张图片


三、ObjectAnimator属性动画的运用:

开始的时候说过,也就是说如果仅仅对上述四个属性(alpha,rotation,translation,scale)改变,那么百分之95的情况,用补间动画就够了,google为何还要费劲心思写出属性动画来?

主要原因就是不够用。比如上一个例子中说的要对颜色进行动画操作。如果用补间动画就没办法处理了。

解决方案:

之前其实已经说过一个引子。图片如下:


既然说我们没有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运用分析_第7张图片


四、ValueAnimator简介

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

[动画]属性动画ObjectAnimator及ValueAnimator运用分析_第8张图片
先看看一段测试代码:( 测试代码一 

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运用分析_第9张图片
可能这个图并不直观,看变化速率。于是我自己手写了一个自定义view用来描点:

onDraw关键代码如下:(大概意思就是通过x轴间距不变(即x轴每次加上10dp),y轴位置乘以100取整描点(即y轴取上面logcat的数据*100取整数部分))

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mdata == 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运用分析_第10张图片

变化率由此可见:先缓慢再加速再减速。

五、ValueAnimator运用

根据上图来看,如果只是数值改变,似乎并没什么用。谁也不会喜欢看到的动画只是一连串的数字在变。
但,我们可以猜想:
1)提出猜想:
属性动画,顾名思义就是根据动画的属性(属性动画名,也就是前面我一直说的)改变而达到展示动画的效果。

而属性如何改变,按照程序的思想,肯定是因为数值的改变而设置不同值而达到改变属性值的方式。


2)ValueAnimator的运法:

下图标红其实就是用法总结,将ValueAnimator对象的两个方法成对使用即可。

[动画]属性动画ObjectAnimator及ValueAnimator运用分析_第11张图片

假如需要实现下图的效果:

[动画]属性动画ObjectAnimator及ValueAnimator运用分析_第12张图片

代码如下:

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运用分析_第13张图片
描点如下:(忽略宽度,请关注走势)
[动画]属性动画ObjectAnimator及ValueAnimator运用分析_第14张图片



注释一:

源码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运用分析_第15张图片

源码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运用分析_第16张图片

















你可能感兴趣的:(android)