自定义View(五)-动画- ObjectAnimator

介绍

ObjectAnimator是派生自ValueAnimator的。所以ValueAnimator中所能使用的方法,在ObjectAnimator中都可以正常使用。但是已经有个ValueAnimator为什么还要加入ObjectAnimator.因为ValueAnimator是通过调用监听,得到变化的值来做动画的。而ObjectAnimator可以针对某一个控件的某个属性直接做动画,而不需要设置监听,并让我自己手动修改控件的属性。相对来说使用更加简单方便。


使用

在介绍完之后我们来看下如何使用。如下:

ObjectAnimator animator = ObjectAnimator.ofFloat(mTextView, "alpha", 1, 0);
animator.setDuration(2000);
animator.start();

效果:

GIF1.gif

旋转(正数为顺时针,负数反之):

ObjectAnimator animator = ObjectAnimator.ofFloat(mTextView, "rotation", 0,180,0);
animator.setDuration(2000);
animator.start();

效果(顺时针旋转180再回到初始状态):

GIF2.gif

注意: rotation表示围绕Z轴旋转。旋转分别可以围绕X,Y,Z。其中Z表示垂直于屏幕的坐标轴。和高中学到的三维坐标系是一样的。

利用ObjectAnimator实现了透明度动画。我们来讲解下他的参数含义:

public static ObjectAnimator ofFloat(Object target, String propertyName, float... values)

<font color=#006400>Object target :</font> 用于指定这个动画要操作的是哪个控件 <font color=#006400>String propertyName :</font> 用于指定这个动画要操作这个控件的哪个属性 <font color=#006400>float... values :</font> 是可变长参数,这个就跟ValueAnimator中的可变长参数的意义一样了,就是指这个属性值是从哪变到哪。像我们上面的代码中指定的就是将textview的alpha属性从0变到1再变到0; 那么alpha这个属性是怎么得到的,又是从哪里来的呢?其实,ObjectAnimator做动画,是通过指定属性所对应的set方法来改变的。比如,我们上面指定的改变alpha的属性值,ObjectAnimator在做动画时就会到指定控件(TextView)中去找对应的setAlpha()方法来改变控件中对应的值。 那TextView中都有这些方法吗?有的,这些方法都是从View中继承过来的,在View中有关动画,总共有下面几组set方法:

//1、透明度:alpha  
public void setAlpha(float alpha)  
  
//2、旋转度数:rotation、rotationX、rotationY  
public void setRotation(float rotation)  
public void setRotationX(float rotationX)  
public void setRotationY(float rotationY)  
  
//3、平移:translationX、translationY  
public void setTranslationX(float translationX)   
public void setTranslationY(float translationY)  
  
//缩放:scaleX、scaleY  
public void setScaleX(float scaleX)  
public void setScaleY(float scaleY) 

那么确实是这样吗?想深入了解这就需要了解动画的工作原理了。这里我就不过多讲解了原理了(在我看了源码之后发现属性动画的原理好像用处不大,重要的getter,setter方法原理)。单独看下为什么我们设置"alpha"就会调用setAlpha方法。首先点击ObjectAnimator.ofFloat()进去: ObjectAnimator#setFloatValues():

·
    @Override
    public void setFloatValues(float... values) {
        if (mValues == null || mValues.length == 0) {
            // No values yet - this animator is being constructed piecemeal. Init the values with
            // whatever the current propertyName is
            if (mProperty != null) {
                setValues(PropertyValuesHolder.ofFloat(mProperty, values));
            } else {
                setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
            }
        } else {
            super.setFloatValues(values);
        }
    }

发现他是调用了PropertyValuesHolder.ofFloat(),那么说明动画是的属性值传入到了PropertyValuesHolder中。PropertyValuesHolder这个类的意义就是,它其中保存了动画过程中所需要操作的属性和对应的值。我们通过ofFloat(Object target, String propertyName, float… values)构造的动画,ofFloat()的内部实现其实就是将传进来的参数封装成PropertyValuesHolder实例来保存动画状态。OK!那么我们开看下它的setupValue():

