天天看点

Android动画解析(二)-----属性动画

本篇文章部分参考自郭神博客 http://blog.csdn.net/guolin_blog/article/details/43536355#t0

上一篇主要介绍了补间动画,但是补间动画有很大的局限性,比如只能对View进行操作,只能实现移动,缩放,旋转,淡入淡出效果,一旦超出这种需求,补间动画就显得捉襟见肘了。最重要的是补间动画只是改变了View的显示效果,而不会改变View的属性。比如在屏幕的左边放一个Button,并设置了点击事件,然后通过平移补间动画移动到右边,点击事件的响应仍然在左边。因为实际上这个按钮还是在屏幕的左边,补间动画只是把这个Button绘制到了右边。

因为补间动画的这些局限性,android在3.0版本(API 11)的时候,引进了一种全新的动画:属性动画。

属性动画的优势有哪些?

  • 属性动画可以作用到所有的Java对象,不再局限于View对象。
  • 可以实现更多动画效果,不只限于平移,翻转,缩放,淡入淡出四种。
  • 属性动画实现的不再只是视觉效果,对象的属性会随着动画的改变而改变。

属性动画的原理

在设定的时间间隔内,通过不断对该对象值进行改变,并赋值给对象的属性,从而实现该对象的动画效果。

属性动画的使用

ValueAnimator类

ValueAnimator类可以说是属性动画中最核心的类了,上面我们说了,属性动画的运行机制是通过不断地对值进行操作来实现的,而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的。ValueAnimator还负责管理动画的播放次数,播放模式,以及对动画设置监听器等,ValueAnimator类当中有三个重要的方法:

ValueAnimator.ofInt(int values)    // 将初始值 以整型数值的形式 过渡到结束值
    ValueAnimator.ofFloat(float values)// 将初始值 以浮点型数值的形式 过渡到结束值
    ValueAnimator.ofObject(int values) // 将初始值 以对象的形式 过渡到结束值
           

我们以 ValueAnimator.ofFloat(float values) 为例,

ValueAnimator anim = ValueAnimator.ofFloat(f, f);  
    anim.setDuration();   //设置动画持续时间,不设置默认值为300ms
    anim.setStartDelay();  // 设置动画延迟播放时间   单位为 ms
    anim.setRepeatCount();   // 设置动画重复播放次数 = 重放次数+1
                              // 动画播放次数 = infinite时,动画无限重复
    anim.setRepeatMode(ValueAnimator.RESTART); // 设置重复播放动画模式
                                               // ValueAnimator.RESTART(默认):正序重放
                                               // ValueAnimator.REVERSE:倒序回放
    anim.start();  
           

调用ValueAnimator的ofFloat()方法就可以构建出一个ValueAnimation的实例,ofFloat()方法当中允许传入多个float值,这里传入和1 就表示将值从0平滑过渡到1,最后调用start()方法启动动画。

这只是一个将值从0过渡到1的动画,看不到任何界面效果,如果想知道动画是不是已经运行了,这就需要借助监听器实现,如下所示:

ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);  

    ... 

    anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {  
        @Override  
        public void onAnimationUpdate(ValueAnimator animation) {  
            float currentValue = (float) animation.getAnimatedValue(); 
            Log.d("TAG", "cuurent value is " + currentValue);  
        }  
    });  
    anim.start();  
           

可以看到,这里我们通过addUpdateListener()方法来添加一个动画的监听器,在动画执行的过程中会不断地进行回调,我们只需要在回调方法当中将当前的值取出并打印出来,就可以知道动画有没有真正运行了。运行上述代码,控制台打印如下所示:

Android动画解析(二)-----属性动画

从打印日志的值我们就可以看出,ValueAnimator确实已经在正常工作了,值在300毫秒的时间内从0平滑过渡到了1,而这个计算工作就是由ValueAnimator帮助我们完成的。另外ofFloat()方法当中是可以传入任意多个参数的,因此我们还可以构建出更加复杂的动画逻辑,比如说将一个值在5秒内从0过渡到5,再过渡到3,再过渡到10,就可以这样写:

ValueAnimator anim = ValueAnimator.ofFloat(, , , );  
    anim.setDuration();  
    anim.start();  
           

当然也许你并不需要小数位数的动画过渡,可能你只是希望将一个整数值从0平滑地过渡到100,那么也很简单,只需要调用ValueAnimator的ofInt()方法就可以了,如下所示:

ObjectAnimator类

相比于ValueAnimator,ObjectAnimator可能才是我们最常接触到的类,因为ValueAnimator只不过是对值进行了一个平滑的动画过渡,但我们实际使用到这种功能的场景好像并不多。而ObjectAnimator则就不同了,它是可以直接对任意对象的任意属性进行动画操作的,比如说View的alpha属性。

ObjectAnimator类是继承自ValueAnimator的,底层的动画实现机制也是基于ValueAnimator来完成的,因此ValueAnmiator仍然是属性动画中最核心的类。既然是继承关系,ValueAnimator中的方法在ObjectAnimator中也是可以正常使用的,用法也非常相似,这里如果我们想要将一个TextView在5秒中内从常规变换成全透明,再从全透明变换成常规,就可以这样写:

ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha", f, f, f);  
    animator.setDuration();  
    animator.start();  
           

这里第一个参数要求传入一个object对象,我们想要对哪个对象进行动画操作就传入什么,这里我传入了一个textview。第二个参数是想要对该对象的哪个属性进行动画操作,由于我们想要改变TextView的不透明度,因此这里传入”alpha”。后面的参数就是不固定长度了,想要完成什么样的动画就传入什么值,这里传入的值就表示将TextView从常规变换成全透明,再从全透明变换成常规。之后调用setDuration()方法来设置动画的时长,然后调用start()方法启动动画。

Android动画解析(二)-----属性动画

学会了这一个用法之后,其它的用法我们就可以举一反三了,那比如说我们想要将TextView进行一次360度的旋转,就可以这样写:

ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "rotation", f, f); 
    animator.setDuration();  
    animator.start();  
           

可以看到,这里我们将第二个参数改成了”rotation”,然后将动画的初始值和结束值分别设置成0和360,现在运行一下代码,效果如下图所示:

Android动画解析(二)-----属性动画

那么如果想要将TextView先向左移出屏幕,然后再移动回来,就可以这样写:

float curTranslationX = textview.getTranslationX();  
    ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "translationX", curTranslationX, -f, curTranslationX);  
    animator.setDuration();  
    animator.start();  
           

这里我们先是调用了TextView的getTranslationX()方法来获取到当前TextView的translationX的位置,然后ofFloat()方法的第二个参数传入”translationX”,紧接着后面三个参数用于告诉系统TextView应该怎么移动,现在运行一下代码,效果如下图所示:

Android动画解析(二)-----属性动画

然后我们还可以TextView进行缩放操作,比如说将TextView在垂直方向上放大3倍再还原,就可以这样写:

ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "scaleY", f, f, f);  
animator.setDuration();  
animator.start(); 
           

这里将ofFloat()方法的第二个参数改成了”scaleY”,表示在垂直方向上进行缩放,现在重新运行一下程序,效果如下图所示:

Android动画解析(二)-----属性动画

ofFloat()方法的第二个参数到底可以传哪些值呢?目前我们使用过了alpha、rotation、translationX和scaleY这几个值,分别可以完成淡入淡出、旋转、水平移动、垂直缩放这几种动画。而实际上,这个参数可以传入任意的值,因为ObjectAnimator再设计的时候就没有单单针对View来设计,而是针对于任意对象的,它所负责的工作就是不断地向某个对象中的某个属性进行赋值,然后对象根据属性值的改变来决定如何展现出来。

比如如说我们调用下面这段代码:

ObjectAnimator.ofFloat(textview, "alpha", , );  
           

