Android知识点 动画机制

在Android 3.0之前的版本,我们能使用的动画类型有两种,分别是逐帧动画和补间动画;

在Android3.0发布时,Android SDK又为开发者带来了更加强大灵活的属性动画,使得实现复杂的动画效果更加容易;

随着时间的推进,在Android4.4中,Android SDK又为开发者带来了android.transition框架。

逐帧动画(Frame Animation)

有两种方式进行实现:

XML资源文件方式 
这是最常用的方式,我们将每一帧的图片放到res/drawable-hdpi目录中,然后在目录中新建一个动画XML文件,在这个文件中使用 animation_list标签来定义动画的帧序列,使用item标签定义动画的每一帧,并在其中指定帧的持续时间等属性,格式如下:


<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item android:drawable="@drawable/icon1" android:duration="150"/>
    <item android:drawable="@drawable/icon2" android:duration="150"/>
    <item android:drawable="@drawable/icon3" android:duration="150"/>
    
animation-list>

其中android:oneshot用来控制动画是否循环,true表示动画不会循环播放,false表示会循环播放。android:duration用来指定每一帧的播放持续时间。

代码方式 ,使用AnimationDrawable .

    //代码实现逐帧动画
        AnimationDrawable animDrawable = new AnimationDrawable();
        for (int i = 1 ;i<4;i++){
            int id = getResources().getIdentifier("icon" + i, "drawable", getPackageName());
            Drawable drawable = getResources().getDrawable(id);
            animDrawable.addFrame(drawable,120);
        }
        imageView.setImageDrawable(animDrawable);
        animDrawable.setOneShot(false);

定义好逐帧动画之后在某个条件中出发开始和停止的操作。伪代码如下:

// 获取AnimationDrawable对象实例,用来控制动画的播放和停止
AnimationDrawable animDrawable = (AnimationDrawable) imageView.getDrawable();
// 开始动画
animDrawable.start();
// 结束动画
animDrawable.stop();

总结:

Frame Animation(逐帧动画)相对来说比较简单,但是在实际开发中使用的频率还是比较高的。但是逐帧动画只能实现比较小的动画效果,如果复杂而且帧数比较多的动画不太建议使用逐帧动画,一方面是因为会造成OOM,另一方面会显得很卡,如果真是超级复杂的动画的话建议选择双缓冲绘制View来实现。


2 补间动画(Tween Animation)

补间动画与逐帧动画在本质上是不同的,逐帧动画通过连续播放图片来模拟动画的效果,而补间动画则是通过在两个关键帧之间补充渐变的动画效果来实现的。补间动画的优点是可以节省空间。目前Android应用框架支持的补间动画效果有以下5种。

AlphaAnimation:透明度(alpha)渐变效果,对应<alpha/>标签。

TranslateAnimation:位移渐变,需要指定移动点的开始和结束坐标,对应<translate/>标签。

ScaleAnimation:缩放渐变,可以指定缩放的参考点,对应<scale/>标签。

RotateAnimation:旋转渐变,可以指定旋转的参考点,对应<rotate/>标签。

AnimationSet:组合渐变,支持组合多种渐变效果,对应<set/>标签。

同样,定义补间动画也可以分为XML资源文件和代码两种方式。不过在这之前还是先来看下插值器Interpolator。

插值器Interpolator  

前面说到的Android系统会在补间动画的开始和结束关键帧之间插入渐变值,它依据的便是Interpolator。具体来说,Interpolator会根据类型的不同,选择不同的算法计算出在补间动画期间所需要动态插入帧的密度和位置,Interpolator负责控制动画的变化速度,使得前面所说的几种动画效果能够以匀速、加速、减速、抛物线等多种速度进行变化。

Android SDK默认提供了几个Interpolator的实现类:

  • AccelerateDecelerateInterpolator 在动画开始与结束的地方速率改变比较慢,在中间的时候加速

  • AccelerateInterpolator 在动画开始的地方速率改变比较慢,然后开始加速

  • AnticipateInterpolator 开始的时候向后然后向前甩

  • AnticipateOvershootInterpolator 开始的时候向后然后向前甩一定值后返回最后的值

  • BounceInterpolator 动画结束的时候弹起

  • CycleInterpolator 动画循环播放特定的次数,速率改变沿着正弦曲线

  • DecelerateInterpolator 在动画开始的地方快然后慢

  • LinearInterpolator 以常量速率改变

  • OvershootInterpolator 向前甩一定值后再回到原来位置

如果android定义的interpolators不符合你的效果也可以自定义interpolator

XML实现方式 
这里只实现一种,其他的都类似