·
    /**
     * Utility function to set the value stored in a particular Keyframe. The value used is
     * whatever the value is for the property name specified in the keyframe on the target object.
     *
     * @param target The target object from which the current value should be extracted.
     * @param kf The keyframe which holds the property name and value.
     */
    private void setupValue(Object target, Keyframe kf) {
        if (mProperty != null) {
            Object value = convertBack(mProperty.get(target));
            kf.setValue(value);
        } else {
            try {
                if (mGetter == null) {
                    Class targetClass = target.getClass();
                    setupGetter(targetClass);
                    if (mGetter == null) {
                        // Already logged the error - just return to avoid NPE
                        return;
                    }
                }
                Object value = convertBack(mGetter.invoke(target));
                kf.setValue(value);
            } catch (InvocationTargetException e) {
                Log.e("PropertyValuesHolder", e.toString());
            } catch (IllegalAccessException e) {
                Log.e("PropertyValuesHolder", e.toString());
            }
        }
    }

在初始化的时候,回调用get方法回去初始值,其中get方法是通过反射得到的。当下一帧到来的时候:PropertyValuesHolder中的setAnimatedValue()方法会将新的属性值设置给对象。调用其set方法。:

·
 void setAnimatedValue(Object target) {
        if (mProperty != null) {
            mProperty.set(target, getAnimatedValue());
        }
        if (mSetter != null) {
            try {
                mTmpValueArray[0] = getAnimatedValue();
                mSetter.invoke(target, mTmpValueArray);
            } catch (InvocationTargetException e) {
                Log.e("PropertyValuesHolder", e.toString());
            } catch (IllegalAccessException e) {
                Log.e("PropertyValuesHolder", e.toString());
            }
        }
    }

可以看到其中set方法也是通过反射得到的。 其他属性效果大家可以自行测试下。不过在逐个测试这些函数的使用方法前,我们先做一个总结: <font color=#DC143C size=3>1、要使用ObjectAnimator来构造对画,要操作的控件中,必须存在对应的属性的set方法 2、setter 方法的命名必须以骆驼拼写法命名,即set后每个单词首字母大写,其余字母小写,即类似于setPropertyName所对应的属性为propertyName</font> 这个是后面我们自定义属性的时候会用到。在这里我们只要知道在传入属性值的时候不是随便传入的,比如:setAlpha(float alpha) 在传入是只能传入"alpha"或"Alpha"其他其他格式是没有效果的,因为反射区找回找不到setAlpha方法。也就是说属性参数必须与setXXXX后方法名一致。那么其他属性参数可以自己测试下。


ObjectAnimator动画流程

我们先来看张图:

image

                      (注: 此图来源: 自定义控件三部曲) 根据上面我们分析ObjectAnimator动画来看这个图也是移交容易理解的。与ValueAnimator不同的是最后一步,在ValueAnimator中,我们要通过添加监听器来监听当前数字值。而在ObjectAnimator中,则是先根据属性值拼装成对应的set函数的名字,比如<font color=#006400>这里的scaleY的拼装方法就是将属性的第一个字母强制大写后,与set拼接,所以就是setScaleY</font> 。然后通过反射找到对应控件的setScaleY(float scaleY)函数,将当前数字值做为setScaleY(float scale)的参数将其传入。 这些都清楚以后如果我们要自定义ObjectAnimator属性就要注意以下几个点:

  • <font color=#A52A2A > 函数命名规则:</font> <font color=#006400 >   在命名时set之后的名字就是属性的名字。同时属性名字第一个字母不区分大小写,后面的名字必须与属性的名字相同。</font>
  • <font color=#A52A2A > 参数类型确定:</font> <font color=#006400 >   在知道参数类型后我们才能确定我们是调用ofFloat还是ofInt,这个参数是根据setXXX方法中的参数类型决定的。例如:setAlpha(float alpha)其中参数类型是float所以上面我们使用的是ofFloat,当然ObjectAnimator继承ValueAnimatior也有.ofObject()支持任意类型,不过和前面我们讲到的一样这里我们就要自定义自己的估值器了。</font>
  • <font color=#A52A2A > 动画的初始值:</font><font color=#006400 >   上面我们提到了在动画初始化的时候会调用getter方法获取变化的初始值。如果不设置的话会警告但是程序不会崩溃,因为会调用数值类型的初始值,比如Int的默认值是0.这个后面再具体讲解</font> 这些都是在自定义ObjectAnimator属性需要注意的几个点。