这段代码的意思是ObjectAnimator会不断地改变textView对象中alpha属性的值,从1f变化到0f,然后textview对象需要根据alpha属性值的改变来不断刷新界面的显示,从而让用户可以看出淡入淡出的动画效果。那么textview对象中是不是有alpha属性这个值呢?没有,不仅textview没有这个属性,连它所有的父类也是没有这个属性的!这就奇怪了,textview当中并没有alpha这个属性,ObjectAnimator是如何进行操作的呢?其实 ObjectAnimator内部的工作机制并不是直接对我们传入的属性名进行操作的,而是会去寻找这个属性名对应的get和set方法, 因此alpha属性所对应的get和set方法应该就是:

public void setAlpha(float value);  
    public float getAlpha();  
           

那么textview对象中是否有这两个方法呢?确实有,并且这两个方法是由View对象提供的,也就是说不仅TextView可以使用这个属性来进行淡入淡出动画操作,任何继承自View的对象都可以的。

既然alpha是这个样子,相信大家一定已经明白了,前面我们所用的所有属性都是这个工作原理,那么View当中一定也存在着setRotation()、getRotation()、setTranslationX()、getTranslationX()、setScaleY()、getScaleY()这些方法,不信的话你可以到View当中去找一下。

使用组合动画

如果想多个动画一起使用,也非常简单,android提供了丰富的API供我们调用。

实现组合动画功能主要需要借助AnimatorSet这个类,(注意和 AnimationSet 区别) 这个类提供了一个play()方法,如果我们向这个方法中传入一个Animator对象(ValueAnimator或ObjectAnimator)将会返回一个AnimatorSet.Builder的实例,AnimatorSet.Builder中包括以下四个方法:

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

有了这四个方法,我们就可以完成组合动画的逻辑了,那么比如说我们想要让TextView先从屏幕外移动进屏幕,然后开始旋转360度,旋转的同时进行淡入淡出操作,就可以这样写:

ObjectAnimator moveIn = ObjectAnimator.ofFloat(textview, "translationX", -f, f);  
    ObjectAnimator rotate = ObjectAnimator.ofFloat(textview, "rotation", f, f);  
    ObjectAnimator fadeInOut = ObjectAnimator.ofFloat(textview, "alpha", f, f, f);  
    AnimatorSet animSet = new AnimatorSet();  
    animSet.play(rotate).with(fadeInOut).after(moveIn);  
    animSet.setDuration();  
    animSet.start();  
           

这里我们先是把三个动画的对象全部创建出来,然后new出一个AnimatorSet对象之后将这三个动画对象进行播放排序,让旋转和淡入淡出动画同时进行,并把它们插入到了平移动画的后面,最后是设置动画时长以及启动动画。运行一下上述代码,效果如下图所示:

Android动画解析(二)-----属性动画

Animator监听器

在很多时候,我们希望可以监听到动画的各种事件,比如动画何时开始,何时结束,然后在开始或者结束的时候去执行一些逻辑处理。这个功能是完全可以实现的,Animator类当中提供了一个addListener()方法,这个方法接收一个AnimatorListener,我们只需要去实现这个AnimatorListener就可以监听动画的各种事件了。

大家已经知道,ObjectAnimator是继承自ValueAnimator的,而ValueAnimator又是继承自Animator的,因此不管是ValueAnimator还是ObjectAnimator都是可以使用addListener()这个方法的。另外AnimatorSet也是继承自Animator的,因此addListener()这个方法算是个通用的方法。

添加一个监听器的代码如下所示:

anim.addListener(new AnimatorListener() {  
    @Override  
    public void onAnimationStart(Animator animation) {  
    // 在动画开始的时候调用
    }  

    @Override  
    public void onAnimationRepeat(Animator animation) {  
    // 在动画重复执行的时候调用
    }  

    @Override  
    public void onAnimationEnd(Animator animation) {  
    // 在动画结束的时候调用
    }  

    @Override  
    public void onAnimationCancel(Animator animation) {  
    // 在动画被取消的时候调用
    }  
}); 
           

