Android的动画可以分为三种:View动画、帧动画和属性动画,其实帧动画也属于 View动画的一种,只不过它和平移、旋转等常见的View动画在表现形式上略有不同而 已。View动画通过对场景里的对象不断做图像变换(平移、缩放、旋转、透明度)从而 产生动画效果,它是一种渐近式动画,并且View动画支持自定义。帧动画通过顺序播放 一系列图像从而产生动画效果,可以简单理解为图片切换动画,很显然,如果图片过多过 大就会导致OOM。属性动画通过动态地改变对象的属性从而达到动画效果,属性动画为 API 11的新特性,在低版本无法直接使用属性动画,但是我们仍然可以通过兼容库来使用 它。在本章中,首先简单介绍View动画以及自定义View动画的方式,接着介绍View动画 的一些特殊的使用场景,最后对属性动画做一个全面性的介绍,另外还介绍使用动画的一 些注意事项。
View动画的作用对象是View,它支持4种动画效果,分别是平移动画、缩放动画、旋 转动画和透明度动画。除了这四种典型的变换效果外,帧动画也属于View动画,但是帧 动画的表现形式和上面的四种变换效果不太一样。
View动画的四种变换效果对应着Animation的四个子类:TranslateAnimation、 ScaleAnimation、RotateAnimation和AlphaAnimation,这四种动画既可以通 过XML来定义,也可以通过代码来动态创建,对于View动画来说,建议采用XML来定义 动画,这是因为XML格式的动画可读性更好。
从上面的语法可以看出,View动画既可以是单个动画,也可以由一系列动画组成。
标签表示动画集合,对应AnimationSet类,它可以包含若干个动画,并且它的内 部也是可以嵌套其他动画集合的,它的两个属性的含义如下:
android:interpolator
表示动画集合所采用的插值器,插值器影响动画的速度,比如非匀速动画就需要通过 插值器来控制动画的播放过程。这个属性可以不指定,默认为 @android:anim/accelerate_decelerate_interpolator,即加速减速插值器,关于插值器的概念 会在7.3.2节中进行具体介绍。
android:shareInterpolator
表示集合中的动画是否和集合共享同一个插值器。如果集合不指定插值器,那么子动画就需要单独指定所需的插值器或者使用默认值。
标签标示平移动画,对应TranslateAnimation类,它可以使一个View在水平 和竖直方向完成平移的动画效果,它的一系列属性的含义如下:
android:fromXDelta——表示x的起始值,比如0;
android:toXDelta——表示x的结束值,比如100;
android:fromYDelta——表示y的起始值;
android:toYDelta——表示y的结束值。
标签表示缩放动画,对应ScaleAnimation,它可以使View具有放大或者缩小的 动画效果,它的一系列属性的含义如下:
android:fromXScale——水平方向缩放的起始值,比如0.5;
android:toXScale——水平方向缩放的结束值,比如1.2;
android:fromYScale——竖直方向缩放的起始值;
android:toYScale——竖直方向缩放的起始值;
android:pivotX——缩放的轴点的x坐标,它会影响缩放的效果;
android:pivotY——缩放的轴点的y坐标,它会影响缩放的效果
在标签中提到了轴点的概念,默认情况下轴点是View的中心点,这个时候在水平方向进行缩放的话会导致View向左右两个方向同时进行缩放,但是 如果把轴点设为View的右边界,那么View就只会向左边进行缩放,反之则向右边进行缩放。
标签表示旋转动画,对于RotateAnimation,它可以使View具有旋转的动画效 果,它的属性的含义如下:
android:fromDegrees——旋转开始的角度,比如0;
android:toDegrees——旋转结束的角度,比如180;
android:pivotX——旋转的轴点的x坐标;
android:pivotY——旋转的轴点的y坐标。
在旋转动画中也有轴点的概念,它也会影响到旋转的具体效果。在旋转动画中,轴点 扮演着旋转轴的角色,即View是围绕着轴点进行旋转的,默认情况下轴点为View的中心
点。考虑一种情况,View围绕着自己的中心点和围绕着自己的左上角旋转90度显然是不同的旋转轨迹,不同轴点对旋转效果的影响读者可以自己测试一下。
标签表示透明度动画,对应AlphaAnimation,它可以改变View的透明度,它 的属性的含义如下:
android:fromAlpha——表示透明度的起始值,比如0.1; android:toAlpha——表示透明度的结束值,比如1。
上面简单介绍了View动画的XML格式,具体的使用方法查看相关文档。除了上面介 绍的属性以外,View动画还有一些常用的属性,如下所示。
android:duration——动画的持续时间;android:fillAfter——动画结束以后View是否停留在结束位置,true表示View停留在结束位置,false则不停留。
下面是一个实际的例子:在anim文件夹下创建xml文件如下,设置代码
Animation的setAnimationListener方法可以给View动画添加过程监听,接口如下所示。从 接口的定义可以很清楚地看出每个方法的含义。
除了系统提供的四种View动画外,我们还可以自定义View动画。自定义动画是一件 既简单又复杂的事情,说它简单,是因为派生一种新动画只需要继承Animation这个抽象 类,然后重写它的initialize和applyTransformation方法,在initialize方法中做一些初始化工 作,在applyTransformation中进行相应的矩阵变换即可,很多时候需要采用Camera来简化 矩阵变换的过程。
帧动画是顺序播放一组预先定义好的图片,类似于电影播放。不同于View动画,系 统提供了另外一个类AnimationDrawable来使用帧动画。
帧动画的使用比较简单,但是比较容易引起OOM,所以在使用帧动画时应尽量避免 使用过多尺寸较大的图片。
在7.1节中我们介绍了View动画的四种形式,除了这四种形式外,View动画还可以在 一些特殊的场景下使用,比如在ViewGroup中可以控制子元素的出场效果,在Activity中可 以实现不同Activity之间的切换效果。
LayoutAnimation作用于ViewGroup,为ViewGroup指定一个动画,这样当它的子元素 出场时都会具有这种动画效果。这种效果常常被用在ListView上,我们时常会看到一种特殊的ListView,它的每个item都以一定的动画的形式出现。
LayoutAnimation也是一个View动画,为了给ViewGroup 的子元素加上出场效果,
(1)定义LayoutAnimation, // res/anim/anim_layout.xml
它的属性的含义如下所示。
android:delay
表示子元素开始动画的时间延迟,比如子元素入场动画的时间周期为300ms,那么0.5 表示每个子元素都需要延迟150ms才能播放入场动画。总体来说,第一个子元素延迟 150ms开始播放入场动画,第2个子元素延迟300ms开始播放入场动画,依次类推。
android:animationOrder
表示子元素动画的顺序,有三种选项:normal、reverse和random,其中normal表示顺
序显示,即排在前面的子元素先开始播放入场动画;reverse表示逆向显示,即排在后面的 子元素先开始播放入场动画;random则是随机播放入场动画。
android:animation
为子元素指定具体的入场动画。
(2)为子元素指定具体的入场动画,如下所示。
(3)为ViewGroup指定android:layoutAnimation属性:android:layoutAnimation= “@anim/ anim_layout”。对于ListView来说,这样ListView的item就具有出场动画了,这种 方式适用于所有的ViewGroup,如下所示。
除了在XML中指定LayoutAnimation外,还可以通过LayoutAnimationController来实 现,具体代码如下所示。
ListView listView = (ListView) layout.findViewById(R.id.list);
Animation animation = AnimationUtils.loadAnimation(this,R.anim.anim_ item);
LayoutAnimationController controller = new LayoutAnimationController(animation);
controller.setDelay(0.5f);
controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
listView.setLayoutAnimation(controller);
Activity有默认的切换效果,但是这个效果我们是可以自定义的,主要用到 overridePendingTransition(int enterAnim,int exitAnim)这个方法,这个方法必须在 startActivity(Intent)或者finish()之后被调用才能生效,它的参数含义如下:
enterAnim——Activity被打开时,所需的动画资源id;
exitAnim——Activity被暂停时,所需的动画资源id。
当启动一个Activity时,可以按照如下方式为其添加自定义的切换效果:
需要注意的是,overridePendingTransition这个方法必须位于startActivity或者finish的后 面,否则动画效果将不起作用。
属性动画可以对任何对象做动画,甚至还可以没有对象。除了作用对象进行了扩展以外,属性动画的效果也得到了加强,不再像View动画那样只能支持四种简单的变换。属性动画中有ValueAnimator、ObjectAnimator和AnimatorSet等概念,通过它们可以实现绚丽的动画。
属性动画的各种参数都比较好理解,在XML中可以定义ValueAnimator、ObjectAnimator以及AnimatorSet,其中标签对应AnimatorSet,标签对应 ValueAnimator,而则对应ObjectAnimator。标签的android:ordering 属性有两个可选值:“together”和“sequentially”,其中“together”表示动画集合中的子动画 同时播放,“sequentially”则表示动画集合中的子动画按照前后顺序依次播放, android:ordering属性的默认值是“together”。
在实际开发中建议采用代码来实现属性动画,这是因为通过代码来实现比较简单。更重要的是,很多时候一个属性的起始值是无法提前确定的,比如让一个Button从屏幕左边移动到屏幕的右边,由于我们无法提前知道屏幕的宽度,因此无法将属性动画定义在 XML中,在这种情况下就必须通过代码来动态地创建属性动画。
TimeInterpolator中文翻译为时间插值器,它的作用是根据时间流逝的百分比来计算出当前属性值改变的百分比,系统预置的有LinearInterpolator(线性插值器:匀速动画)、 AccelerateDecelerateInterpolator(加速减速插值器:动画两头慢中间快)和DecelerateInterpolator(减速插值器:动画越来越慢)等。
属性动画提供了监听器用于监听动画的播放过程,主要有如下两个接口: AnimatorUpdateListener和AnimatorListener。
AnimatorListener :
void onAnimationStart(Animator animation);
void onAnimationEnd(Animator animation);
void onAnimationCancel(Animator animation);
void onAnimationRepeat(Animator animation);
AnimatorUpdateListener:
void onAnimationUpdate(ValueAnimator animation);
从AnimatorListener的定义可以看出,它可以监听动画的开始、结束、取消以及重复播放。同时为了方便开发,系统还提供了AnimatorListenerAdapter这个类,它是AnimatorListener的适配器类,这样我们就可以有选择地实现上面的4个方法了,毕竟不是所有方法 都是我们感兴趣的。
AnimatorUpdateListener比较特殊,它会监听整个动画过程,动画是由许多帧组成的, 每播放一帧,onAnimationUpdate就会被调用一次,利用这个特性,我们可以做一些特殊 的事情。
我们对object的属性abc做动画,如果想让动画生效,要同时满足两个条件:
(1)object必须要提供setAbc方法,如果动画的时候没有传递初始值,那么还要提供getAbc方法,因为系统要去取abc属性的初始值(如果这条不满足,程序直接Crash)。
(2)object的setAbc对属性abc所做的改变必须能够通过某种方法反映出来,比如会带来UI的改变之类的(如果这条不满足,动画无效果但不会Crash)。
以上条件缺一不可。那么为什么我们对Button的width属性做动画会没有效果?这是因 为Button内部虽然提供了getWidth和setWidth方法,但是这个setWidth方法并不是改变视图 的大小,它是TextView新添加的方法,View是没有这个setWidth方法的,由于Button继承 了TextView,所以Button也就有了setWidth方法。
针对上述问题,官方文档上告诉我们有3种解决方法:
给你的对象加上get和set方法,如果你有权限的话; 用一个类来包装原始对象,间接为其提供get和set方法; 采用ValueAnimator,监听动画过程,自己实现属性的改变。
针对上面提出的三种解决方法,下面给出具体的介绍。
1. 给你的对象加上get和set方法,如果你有权限的话
这个的意思很好理解,如果你有权限的话,加上get和set就搞定了。但是很多时候我 们没权限去这么做。比如本文开头所提到的问题,你无法给Button加上一个合乎要求的 setWidth方法,因为这是Android SDK内部实现的。这个方法最简单,但是往往是不可行 的,这里就不对其进行更多的分析了。
2. 用一个类来包装原始对象,间接为其提供get和set方法
这是一个很有用的解决方法,是笔者最喜欢用的,因为用起来很方便,也很好理解, 下面将通过一个具体的例子来介绍它。
通过动画可以实现一些比较绚丽的效果,但是在使用过程中,也需要注意一些事情, 主要分为下面几类。