总结:<font color=#DC143C size=3>属性动画要求动画作用的对象提供属性的get和set方法,属性动画根据外界传递的该属性的初始值和最终值,以动画的效果多次去调用set方法,每次传递的set方法的值都不一样。随着时间的推移所传递的值越来越接近最终值。所以让动画生效要同时满足以下条件(缺一不可): </font>

  1. <font color=#006400 >要做动画的对象(如上面的TextView)必须提供set方法,如果对象没有传递初始值(如:只传递一个值)还需要提供get方法,以为系统要取变化属性的初始值</font>
  2. <font color=#006400 >要做动画的对象(如上面的TextView),要改变的属性变化必须能通过某种方法反应出来,比如UI改变之类的。</font>

下面我们就开试着自定义自己的ObjectAnimator属性。


自定义ObjectAnimator属性

自定义ObjectAnimator属性,说白了就是在set方法中实现我们想要变换的属性。具体如何变幻都是在setter函数中实现的。我们先来个简单的动画,之前我们用ValueAnimator实现控件移动,如下:

·
                valueAnimator = ValueAnimator.ofInt(0, 400);
                valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        int curValue = (int) animation.getAnimatedValue();
                        mTextView.layout(mTextView.getLeft(), curValue, mTextView.getRight(), curValue + mTextView.getHeight());
                    }
                });
                valueAnimator.setDuration(2000);
                valueAnimator.start();

效果如下:

GIF3.gif

如果现在我们使用自定义ObjectAnimator该如何实现呢?代码如下:

·
                ObjectAnimator animator = ObjectAnimator.ofInt(new TextWidth(mTextView), "height", 400);
                animator.setDuration(2000);
                animator.start();
                
private static class TextWidth {
        View target;

        public TextWidth(View target) {
            this.target = target;
        }

        public void setHeight(int widthY) {
            target.layout(target.getLeft(), widthY, target.getRight(), widthY + target.getHeight());
        }
    }

效果:

GIF5.gif

发现效果完全一样。 但是我们之前讲过,在初始化的时候没有设置初始值将会调用get方法,首先我们设置时只传入了400,并没有传入0,但是从效果上来看和ValueAnimator是一样的,这是为什么呢?因为我们之前说了,如果没有设置初始值将会调用get方法获取初始值。如果没有get方法的话将会根据传入的参数的默认值作为初始值。Int的默认值是0,所以效果和上面(0,400)是一样的。索然程序没有崩溃但是提出了警告:

W/PropertyValuesHolder: Method getHeight() with type null not found on target class class com.example.ggxiaozhi.customview2.ValueAnimatorActivity$TextWidth

那我们改一下:

private static class TextWidth {
        View target;

        public TextWidth(View target) {
            this.target = target;
        }

        public int getHeight() {
            return 100;
        }

        public void setHeight(int widthY) {
            target.layout(target.getLeft(), widthY, target.getRight(), widthY + target.getHeight());
        }
    }

加上get方法,并且返回的初始值是100.效果:

GIF6.gif

可以看到点击后先从原来的位置调到100之后做的动画。并且没有警告提示也没有了。看来确实是上面图中一样会不断调用set方法来做动画。大家也可以试试小球变化动画,效果如下:

GIF7.gif

这个效果是半径从50到200.大家可以自己试一试。


其他方法

由于ObjectAnimator继承ValueAnimator所以它的方法都适合ObjectAnimator。这里需要说的有两个方法:

  • 改变颜色

我们在ValueAnimator中改变颜色,那么ObjectAnimator支不支持呢?答案是一定的。因为在View中有这个方法:

public void setBackgroundColor(int color);  

所以所有继承自View的都可以使用:

ObjectAnimator animator = ObjectAnimator.ofInt(tv, "BackgroundColor", 0xffff00ff, 0xffffff00, 0xffff00ff);  
animator.setDuration(8000);  
animator.setEvaluator(new ArgbEvaluator());  
animator.start(); 

这里大家不要忘记了,要使用颜色估值器。反之动画效果会错乱。

  • 改变宽度

在Android开发艺术这本中讲解改变宽度的方法需要自己重写。但是在实践之后发现不要重写也可以实现。并且书中的例子效果和讲解的不符,所以在这里记录下,方法如下:

  1. 使用View自带的setWidth:
`
                ObjectAnimator animator = ObjectAnimator.ofInt(mTextView, "width", 100);
                animator.setDuration(2000);
                animator.start();
  1. 自定义改变宽度:
  private static class TextWidth {
        View target;

        public TextWidth(View target) {
            this.target = target;
        }

        public int getHeight() {
            return target.getWidth();
        }

        public void setHeight(int widthY) {
            target.getLayoutParams().width = widthY;
            target.requestLayout();
        }
    }

效果:

GIF7.gif

两种效果图是一样的。这里不光是记录,也想体现属性动画的优势,使用更加自由灵活。可以对任意属性做动画。如果用View动画是做不出来的。(如果用scaleX是方法,和增加宽度是不同的概念。大家可以测试下效果)


PropertyValuesHolder与Keyframe

ObjectAnimator除了0fInt(),ofFloat(),ofObject(),还有一个ofPropertyValuesHolder。如下:

/** 
 * valueAnimator的 
 */  
public static ValueAnimator ofPropertyValuesHolder(PropertyValuesHolder... values)   
/** 
 * ObjectAnimator的 
 */  
public static ObjectAnimator ofPropertyValuesHolder(Object target,PropertyValuesHolder... values) 

对于PropertyValuesHolder上面我们提到过了它保存了动画过程中所需要操作的属性和对应的值,后期的各种操作也是以PropertyValuesHolder为主的。这里重点看ObjectAnimator中的ofPropertyValuesHolder()因为他用到比较多。

  • <font color=#006400 >Object target:</font> 指需要执行动画的控件
  • <font color=#006400 >PropertyValuesHolder...values:</font> 是一个可变长参数,可以传进去多个PropertyValuesHolder实例,由于每个PropertyValuesHolder实例都会针对一个属性做动画,所以如果传进去多个PropertyValuesHolder实例,将会对控件的多个属性同时做动画操作。

它的使用和ObjectAnimator的0fInt(),ofFloat(),ofObject()基本上一样,只是我们需要先创建PropertyValuesHolder,如下:

PropertyValuesHolder rotationHolder = PropertyValuesHolder.ofFloat("Rotation", 60f, -60f, 40f, -40f, -20f, 20f, 10f, -10f, 0f);
PropertyValuesHolder colorHolder = PropertyValuesHolder.ofInt("BackgroundColor", 0xffffffff, 0xffff00ff, 0xffffff00);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(mTextView, rotationHolder, colorHolder);
animator.setDuration(3000);
animator.setInterpolator(new AccelerateInterpolator());
animator.start();

效果: 它的特点是可以在一个控件上实现多个属性同时做动画。PropertyValuesHolder除了.ofFloat()与.ofInt(),还有两个创建对象的方法:

public static PropertyValuesHolder ofObject(String propertyName, TypeEvaluator evaluator,Object... values)  

public static PropertyValuesHolder ofKeyframe(String propertyName, Keyframe... values)  
  • <font color=#A52A2A size=3> PropertyValuesHolder.ofObject()</font> 参数含义:
propertyName:ObjectAnimator动画操作的属性名;
evaluator:Evaluator实例,Evaluator是将当前动画进度计算出当前值的类,可以使用系统自带的IntEvaluator、FloatEvaluator也可以自定义  
values:可变长参数,表示操作动画属性的值 

这个和之前ValueAniator.ofObject()是一样的。使用如下:

`
                PropertyValuesHolder propertyValuesHolde = PropertyValuesHolder.ofObject("CharText", new MyEvaluator(), 'A', 'Z');
                ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(mTextView, propertyValuesHolde);
                animator.setDuration(3000);
                animator.setInterpolator(new AccelerateInterpolator());
                animator.start();
                
                
