Android动画浅析1——View动画

动画分类

Android3.0之前提供了两种动画:

  • 补间动画 Tween Animation
  • 帧动画 Frame Animation

Android3.0之后又增加了新的属性动画(Property Animation)。
按照官方分类,现在Android的动画包括:

  • 属性动画 Property Animation
  • View动画 View Animation
    • 补间动画 Tween Animation
    • 帧动画 Frame Animation

因为补间动画和帧动画只能用于View,所以统称为View动画。

补间动画

补间动画主要用于View,实现一些简单的动画效果。
补间动画主要分为四类:

  • AlphaAnimation 渐变透明度
  • RotateAnimation 画面旋转
  • ScaleAnimation 渐变尺寸缩放
  • TranslateAnimation 位置移动

每一种动画都可以通过XML或者代码的形式实现,当然,推荐的是XML形式实现。

简单的代码形式

AlphaAnimation

1
2
3
Animation alphaAnimation = new AlphaAnimation(0.1f, 1.0f);    
alphaAnimation.setDuration(2000);
view.startAnimation(alphaAnimation);

AlphaAnimation构造函数中:
第一个参数表示起始透明度,第二个参数表示结束透明度。

RotateAnimation

1
2
3
Animation rotateAnimation = new RotateAnimation(0f, 360f);
rotateAnimation.setDuration(2000);
view.startAnimation(rotateAnimation);

RotateAnimation构造函数中:
第一个参数fromDegrees表示动画起始时的角度,第二个参数toDegrees表示动画结束时的角度。

ScaleAnimation

1
2
3
Animation scaleAnimation = new ScaleAnimation(0.1f, 1.0f, 0.1f, 1.0f); 
scaleAnimation.setDuration(500);
view.startAnimation(scaleAnimation);

ScaleAnimation构造函数中:
第一个参数fromX,第二个参数toX:分别是动画起始、结束时X坐标上的伸缩尺寸;
第三个参数fromY,第四个参数toY:分别是动画起始、结束时Y坐标上的伸缩尺寸。

TranslateAnimation

1
2
3
Animation translateAnimation = new TranslateAnimation(0.1f, 100.0f, 0.1f, 100.0f);
translateAnimation.setDuration(1000);
view.startAnimation(translateAnimation);

TranslateAnimation构造函数中:
第一个参数fromXDelta,第二个参数toXDelta:分别是动画起始、结束时X坐标;
第三个参数fromYDelta,第四个参数toYDelta:分别是动画起始、结束时Y坐标。

XML形式

XML文件可以用来定义动画,文件放于 res/anim/ 目录下。
根元素包括:

  • <alpha>
  • <scale>
  • <translate>
  • <rotate>
  • <set>

很明显,前四个对应了四种补间动画。
而最后一个 <set> 则是动画集,它可以包含多个其他标签,也可以嵌套 <set> 标签。
使用 <set> 的默认情况下,所有动画会同时播放。
如果想按顺序播放,则需要制定 startOffset 属性。
此外,还可以通过设置 interpolator 控制动画变化的速率,比如匀速、加减速等等。

<alpha>

<alpha> 可以实现透明度渐变的动画效果,也就是淡入淡出的效果,可通过设置下面三个属性来设置淡入或淡出效果:

  • android:duration 动画从开始到结束持续的时长,单位为毫秒
  • android:fromAlpha 动画开始时的透明度,0.0为全透明,1.0为不透明,默认为1.0
  • android:toAlpha 动画结束时的透明度,0.0为全透明,1.0为不透明,默认为1.0

当设置开始时透明度为0.0,结束时为1.0,就能实现淡入效果;
相反,当设置开始时透明度为1.0,结束时为0.0,那就能实现淡出效果。
示例如下:

1
2
3
4
5
6
<!-- res/anim/fade_in.xml -->
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:fromAlpha="0.0"
android:toAlpha="1.0" />

将这动画效果添加到View上也只需要一行代码:

1
view.startAnimation(AnimationUtils.loadAnimation(this, R.anim.fade_in));

如果需要重用这个动画,也可以将其抽离出来,对应的类是AlphaAnimation类:

1
2
AlphaAnimation fadeInAnimation = (AlphaAnimation) AnimationUtils.loadAnimation(this, R.anim.fade_in);
view.startAnimation(fadeInAnimation);

<scale>