<rotate xmlns:android="http://schemas.android.com/apk/res/android"  
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"  
    android:fromDegrees="0"  
    android:toDegrees="360"  
    android:duration="1000"  
    android:repeatCount="1"  
    android:repeatMode="reverse"/>  
 

代码实现

TranslateAnimation translateAnimation = new TranslateAnimation(0, 
200, 0, 0); translateAnimation.setDuration(2000); 
imageView.startAnimation(translateAnimation); 

在实际项目中,我们经常使用补间动画,原因是补间动画使用起来比较方便,功能也比逐帧动画强大不少,而且还可以很方便地进行动画叠加,实现更加复杂的效果。

3 属性动画(Property Animation)

属性动画是在Android3.0中引入的,为什么要引入属性动画呢?

在补间动画中,我们只能改变View的绘制效果,View的真实属性没有变化,而属性动画则是直接改变View对象的属性值。什么意思?假如你把一个Button从屏幕的左上角移到右下角,你在补间动画中点击右下角的Button按钮是没有任何作用的,因为Button的真实属性位置还在左上角。这是补间动画的致命缺陷。

然后补间动画还有一个缺陷,就是它只能够实现移动、缩放、旋转和淡入淡出这四种动画操作,那如果我们希望可以对View的背景色进行动态地改变呢?很遗憾,我们只能靠自己去实现了。说白了,之前的补间动画机制就是使用硬编码的方式来完成的,功能限定死就是这些,基本上没有任何扩展性可言。

属性动画几乎可以对任何对象执行动画,而不是局限在View对象上。补间动画是只能够作用在View上,只要是任意继承View的控件都可以使用,但是对于非View控件,补间动画就爱莫能助了。可能有的朋友会感到不能理解,我怎么会需要对一个非View的对象进行动画操作呢?这里我举一个简单的例子,比如说我们有一个自定义的View,在这个View当中有一个Point对象用于管理坐标,然后在onDraw()方法当中就是根据这个Point对象的坐标值来进行绘制的。也就是说,如果我们可以对Point对象进行动画操作,那么整个自定义View的动画效果就有了。

说了那么多属性动画有而补间动画不足的地方,相信大家已经对饥渴难耐了,下面就开始学习属性动画。

Evaluator 
在说属性动画之前,还是要聊一聊Evaluator这个概念。它是用来控制属性动画如何计算属性值的。它的接口定义是TypeEvaluator,其中定义了evaluate方法,供不同的子类实现。可能在大多数情况下我们使用属性动画的时候都不会用到TypeEvaluator,但是大家还是应该了解一下它的用法,以防止当我们遇到一些解决不掉的问题时能够想起来还有这样的一种解决方案。

那么TypeEvaluator的作用到底是什么呢?简单来说,就是告诉动画系统如何从初始值过度到结束值。我们在上一篇文章中学到的ValueAnimator.ofFloat()方法就是实现了初始值与结束值之间的平滑过度,那么这个平滑过度是怎么做到的呢?其实就是系统内置了一个FloatEvaluator,它通过计算告知动画系统如何从初始值过度到结束值,我们来看一下FloatEvaluator的代码实现:

    public class FloatEvaluator implements TypeEvaluator {  
        public Object evaluate(float fraction, Object startValue, Object endValue) {  
            float startFloat = ((Number) startValue).floatValue();  
            return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);  
        }  
    }  

可以看到,FloatEvaluator实现了TypeEvaluator接口,然后重写evaluate()方法。evaluate()方法当中传入了三个参数,第一个参数fraction非常重要,这个参数用于表示动画的完成度的,我们应该根据它来计算当前动画的值应该是多少,第二第三个参数分别表示动画的初始值和结束值。那么上述代码的逻辑就比较清晰了,用结束值减去初始值,算出它们之间的差值,然后乘以fraction这个系数,再加上初始值,那么就得到当前动画的值了。

好的,那FloatEvaluator是系统内置好的功能,并不需要我们自己去编写,但介绍它的实现方法是要为我们后面的功能铺路的。前面我们使用过了ValueAnimator的ofFloat()和ofInt()方法,分别用于对浮点型和整型的数据进行动画操作的,但实际上ValueAnimator中还有一个ofObject()方法,是用于对任意对象进行动画操作的。但是相比于浮点型或整型数据,对象的动画操作明显要更复杂一些,因为系统将完全无法知道如何从初始对象过度到结束对象,因此这个时候我们就需要实现一个自己的TypeEvaluator来告知系统如何进行过度。

ValueAnimator

ValueAnimator是整个属性动画机制当中最核心的一个类,前面我们已经提到了,属性动画的运行机制是通过不断地对值进行操作来实现的,而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的。它的内部使用一种时间循环的机制来计算值与值之间的动画过渡,我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长,那么ValueAnimator就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。除此之外,ValueAnimator还负责管理动画的播放次数、播放模式、以及对动画设置监听器等,确实是一个非常重要的类。

