Android动画之Property Animation(属性动画)

介绍

在属性在没有出现之前,在使用ViewAnimation的时候,相信多知道它有以下这些缺点:

1.ViewAnimation系统受到限制,它仅将View对象的几个方面暴露给动画,只能够实现平移、缩放、旋转和透明度这四种动画操作,如果我们希望可以对View的背景色、或者是非View对象进行动态地改变,只能自己去实现了。
2.ViewAnimation还有一个缺点就是,就是它只修改View的绘制位置,而不是实际View的本身。例如,如果使用动画将按钮从左移动到屏幕右边,按钮正确绘制,但当你单击按钮时事件却不会触发,点击左边按键原来的位置,事件却触发了。因此,要处理这些问题,也要自己去实现逻辑。

因为以上的需求,在Android 3.0版本开始,系统给我们提供了一种全新的动画模式,属性动画(property animation)

使用属性动画系统,不但可以实现ViewAnimation一样的动画效果,而且也不会有以上缺点,即属性动画可以对任何对象(视图和非视图)的任何属性进行动画处理,并且对象本身实际上进行了修改。属性动画系统在执行动画的过程中也更为强大,在大部分场合完全可以替换ViewAnimation


那么,属性动画那么牛逼,是不是就学习属性动画就可以了?

我觉得,属性动画是很好的补充,但并非是替代作用。有些场景目前还必须使用视图动画,比如ListView的item动画;而且,我觉得属性动画有个麻烦的地方就是,对于清除动画的效果,没有直接公用的方法,需要自己写(也可能我不知道)。

官方文档是这样推荐:

视图动画系统需要较少的时间来设置,并且需要较少的代码来写入。如果视图动画完成了您需要执行的所有操作,或者如果现有代码已经按照您想要的方式工作,则不需要使用属性动画系统。比如有些用例,在不同情况下使用2种动画系统,那也可能是有意义的。


属性动画的API

在属性动画中,很多api的方法与视图动画的方法很类似,有些是公用的,比如,在视图动画系统中,已经定义了许多插值在android.view.animation,同样,属性动画也可以使用。

属性动画Animator类,提供子类给我们使用,可以分为以下3类:

说明
ValueAnimator 主要提供了属性动画的时间引擎,并且以动画的方式计算各种属性值。它定义了属性动画绝大部分的核心功能,包括:计算各帧的相关属性值、动画是否重复、负责处理更新时间、可以定义evaluate类型控制计算规则等等。属性动画主要有2方面组成:①计算各帧的相关属性值;②为指定的对象设置计算后的值。ValueAnimator只负责第一方面,因此使用时必须根据ValueAnimator计算并监听值的更新,来更新对象的相关属性值。
ObjectAnimator 它是ValueAnimator的直接子类,允许指定目标对象和相关属性值执行动画。它可以直接在目标对象使用动画,使用相对比较简单,因此最为常用。但有时,却需要使用ValueAnimator,因为,ObjectAnimator设置目标的属性值有一定的限制,目标对象需要拥有特定的方法属性。
AnimatorSet 用于组合多个动画,类似视图动画的AnimationSet。同样的,可以指定多个Animator的播放顺序,设置延迟时间等。

ValueAnimator 

ValueAnimator 可以静态的调用其工厂模式的方法返回想要的ValueAnimator 实例,如图:


其使用也较为简单,多是有套路的,把他们总结如下:

  1. 调用ValueAnimator 的静态方法创建ValueAnimator 实例,并设置需要变化的相关属性
  2. 调用ValueAnimator 的setXxx( )方法设置动画的持续时间、插值方式、重复次数等等,与视图动画大部分一样样;
  3. 调用ValueAnimator的start( )方法启动动画
  4. (可选)为ValueAnimator设置AnimatorUpdateListener监听,监听属性值的变化,并设置到目标对象

有了以上的套路,那么就可以按部就班了。。。

1.ofFloat(float... values)

参数:可变参数,若设置一个,那么默认范围就是0~values[0];

使用:

ValueAnimator valueAnimator = ValueAnimator.ofFloat(-100, 100);//设置需要变化的属性值范围
                valueAnimator.setDuration(2000);//设置动画时间
                valueAnimator.setRepeatCount(ValueAnimator.INFINITE);//设置动画一直重复
                valueAnimator.setRepeatMode(ValueAnimator.REVERSE);//设置动画重复的模式
                valueAnimator.start();//开始动画
                //注册监听,监听属性值的变化,并设置给目标对象
                valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
                {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation)
                    {
                        float value = (float) animation.getAnimatedValue();
                        mTarget.setTranslationX(value);
                    }
                });
2.ofInt(int... values) 

它的用法完全和ofFloat()类似,只是属性值的类型不同而已,这里不再重复。

3.ofObject(TypeEvaluator evaluator, Object... values)

参数:第一个参数是估值器,即控制属性值的变化规则,想要详细了解估值器可先往下看; 第二个,与上面的一样的,可变参数,只不过对象是Object,也可以自定义对象,范围更广。

使用:只要将上面的返回的实例改一下即可:

 
valueAnimator = ValueAnimator.ofObject(new FloatEvaluator(), -100f, 100f);

注意的是,后面2个参数是Float类型的,那么估算值的类型就不能用IntEvaluator。当然,我们也可以自定义估算值的类型和对象。

4.ofArgb(int... values)

参数:可变参数,其参数为颜色值,即16进制的int值,如:0xff000000

使用:

objectAnimator = ObjectAnimator.ofArgb(BLCAK, RED, BLUE)

该方法是从API Level 21开始,如果版本太低需要加判断。该方法接收一些代表颜色的int值,其内部使用了ArgbEvaluator估值器,可以用该方法实现将一个颜色动画渐变到另一个颜色,并且从监听中可以不断获取中间动画产生的颜色值。

那么,你可能有这样的问题:既然传入的还是int值,那么直接用ofInt()方法不就行了吗?干嘛还要新增一个ofArgb()方法呢?

你可以试试,如果使用ofInt( ),虽然可以完成颜色动画渐变,但效果会一直闪动。如果使用ofArgb(),可以从源码看到:

public static ValueAnimator ofArgb(int... values) {
    ValueAnimator anim = new ValueAnimator();
    anim.setIntValues(values);
    anim.setEvaluator(ArgbEvaluator.getInstance());
    return anim;
}

其实也是先调用ofInt()方法,只不过后面设置了颜色的估值器ArgbEvaluator。既然这样,那么就有办法消除刚才一直闪动的问题了。是的,只要设置了估值器,和ofArgb()效果一样。

那ArgbEvaluator里面到底设置了什么呢?通过源码我们知道:

一个颜色的int值包含四个字节,在Android中第一个字节代表Alpha分量,第二个字节代表Red分量,第三个字节代表Green分量,第四个字节代表Blue分量,且我们常用16进制表示颜色,这样看起来更明显易懂一些,比如int值0xffff0000表示的红色,0xff00ff00表示的是绿色,最前面的ff表示的是Alpha。ofArgb方法会通过ArgbEvaluator将颜色拆分成四个分量,然后分别对各个分量进行动画计算,然后将四个计算完的分量再重新组合成一个表示颜色的int值,这就是ofArgb方法的工作原理。所以,如果使用ofInt()不设置ArgbEvaluator,那么会将整个颜色值当做一个单独的整数来计算,这样子就会导致闪烁了。


好了,ValueAnimator 的使用就是这么简单,但实际中应用相对比较少。用的最多是ObjectAnimator,下面来介绍它。


ObjectAnimator

ObjectAnimator是最常用的了,它是ValueAnimator 的直接子类,那么就继承ValueAnimator 的功能,而且它可以直接将属性值设置给目标对象,这使得任何对象变得更加容易,因为不再需要实现ValueAnimator.AnimatorUpdateListener,动画属性会自动更新。

ObjectAnimator中,通过静态方法返回ObjectAnimator实例的方法有:


这么多,有点吓人。但平时用的多也就几个,只要会使用ValueAnimator ,这个也很简单,只是参数不一样。拿一个来举例子。

ofInt(Object target, String propertyName, int... values)

参数:target:目标对象 ; propertyName 属性名 ; values 属性范围值