<scale> 可以实现缩放的动画效果,主要的属性如下:

  • android:fromXScale 动画开始时X坐标上的缩放尺寸
  • android:toXScale 动画结束时X坐标上的缩放尺寸
  • android:fromYScale 动画开始时Y坐标上的缩放尺寸
  • android:toYScale 动画结束时Y坐标上的缩放尺寸
    (以上四个属性,0.0表示缩放到没有,1.0表示正常无缩放,小于1.0表示收缩,大于1.0表示放大)
  • android:duration 动画从开始到结束持续的时长,单位为毫秒
  • android:pivotX 缩放时的固定不变的X坐标,一般用百分比表示,0%表示左边缘,100%表示右边缘
  • android:pivotY 缩放时的固定不变的Y坐标,一般用百分比表示,0%表示顶部边缘,100%表示底部边缘

示例如下:

1
2
3
4
5
6
7
8
9
10
<!-- res/anim/zoom_out.xml -->
<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:fromXScale="1.0"
android:fromYScale="1.0"
android:pivotX="0%"
android:pivotY="100%"
android:toXScale="1.5"
android:toYScale="1.5" />

<scale> 标签对应的类为ScaleAnimation,添加到View上的用法和AlphaAnimation一样,代码如下:

1
2
ScaleAnimation zoomOutAnimation = (ScaleAnimation) AnimationUtils.loadAnimation(this, R.anim.zoom_out);
view.startAnimation(zoomOutAnimation);

<translate>

<translate> 可以实现位置移动的动画效果,可以是垂直方向的移动,也可以是水平方向的移动。
坐标的值可以有三种格式:

  • 从-100到100,以”%”结束,表示相对于View本身的百分比位置;
  • 如果以”%p”结束,表示相对于View的父View的百分比位置;
  • 如果没有任何后缀,表示相对于View本身具体的像素值。

主要的属性如下:

  • android:duration 动画从开始到结束持续的时长,单位为毫秒
  • android:fromXDelta 起始位置的X坐标的偏移量
  • android:toXDelta 结束位置的X坐标的偏移量
  • android:fromYDelta 起始位置的Y坐标的偏移量
  • android:toYDelta 结束位置的Y坐标的偏移量

以下代码实现的是从左到右的移动效果,起始位置为相对于控件本身-100%的位置,即在控件左边,与控件本身宽度一致的位置;结束位置为相对于父控件100%的位置,即会移出父控件右边缘的位置:

1
2
3
4
5
6
7
8
<!-- res/anim/move_left_to_right.xml -->
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2000"
android:fromXDelta="-100%"
android:fromYDelta="0"
android:toXDelta="100%p"
android:toYDelta="0" />

<translate> 标签对应的类为TranslateAnimation:

1
2
TranslateAnimation moveAnimation = (TranslateAnimation) AnimationUtils.loadAnimation(this, R.anim.move_left_to_right);
view.startAnimation(moveAnimation);

<rotate>

<rotate> 可以实现旋转的动画效果,主要的属性如下:

  • android:duration 动画从开始到结束持续的时长,单位为毫秒
  • android:fromDegrees 旋转开始的角度
  • android:toDegrees 旋转结束的角度
  • android:pivotX 旋转中心点的X坐标,纯数字表示相对于View本身左边缘的像素偏移量;带”%”后缀时表示相对于View本身左边缘的百分比偏移量;带”%p”后缀时表示相对于父View左边缘的百分比偏移量
  • android:pivotY 旋转中心点的Y坐标,纯数字表示相对于View本身顶部边缘的像素偏移量;带”%”后缀时表示相对于View本身顶部边缘的百分比偏移量;带”%p”后缀时表示相对于父View顶部边缘的百分比偏移量

以下示例代码旋转角度从0到360,即旋转了一圈,旋转的中心点都设为了50%,即是View本身中点的位置:

1
2
3
4
5
6
7
8
<!-- res/anim/rotate_one.xml -->
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2000"
android:fromDegrees="0"
android:toDegrees="360"
android:pivotX="50%"
android:pivotY="50%" />

<rotate> 标签对应的类为RotateAnimation:

1
2
RotateAnimation rotateAnimation = (RotateAnimation) AnimationUtils.loadAnimation(this, R.anim.rotate_one);
view.startAnimation(rotateAnimation);

<set>