但是也许很多时候我们并不想要监听那么多个事件,可能我只想要监听动画结束这一个事件,那么每次都要将四个接口全部实现一遍就显得非常繁琐。没关系,为此Android提供了一个适配器类,叫作AnimatorListenerAdapter,使用这个类就可以解决掉实现接口繁琐的问题了,如下所示:

anim.addListener(new AnimatorListenerAdapter() {  
    });  
           

这里我们向addListener()方法中传入这个适配器对象,由于AnimatorListenerAdapter中已经将每个接口都实现好了,所以这里不用实现任何一个方法也不会报错。那么如果我想监听动画结束这个事件,就只需要单独重写这一个方法就可以了,如下所示:

anim.addListener(new AnimatorListenerAdapter() {  
        @Override  
        public void onAnimationEnd(Animator animation) {  
        }  
    });  
           

我们可以使用代码来编写所有的动画功能,这也是最常用的一种做法。不过,过去的补间动画除了使用代码编写之外也是可以使用XML编写的,因此属性动画也提供了这一功能,即通过XML来完成和代码一样的属性动画功能。

通过XML来编写动画可能会比通过代码来编写动画要慢一些,但是在重用方面将会变得非常轻松,比如某个将通用的动画编写到XML里面,我们就可以在各个界面当中轻松去重用它。

如果想要使用XML来编写动画,首先要在res目录下面新建一个animator文件夹,所有属性动画的XML文件都应该存放在这个文件夹当中。然后在XML文件中我们一共可以使用如下三种标签:

  • <animator>

    对应代码中的ValueAnimator
  • <objectAnimator>

    对应代码中的ObjectAnimator
  • <set>

    对应代码中的AnimatorSet

那么比如说我们想要实现一个从0到100平滑过渡的动画,在XML当中就可以这样写:

<animator xmlns:android="http://schemas.android.com/apk/res/android"  
        android:valueFrom="0"  
        android:valueTo="100"  
        android:valueType="intType"/>  
           

而如果我们想将一个视图的alpha属性从1变成0,就可以这样写:

<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"  
        android:valueFrom="1"  
        android:valueTo="0"  
        android:valueType="floatType"  
        android:propertyName="alpha"/>  
           

另外,我们也可以使用XML来完成复杂的组合动画操作,比如将一个视图先从屏幕外移动进屏幕,然后开始旋转360度,旋转的同时进行淡入淡出操作,就可以这样写

<set xmlns:android="http://schemas.android.com/apk/res/android"  
        android:ordering="sequentially" >  

        <objectAnimator  
            android:duration="2000"  
            android:propertyName="translationX"  
            android:valueFrom="-500"  
            android:valueTo="0"  
            android:valueType="floatType" >  
        </objectAnimator>  

        <set android:ordering="together" >  
            <objectAnimator  
                android:duration="3000"  
                android:propertyName="rotation"  
                android:valueFrom="0"  
                android:valueTo="360"  
                android:valueType="floatType" >  
            </objectAnimator>  

            <set android:ordering="sequentially" >  
                <objectAnimator  
                    android:duration="1500"  
                    android:propertyName="alpha"  
                    android:valueFrom="1"  
                    android:valueTo="0"  
                    android:valueType="floatType" >  
                </objectAnimator>  
                <objectAnimator  
                    android:duration="1500"  
                    android:propertyName="alpha"  
                    android:valueFrom="0"  
                    android:valueTo="1"  
                    android:valueType="floatType" >  
                </objectAnimator>  
            </set>  
        </set>  

    </set>  
           

这段XML实现的效果和我们刚才通过代码来实现的组合动画的效果是一模一样的。

最后XML文件是编写好了,那么我们如何在代码中把文件加载进来并将动画启动呢?只需调用如下代码即可:

Animator animator = AnimatorInflater.loadAnimator(context, R.animator.anim_file);  
    animator.setTarget(view);  
    animator.start();  
           

调用AnimatorInflater的loadAnimator来将XML动画文件加载进来,然后再调用setTarget()方法将这个动画设置到某一个对象上面,最后再调用start()方法启动动画就可以了。