使用:

 objectAnimator = ObjectAnimator.ofInt(mTarget, "textColor", BLCAK, RED, BLUE);//实例化,设置文本颜色值从BLCAK~RED~BLUE变化
                objectAnimator.setDuration(2000);//设置动画持续时间
                objectAnimator.setEvaluator(new ArgbEvaluator());//设置估算值(前面说过,使用ofInt()需要添加这行代码)
                objectAnimator.setRepeatCount(ValueAnimator.INFINITE);//设置一直重复
                objectAnimator.setRepeatMode(ValueAnimator.REVERSE);//设置一直重复模式
                objectAnimator.start();//开始动画
第一个参数:就是需要设置动画的控件对象mTarget; 第2个参数:设置属性名为“textColor”,文本的颜色;后面几个参数是属性计算值;最后设置一些和上面一样的属性就可以了。

发现没有,这里不用注册监听就直接可以将计算的值设置给目标对象,简单易用。

运行之后,我们可以看到字体的颜色一直在变化,是不是有点酷,但你要是放在视图动画中就不能直接实现。

其他方法使用都一样,这里不再详细介绍。相信大家更想知道的问题是这个:

问题:第2个参数propertyName ,到底可以设置哪些值呢?

答案是:任意值!是的,属性动画不仅单独是针对视图,它可以支持任何对象,那么就可以设置任何值,没毛病!下面进行介绍:

首先,我们前面说到,属性动画可以替换(不是替代)视图动画实现相同的效果,那么改参数肯定有透明度、旋转、缩放等,如下:

  • translationX和translationY:控制View由它的布局容器设置其左侧和顶部坐标的增量。
  • rotation,rotationX和rotationY:控制View在容器2D(旋转rotation绕枢轴点属性)和3D旋转,可以支持3D效果噢。
  • scaleX和scaleY:这些属性控制围绕其轴心点视图的2D缩放。
  • alpha:代表在查看Alpha透明度。此值是1(不透明)通过默认,以0表示全透明度的值(不可见)。
  • ......

这里拿旋转来示范,他可以支持2D、3D的旋转,其他可以自行测试:

                                     


那么问题又来了,既然可以是任意值,是否随意拿一个就可以呢?非也!官方文档的解释是:

要使ObjectAnimator更新属性正确,您必须执行以下操作:

  ● 您要动画化的对象属性必须具有setter函数(以骆驼的形式) set<propertyName>()。因为在ObjectAnimator 动画过程中自动更新属性,所以必须能够使用此setter方法访问该属性。例如,如果属性名称是foo,则需要有一个setFoo()方法。如果此setter方法不存在,您有三个选项:
      ○ 如果您有权这样做,请将setter方法添加到类中。
      ○ 使用您有权更改的包装类,并使该包装器使用有效的setter方法接收该值并将其转发到原始对象。
      ○ 使用ValueAnimator来代替。
  ● 如果values...在一种ObjectAnimator工厂方法中仅为参数指定一个值,则假设它是动画的结束值。因此,您正在动画的对象属性必须具有用于获取动画起始值的getter函数。getter方法必须采用的形式get<propertyName>()。例如,如果属性名称是 foo,则需要有一个getFoo()方法。
  ● 您所动画的属性的getter(如果需要)和setter方法必须与您指定的起始和结束值相同的类型运行ObjectAnimator。例如,您必须拥有 targetObject.setPropName(float)并targetObject.getPropName(float) 构建以下内容ObjectAnimator:
       ObjectAnimator.ofFloat(targetObject, "propName", 1f)

  ●  根据您要动画的属性或对象,您可能需要invalidate()在“视图”上调用该方法,强制屏幕使用更新的动画值重新绘制自己。你在onAnimationUpdate() 回调中这样做 。例如,当Drawable对象重新绘制时,动画化Drawable对象的颜色属性只会导致更新屏幕。所有的属性设置,如 setAlpha()和setTranslationX() 正确无效视图,所以你不需要调用这些方法与新的值时无效视图。

总结出来就是:该对象的属性必须有setter函数,getter函数则需看设置属性计算值的参数,1个则必须,2个可以没有。


AnimatorSet