<set> 标签可以将多个动画组合起来,变成一个动画集。
比如想将一张图片缩放的同时也做移动,这时候就要用<set>标签组合缩放动画和移动动画了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- res/anim/move_and_scale.xml -->
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2000">
<translate
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="200%"
android:toYDelta="0" />
<scale
android:fromXScale="1.0"
android:fromYScale="1.0"
android:pivotX="0%"
android:pivotY="100%"
android:toXScale="1.5"
android:toYScale="1.5" />
</set>

以上代码实现的动画效果为向右移动的同时也同步放大。
<set> 标签在视图动画中除了可以组合<alpha>, <scale>, <translate>, <rotate>这四种标签,也可以嵌套其他<set>标签。
另外,<set> 标签可嵌套的标签元素并不只有这几个,后面谈到属性动画时会再讲其他的标签及用法。

通用属性

除了android:duration,补间动画还有很多通用属性:

  • android:duration 动画从开始到结束持续的时长,单位为毫秒
  • android:detachWallpaper 设置是否在壁纸上运行,只对设置了壁纸背景的窗口动画(window animation)有效。设为true,则动画只在窗口运行,壁纸背景保持不变
  • android:fillAfter 设置为true时,动画执行完后,View会停留在动画的最后一帧;默认为false;如果是动画集,需在标签中设置该属性才有效
  • android:fillBefore 设置为true时,动画执行完后,View回到动画执行前的状态,默认即为true
  • android:fillEnabled 设置为true时,android:fillBefore的值才有效,否则android:fillBefore会被忽略
  • android:repeatCount 设置动画重复执行的次数,默认为0,即不重复;可设为-1或infinite,表示无限重复
  • android:repeatMode 设置动画重复执行的模式,可设为以下两个值其中之一:
    • restart 动画重复执行时从起点开始,默认为该值
    • reverse 动画会反方向执行
  • android:startOffset 设置动画执行之前的等待时长,毫秒为单位;重复执行时,每次执行前同样也会等待一段时间
  • android:zAdjustment 表示被设置动画的内容在动画运行时在Z轴上的位置,取值为以下三个值之一:
    • normal 默认值,保持内容在Z轴上的位置不变
    • top 保持在Z周最上层
    • bottom 保持在Z轴最下层
  • android:interpolator 设置动画速率的变化,比如加速、减速、匀速等,需要指定Interpolator资源
  • <set>标签还有个android:shareInterpolator属性,设置为true时则可将interpolator应用到所有子元素中

Interpolator

通过interpolator可以定义动画速率变化的方式,比如加速、减速、匀速等。
下面列出了Android本身提供的各类Interpolator。

  • AccelerateDecelerateInterpolator
    @android:anim/accelerate_decelerate_interpolator
    在动画开始与结束时速率改变比较慢,在中间的时候加速
  • AccelerateInterpolator
    @android:anim/accelerate_interpolator
    在动画开始时速率改变比较慢,然后开始加速
  • AnticipateInterpolator
    @android:anim/anticipate_interpolator
    动画开始的时候向后然后往前抛
  • AnticipateOvershootInterpolator
    @android:anim/anticipate_overshoot_interpolator
    动画开始的时候向后然后向前抛,会抛超过目标值后再返回到最后的值
  • BounceInterpolator
    @android:anim/bounce_interpolator
    动画结束的时候会弹跳
  • CycleInterpolator
    @android:anim/bounce_interpolator
    动画循环做周期运动,速率改变沿着正弦曲线
  • DecelerateInterpolator
    @android:anim/decelerate_interpolator
    在动画开始时速率改变比较快,然后开始减速
  • LinearInterpolator
    @android:anim/decelerate_interpolator
    动画匀速播放
  • OvershootInterpolator
    @android:anim/overshoot_interpolator
    动画向前抛,会抛超过最后值,然后再返回
自定义Interpolator

如果系统提供的以上Interpolator还不符合你的效果,也可以自定义。
自定义的方式有两种:

  • 一种是通过继承 Interpolator 父类或其子类;
  • 另一种是通过自定义的xml文件,可以更改上表中Interpolator的属性