但是ValueAnimator的用法却一点都不复杂,我们先从最简单的功能看起吧,比如说想要将一个值从0平滑过渡到1,时长300毫秒,就可以这样写:

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

怎么样?很简单吧,调用ValueAnimator的ofFloat()方法就可以构建出一个ValueAnimator的实例,ofFloat()方法当中允许传入多个float类型的参数,这里传入0和1就表示将值从0平滑过渡到1,然后调用ValueAnimator的setDuration()方法来设置动画运行的时长,最后调用start()方法启动动画。

另外ofFloat()方法当中是可以传入任意多个参数的,因此我们还可以构建出更加复杂的动画逻辑,比如说将一个值在5秒内从0过渡到5,再过渡到3,再过渡到10,就可以这样写:

    ValueAnimator anim = ValueAnimator.ofFloat(0f, 5f, 3f, 10f);  
    anim.setDuration(5000);  
    anim.start();  

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

ValueAnimator anim = ValueAnimator.ofInt(0, 100); 

那么除此之外,我们还可以调用setStartDelay()方法来设置动画延迟播放的时间,调用setRepeatCount()和setRepeatMode()方法来设置动画循环播放的次数以及循环播放的模式,循环模式包括RESTART和REVERSE两种,分别表示重新播放和倒序播放的意思。这些方法都很简单,我就不再进行详细讲解了。

ObjectAnimator

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

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

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

可以看到,我们还是调用了ofFloat()方法来去创建一个ObjectAnimator的实例,只不过ofFloat()方法当中接收的参数有点变化了。这里第一个参数要求传入一个object对象,我们想要对哪个对象进行动画操作就传入什么,这里我传入了一个textview。第二个参数是想要对该对象的哪个属性进行动画操作,由于我们想要改变TextView的不透明度,因此这里传入”alpha”。后面的参数就是不固定长度了,想要完成什么样的动画就传入什么值,这里传入的值就表示将TextView从常规变换成全透明,再从全透明变换成常规。之后调用setDuration()方法来设置动画的时长,然后调用start()方法启动动画。效果就不展示了,大家可以自行测试。

组合动画(AnimatorSet)

独立的动画能够实现的视觉效果毕竟是相当有限的,因此将多个动画组合到一起播放就显得尤为重要。幸运的是,Android团队在设计属性动画的时候也充分考虑到了组合动画的功能,因此提供了一套非常丰富的API来让我们将多个动画组合到一起。

实现组合动画功能主要需要借助AnimatorSet这个类,这个类提供了一个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", -500f, 0f);  
    ObjectAnimator rotate = ObjectAnimator.ofFloat(textview, "rotation", 0f, 360f);  
    ObjectAnimator fadeInOut = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f);  
    AnimatorSet animSet = new AnimatorSet();  
    animSet.play(rotate).with(fadeInOut).after(moveIn);  
    animSet.setDuration(5000);  
    animSet.start();  

可以看到,这里我们先是把三个动画的对象全部创建出来,然后new出一个AnimatorSet对象之后将这三个动画对象进行播放排序,让旋转和淡入淡出动画同时进行,并把它们插入到了平移动画的后面,最后是设置动画时长以及启动动画。

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) {  
    }  
}); 
  • 我们需要实现接口中的四个方法,onAnimationStart()方法会在动画开始的时候调用,onAnimationRepeat()方法会在动画重复执行的时候调用,onAnimationEnd()方法会在动画结束的时候调用,onAnimationCancel()方法会在动画被取消的时候调用。

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

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

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

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

使用XML编写动画

我们可以使用代码来编写所有的动画功能,这也是最常用的一种做法。不过,过去的补间动画除了使用代码编写之外也是可以使用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()方法启动动画就可以了,就是这么简单。


使用动画的注意事项 :

1. OOM 

主要是出现在帧动画中,注意图片大小

2. 内存泄漏

属行动画循环播放的时候,在Activity退出的时候要及时停止,否则会持有Activity ,发生内存泄漏

3. 兼容性问题

4. View动画的问题

有时候动画结束之后,发现View无法隐藏,这个时候,要调用view.clearAnimation(),清除动画

5.不要使用px

px会导致在同步设备上显示小活不同

6. 动画元素的交互

View移动之后,3.0之前的系统,触发事件仍然保存在原位置。3.0以后出发位置在移动后的位置

7. 硬件加速,使用动画建议开启硬件加速,这样会提供动画的流畅性


参考 :Android 开发 艺术探索


你可能感兴趣的:(Android,Android开发艺术探索学习,面试点整理)