在许多情况下,当播放依赖于另一个动画启动或完成的动画,或者多个动画一起播放的时候,则需要将动画组合起来。Android系统允许将动画捆绑在一起AnimatorSet,以便可以指定是同时,顺序还是在指定的延迟之后启动动画,也可以将AnimatorSet对象再嵌套AnimatorSet中。

AnimatorSet这个类主要提供了三个播放方法:

playSequentially():表示多个动画按顺序执,它主要有两种形式的参数playSequentially(Animator… items)和playSequentially(List <Animator> animator);一个是可变参数,另一个是动画集合。

playTogether():表示几个动画同时执行,它接收的参数类型也有2种,与playSequentially()一致。

play():play(Animator anim),参数是一个Animator动画实例,调用它之后会返回一个AnimatorSet.Builder的实例,AnimatorSet.Builder中包括以下四个方法:

  • after(Animator anim)   将现有动画插入到传入的动画之后执行
  • after(long delay)   将现有动画延迟指定毫秒后执行
  • before(Animator anim)   将现有动画插入到传入的动画之前执行
  • with(Animator anim)   将现有动画和传入的动画同时执行

使用:

ObjectAnimator translationX = ObjectAnimator.ofFloat(mTarget, "translationX", 400f, 0f);
			ObjectAnimator alpha = ObjectAnimator.ofFloat(mTarget, "alpha", 1f, 0f, 1f);
			ObjectAnimator rotation = ObjectAnimator.ofFloat(mTarget, "rotation", 0f, 360f);

			AnimatorSet animSet = new AnimatorSet();
			animSet.play(rotation).with(alpha).after(translationX);
			animSet.setDuration(4000);
			animSet.start();                                          

                                                                                                            

Animation Listeners

同样,属性动画也有自己的监听器,对于所有子类多可以实现的监听是:
  ● Animator.AnimatorListener
       onAnimationStart() - 动画启动时调用。
       onAnimationEnd() - 当动画结束时调用。
       onAnimationRepeat() - 当动画重演调用。
       onAnimationCancel()-当动画被取消调用。取消的动画还呼吁onAnimationEnd(),不管他们是如何结束。
如果不希望实现所有的方法Animator.AnimatorListener 接口,那么可以可扩展AnimatorListenerAdapter类,选择需要实现的方法,而不是实现Animator.AnimatorListener接口。
使用:
//实现 AnimatorListener 接口
        objectAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {

            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });

        //实现 AnimatorListenerAdapter 抽象类
        objectAnimator.addListener(new AnimatorListenerAdapter() {
            //可以自行覆盖未实现方法
        });
对于ValueAnimator还有另外的监听方法:
  ● ValueAnimator.AnimatorUpdateListener
       onAnimationUpdate() 每一帧动画都会回调,监听属性计算值的变化,可以通过getAnimatedValue()的方法来查询值的变化。
注意:实现这个监听器,必需的ValueAnimator类或者其子类ObjectAnimator,AnimatorSet不支持这个监听。
在前面已有使用过,这里不贴伪代码了。

在XML使用Animator

在视图动画,使用动画时,相对于在代码编写,使用xml编写应该更为频繁。属性动画也支持xml编写,但相反的是,在代码中编写更为频繁,这只是个人的喜好而已。但使用xml好处就是轻松地重复使用动画,并轻松地编辑动画序列,可以接下来介绍,如何在xml中使用属性动画。
首先,在res下建立一个animator文件夹(注意视图动画是anim文件夹),然后创建一个xml文件。
然后,该文件中使用以下三个标签:
  • <animator>            对应代码中的ValueAnimator
  • <objectAnimator>  对应代码中的ObjectAnimator
  • <set>                     对应代码中的AnimatorSet
每个标签的拥有的属性值如下:

<set
  android:ordering=["together" | "sequentially"]>   //指定此集中动画的播放顺序,其值可以是:together(依次播放此集合中的动画)、sequentially(在同一时间播放动画)。

    <objectAnimator
        android:propertyName="string"              //动画的属性名称。比如:"alpha"或 "backgroundColor"等
        android:duration="int"                     //动画持续时间,默认值为300ms。
        android:valueFrom="float | int | color"    //动画开始的值。颜色表示为六位十六进制数字(例如#333333)
        android:valueTo="float | int | color"      //动画结束的值。颜色表示为六位十六进制数字(例如#333333)
        android:startOffset="int"                  //动画延迟的毫秒数
        android:repeatCount="int"                  //动画重复的次数。设置为"-1"无限重复或正整数。例如,值"1"表示动画在动画的初始运行后重复一次,因此动画总共播放两次。默认值是"0",这意味着没有重复。
        android:repeatMode=["repeat" | "reverse"]  //动画重复的行为模式。设置为"reverse" 使动画反转方向与每次迭代或每次"repeat"从一开始就有动画循环。
        android:valueType=["intType" | "floatType"]/> //参数值的类型,与android:valueFrom,android:valueTo类型对应。如果值为颜色,则不要指定此属性。

    <animator
        android:duration="int"
        android:valueFrom="float | int | color"
        android:valueTo="float | int | color"
        android:startOffset="int"
        android:repeatCount="int"
        android:repeatMode=["repeat" | "reverse"]
        android:valueType=["intType" | "floatType"]/>

    <set>
        ...
    </set>
</set>

最后,在代码中调用改xml文件,通过AnimatorInflater类的loadAnimator(Context context, @AnimatorRes int id)方法加载,并设置目标对象即可:

Animator animator = AnimatorInflater.loadAnimator(this, R.animator.property_set);
			animator.setTarget(mTarget);
			animator.start();

如果还不是很清楚,可以看源码的具体实现。

在实际开发,如果不考虑复用性,建议使用代码来实现属性动画,比较简单,重要的是,很多时候一个属性的起始值是无法提前确定的,比如让button从屏幕的左边移动屏幕的右边,由于我们无法提前知道屏幕的宽度,因此无法在xml定义,这时我们就必须在代码中动态的创建属性动画了。

TypeEvaluator(估值器

估值器的作用是:告诉属性动画,如何计算给定的属性值。即根据Animator类提供的属性变化的数值:开始值,结束值,然后根据设置的估值器,从开始值 过渡到 结束值。

比如:ValueAnimator.ofInt()方法,系统内置了一个IntEvaluator,从而实现了初始值与结束值之间的平滑过度,并返回整形的结果。来看一下IntEvaluator的代码实现:

public class IntEvaluator implements TypeEvaluator<Integer> {

    /**
     * @param fraction   动画完成的比例:开始值与结束值的比例
     * @param startValue 动画的开始值
     * @param endValue   动画的结束值
     * @return  对象动画过渡的逻辑计算后的值
     */
    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int)(startInt + fraction * (endValue - startInt));
    }
}
可以看到,IntEvaluator 实现了TypeEvaluator接口,并重写了evaluate()方法,里面就是计算的逻辑。如果了解插值器(如需了解可以查看: Android动画之Interpolator(插值器)),会觉得他们的套路是一样一样的。evaluate()里面有三个参数,在上面的代码已有注释解释了。里面算法的逻辑也很简单,简单讲,根据 fraction  计算出动画完成值,再加上开始值,返回当前动画值。

这个是系统对Int类型采用默认的估值器,如果你有其他需求,完全可以重新定义一个自己的IntEvaluator 。

那么,还有那些估值器呢?如下:

估值器 说明
IntEvaluator 默认计算值为int的属性
FloatEvaluator 默认计算值为float的属性
ArgbEvaluator 默认计算值为十六进制值颜色的属性
TypeEvaluator 一个接口,可以实现该接口自定义估值器。如果动画对象的属性,不是一个int,float或color类型,那么必须实现TypeEvaluator指定如何计算对象属性的动画值接口。当然,如果你想以不同的方式处理这些类型比默认的行为,也可以重新自定义类型为int,float和color的估值器。
PointFEvaluator 用于有坐标类型的对象,比如点

在ValueAnimator 类中,有ofObject(TypeEvaluator evaluator, Object... values)方法,如果参数类型不是int、float、color类型,那么就需要自己实现估值器了。平时也较少用到,这里就不再介绍了。

这里顺便说一个插值器与估值器的区别:

插值器:决定值的变化速率;

估值器:决定返回值的具体变化


ViewPropertyAnimator的使用