public class MyTextView extends android.support.v7.widget.AppCompatTextView {
    public MyTextView(Context context) {
        super(context);
    }

    public MyTextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void setCharText(Character character) {
        setText(String.valueOf(character));
    }

}

从CharEvaluator中产出的动画中间值类型为Character类型。TextView中虽然有setText(CharSequence text) 函数,但这个函数的参数类型是CharSequence,而不是Character类型。所以我们要自定义一个类派生自TextView来改变TextView的字符这里正是用到set反射拿到setter方法。实现动画的改变。由于我们提供了初始值‘A’,所以可以不用提供get方法。效果如下:

  • <font color=#A52A2A size=3> PropertyValuesHolder.ofKeyframe</font>
public static PropertyValuesHolder ofKeyframe(String propertyName, Keyframe... values)  

参数含义:

propertyName:动画所要操作的属性名
values:Keyframe的列表,PropertyValuesHolder会根据每个Keyframe的设定,定时将指定的值输出给动画。

在使用.ofKeyframe方法之前我们首先要清楚什么是Keyframe。 <font color=#008000 >KeyFrame直译过来就是关键帧。这个关键帧是表示的是某个物体在哪个时间点应该在哪个位置上。 一个关键帧必须包含两个原素,第一时间点,第二位置。 </font>

public static Keyframe ofFloat(float fraction, float value)  
fraction:表示当前的显示进度,即从加速器中getInterpolation()函数的返回值;
value:表示当前应该在的位置 

使用:

Keyframe frame0 = Keyframe.ofFloat(0f, 0);
Keyframe frame1 = Keyframe.ofFloat(0.1f, -20f);
Keyframe frame2 = Keyframe.ofFloat(1, 0);
PropertyValuesHolder frameHolder = PropertyValuesHolder.ofKeyframe("rotation",frame0,frame1,frame2);
Animator animator = ObjectAnimator.ofPropertyValuesHolder(mTextView,frameHolder);
animator.setDuration(1000);
animator.start();

首先我们要明白fraction的范围是0-1,0表示动画没开始,1表示动画结束,这段代码的含义是,在初始状态(0f)时旋转角度为0,在动画时间运行到0.1(1/10)时旋转-20度,然后再回到初始状态。 效果:

GIF9.gif

其中我们把时间区间划分的越细,在每个划分的时间上做不同的动画,那么动画的效果就越复杂。

<font color=#008000 >(1) 常用函数</font>

/** 
 * 设置fraction参数,即Keyframe所对应的进度 
 */  
public void setFraction(float fraction)   
/** 
 * 设置当前Keyframe所对应的值 
 */  
public void setValue(Object value)  
/** 
 * 设置Keyframe动作期间所对应的插值器 
 */  
public void setInterpolator(TimeInterpolator interpolator)  

<font color=#008000 >(2) 插值器</font>

Keyframe frame0 = Keyframe.ofFloat(0f, 0);  
Keyframe frame1 = Keyframe.ofFloat(0.1f, -20f);  
frame1.setInterpolator(new BounceInterpolator());  
Keyframe frame2 = Keyframe.ofFloat(1f, 20f);  
frame2.setInterpolator(new LinearInterpolator());  

在上面的代码中,我们给frame1设置了插值器BounceInterpolator(回弹插值器),那么在frame0到frame1的中间值计算过程中,就是用的就是回弹插值器; 同样,我们给frame2设置了线性插值器(LinearInterpolator),所以在frame1到frame2的中间值计算过程中,使用的就是线性插值器 很显然,给Keyframe.ofFloat(0f, 0)设置插值器是无效的,因为它是第一帧。

结论: <font color=#006400 >

  1. 在给关键帧设置插值器时,第一帧无效果。
  2. 给当前帧设置插值器。显示的效果会在上一帧运动到当前帧时显示。  </font>

<font color=#008000 >(3) Keyframe.ofObject()</font>