自定义的xml文件需存放于res/anim/目录下,根标签与上面列出的Interpolator相对应。
注意,<accelerateDecelerateInterpolator><bounceInterpolator><linearInterpolator>没有可变的属性。

  • <accelerateInterpolator>
    • android:factor 浮点值,加速的速率,默认为1
  • <anticipateInterpolator>
    • android:tension 浮点值,向后的拉力,默认为2,当设为0时,则不会有向后的动画了
  • <anticipateOvershootInterpolator>
    • android:tension 浮点值,向后的拉力,默认为2,当设为0时,则不会有向后的动画了
    • android:extraTension 浮点值,拉力的倍数,默认为1.5(2*1.5),当设为0时,则不会有拉力了
  • <cycleInterpolator>
    • android:cycles 整数值,循环的次数,默认为1
  • <decelerateInterpolator>
    • android:factor 浮点值,减速的速率,默认为1
  • <overshootInterpolator>
    • android:tension 浮点值,超出终点后的拉力,默认为2

举例如下:

1
2
3
4
5
6
<!-- res/anim/my_interpolator.xml -->
<?xml version="1.0" encoding="utf-8"?>
<anticipateOvershootInterpolator
xmlns:android="http://schemas.android.com/apk/res/android"
android:tension="3"
android:extraTension="2" />

接着,将其设置到要应用的动画的android:interpolator属性即可,代码如下:

1
2
3
4
5
6
7
8
9
<!-- res/anim/rotate_one.xml -->
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2000"
android:fromDegrees="0"
android:toDegrees="360"
android:pivotX="50%"
android:pivotY="50%"
android:interpolator="@anim/my_interpolator" />

帧动画

帧动画的原理是在“连续的关键帧”中分解动画动作,也就是在时间轴的每帧上逐帧绘制不同的内容,使其连续播放而成动画。
在Android中逐帧动画需要得到AnimationDrawable类的支持。
它主要用来创建一个逐帧动画,并且可以对帧进行拉伸,把它设置为View的背景即可使用AnimationDrawable.start()方法播放。
同样,创建帧动画的形式有XML定义的资源文件和Java代码创建两种。
AnimationDrawable的主要方法包括:

1
2
3
4
5
6
7
8
9
void start():开始播放逐帧动画。
void stop():停止播放逐帧动画。
void addFrame(Drawable frame,int duration):为AnimationDrawable添加一帧,并设置持续时间。
int getDuration(int i):得到指定index的帧的持续时间。
Drawable getFrame(int index):得到指定index的帧Drawable。
int getNumberOfFrames():得到当前AnimationDrawable的所有帧数量。
boolean isOneShot():当前AnimationDrawable是否执行一次,返回true执行一次,false循环播放。
boolean isRunning():当前AnimationDrawable是否正在播放。
void setOneShot(boolean oneShot):设置AnimationDrawable是否执行一次,true执行一次,false循环播放

XML形式

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false" >
<!-- 定义一个动画帧,Drawable为img0,持续时间50毫秒 -->
<item android:drawable="@drawable/img0" android:duration="50" />
<item android:drawable="@drawable/img1" android:duration="50" />
<item android:drawable="@drawable/img2" android:duration="50" />
<item android:drawable="@drawable/img3" android:duration="50" />
<item android:drawable="@drawable/img4" android:duration="50" />
<item android:drawable="@drawable/img5" android:duration="50" />
<item android:drawable="@drawable/img6" android:duration="50" />
</animation-list>

再在java代码中获得该动画:

1
2
3
4
5
6
// 通过逐帧动画的资源文件获得AnimationDrawable示例
AnimationDrawable frameAnim = (AnimationDrawable) getResources().getDrawable(R.drawable.bullet_anim);
// 把AnimationDrawable设置为ImageView的背景
imageView.setBackgroundDrawable(frameAnim);
// 开始动画
frameAnim.start();

或者也可以:

1
2
3
4
5
6
7
// 将动画设为imageView的背景
imageView.setBackgroundResource(R.drawable.drawable_anim);
// 获取AnimationDrawable
anim = (AnimationDrawable) imageView.getBackground();
anim.stop();
// 开始动画
anim.start();

在Android中,除了可以通过XML文件定义一个逐帧动画之外,还可以通过AnimationDrawable.addFrame()方法为AnimationDrawable添加动画帧。

注意点

使用帧动画有这么几个注意点:

  • 要在代码中调用ImageView的setBackgroundDrawable,不能直接在XML布局文件中设置其src属性
  • 在动画start()之前要先stop(),不然在第一次动画之后会停在最后一帧,这样动画就只会触发一次
  • 不要在onCreate中调用start,因为AnimationDrawable还没有完全跟Window相关联,如果想要界面显示时就开始动画的话,可以在onWindowFoucsChanged()中调用start()