视图动画只能对View对象操作,是面对对象的操作。而属性动画的机制已经不是再针对于View而进行设计的了,而是一种不断地对值进行操作的机制,它可以将值赋值到指定对象的指定属性上,不再是面对对象的思维了。但是我们大部分使用还是对View的操作,因此在Android 3.1系统当中补充了ViewPropertyAnimator。

它的行为很像ObjectAnimator,因为它修改视图属性的实际值,针对同时许多动画属性时,更有效。比如:

如果View对象只需要对一个或两个属性使用动画,那么使用一个 ObjectAnimator是好的,但是,如果多个属性同时动画(注意是同时),或者如果只想要一个更方便的语法来动画化特定属性,则ViewPropertyAnimator可能更适合。这个类可以为几个同时的动画提供更好的性能,此外,使用的代码ViewPropertyAnimator更加简洁,更易于阅读。
下面的代码段示出了使用多个差异 ObjectAnimator的对象,与使用ViewPropertyAnimator实现相同效果的对比:
使用ObjectAnimator:
			ObjectAnimator animX = ObjectAnimator.ofFloat(mTarget, "x", 50f);
			ObjectAnimator animY = ObjectAnimator.ofFloat(mTarget, "y", 100f);
			AnimatorSet animSetXY = new AnimatorSet();
			animSetXY.playTogether(animX, animY);
			animSetXY.start();
使用ViewPropertyAnimator:
	mTarget.animate().x(50f).y(100f);
可以看到,实在简洁太多了。。。ViewPropertyAnimator并不难:
首先看看API,ViewPropertyAnimator的功能都是建立在View类新增的animate()方法之上的,然后可以看到:

可以看到,ViewPropertyAnimator包含了透明度、缩放、旋转、平移等设置,还有插值器、持续时间、延迟时间等属性同样可以设置,而且返回的多是ViewPropertyAnimator,那么它有一个特点就是,它可以使用连缀的方式使用,一行代码就可以搞定复杂的动画,但是全部是一起执行,这是要是注意的。
有些人可能对:x (float value) 和 xBy (float value)有疑问,这2个有什么区别?
其实和scrollTo(int x, int y),scrollBy(int x, int y)原理类似。x (float value)是指,滚动到x坐标的value;而xBy (float value)是指,相对当前位置,滚动值为value的偏移量。具体可自行测试。

还有一个注意的是:使用的时候,可以不显示的调用start()。因为每调用一个属性方法的时候,多会隐式开启动画的功能mView.postOnAnimation(mAnimationStarter)。如果后面有连缀,会先会移除之前的开始动画功能mView.removeCallbacks(mAnimationStarter),在继续发送,一直到最后的连缀方法执行完,然后就一起执行动画。当然,也可以自己显示调用。源码如下:
private void animatePropertyBy(int constantName, float startValue, float byValue) {
       ........
        mView.removeCallbacks(mAnimationStarter);
        mView.postOnAnimation(mAnimationStarter);
    }
private Runnable mAnimationStarter = new Runnable() {
        @Override
        public void run() {
            startAnimation();
        }
    };
public void start() {
        mView.removeCallbacks(mAnimationStarter);
        startAnimation();
    }

好了,ViewPropertyAnimator相对还是比较简单,介绍就到这里。举个例子使用,如:
mTarget.animate().yBy(200f).rotation(360).scaleY(2).setDuration(3000).setInterpolator(new LinearInterpolator()).start();
这样子就实现了,较复杂的动画,而且样样俱全,主要是一行代码就可以搞定了,简直有点酷!

好了,属性动画的介绍就到此结束了。每次总结,收获满满,主要是自己动手去实践,去了解清楚。同样的知识点,网上很多博客都写得很精彩,更详细,比这好很多,但等你需要时翻阅以前的知识点时,可能没有无从下手,你会发现,再好的也是别人。而现在,它在你的文档里了,可能不是很详细,甚至有错,但起码属于你的,里面思路一看就明了,捡起来就高效率了。


如需源码点这里:点我


主要参考:

https://developer.android.google.cn/guide/topics/graphics/prop-animation.html

http://blog.csdn.net/guolin_blog/article/details/43536355









猜你喜欢

转载自blog.csdn.net/pzm1993/article/details/77929368