Keyframe frame0 = Keyframe.ofObject(0f, new Character('A'));  
Keyframe frame1 = Keyframe.ofObject(0.1f, new Character('L'));  
Keyframe frame2 = Keyframe.ofObject(1,new Character('Z'));  
  
PropertyValuesHolder frameHolder = PropertyValuesHolder.ofKeyframe("CharText",frame0,frame1,frame2);  
frameHolder.setEvaluator(new CharEvaluator());  
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(mTextView,frameHolder);  
animator.setDuration(3000);  
animator.start();  

ofInt(),ofFloat()比较简单,相对ofObject,在使用时是一样的,不过我们要记住,在使用ofObject()是都要自定义Evaluator估值器。否则系统是无法根据当前返回进度计算具体的数值的。 <font color=#A52A2A >(4) 使用关键帧注意的点:</font>

<font color=#006400 >

  1. 如果去掉第0帧,将以第一个关键帧为起始位置
  2. 如果去掉结束帧,将以最后一个关键帧为结束位置
  3. 使用Keyframe来构建动画,至少要有两个或两个以上帧</font>
  • <font color=#A52A2A size=3> PropertyValuesHolder其他函数:</font>
/** 
 * 设置动画的Evaluator 
 */  
public void setEvaluator(TypeEvaluator evaluator)  
/** 
 * 用于设置ofFloat所对应的动画值列表 
 */  
public void setFloatValues(float... values)  
/** 
 * 用于设置ofInt所对应的动画值列表 
 */  
public void setIntValues(int... values)  
/** 
 * 用于设置ofKeyframe所对应的动画值列表 
 */  
public void setKeyframes(Keyframe... values)  
/** 
 * 用于设置ofObject所对应的动画值列表 
 */  
public void setObjectValues(Object... values)  
/** 
 * 设置动画属性名 
 */  
public void setPropertyName(String propertyName) 

注意: 如果是利用PropertyValuesHolder.ofObject()来创建动画实例的话,我们是一定要显示调用 PropertyValuesHolder.setEvaluator()来设置Evaluator的。


本文总结的有点多。下篇来看下组合动画与XML设置属性动画。

感谢

站在巨人的肩膀上可以让我们看的更远。 Android自定义控件三部曲文章

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏程序你好

CSharp代码示例每日一讲: 在GDI+中使用填充Fill方法

752
来自专栏游戏杂谈

as3绘制抛物线

一般做页游的过程中,特效的释放可能是不是固定位置的播放,是需要进行“运动的”(其实就是移动特效这个影响剪辑)。举个例子:步兵射箭,不确定箭发射的方向,事先也不...

1052
来自专栏GIS讲堂

Arcgis for JS之Cluster聚类分析的实现

在做项目的时候,碰见了这样一个问题:给地图上标注点对象,数据是从数据库来的,包含XY坐标信息的,通过graphic和graphiclayer 的方式添加到地图上...

1783
来自专栏deepcc

emmet(Zen coding)前端写代码神器

3807
来自专栏菩提树下的杨过

Silverlight:Mouse Avoiding 躲避鼠标效果

昨晚在一国外博客上(从域名后缀pl上猜想应该是波兰)看到这种效果(Mouse Avoid 躲避鼠标),是基于Flash/AS3开发的,这个示例把弹性运动,摩擦力...

2127
来自专栏章鱼的慢慢技术路

用Python中的tkinter模块作图(续)

2797
来自专栏琯琯博客

JavaScript 103 条技能

1、原生JavaScript实现字符串长度截取 function cutstr(str, len) { var temp; var ic...

2826
来自专栏听雨堂

mapx实现热点效果

        当鼠标移动到图元上方时,标注改变样式(变色,加下划线等),移开后还原。通过vb+mapx基本实现这个效果,但由于mapx在label进行变化时的...

1887
来自专栏曾大稳的博客

OpenGLES通过SurfaceTexture预览摄像头画面

比如美颜相机那些,处理摄像头数据展示出来,为了提高预览的效率,所以这里使用了VBO和FBO,如果不知道这个,请看上面的文章。

4562
来自专栏章鱼的慢慢技术路

Go指南练习_切片

1563

扫码关注云+社区

领取腾讯云代金券