Android动画——基础篇

本文结构:

Android动画

1.种类

Android的动画可以分为三种:View动画、帧动画和属性动画;

  1. View动画通过对场景里的对象不断做图像变化(平移、缩放、旋转、渐变),从而产生动画效果,它是一种渐近式动画,并且View动画支持自定义;
  2. 帧动画通过顺序播放一系列图像从而达到动画效果,可以理解为图片切换动画
  3. 属性动画通过动态地改变对象的属性从而达到动画效果,属性动画为API11的新特性。

2.View动画

View动画的四种变换如下表所示:

名称 标签 子类
平移动画 <translate> TranslateAnimation
缩放动画 <scale> ScaleAnimation
旋转动画 <rotate> RotateAnimation
透明度动画 <alpha> AlphaAnimation

上述的四种动画既可以通过XML来定义,也可以通过代码来动态创建。

2.1 在XML中定义动画

动画xml文件定义在:res/anim/目录下,一些常用语法及属性解释如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?xml version="1.0" encoding="utf-8"?>
<!--set:interpolator:动画采用的插值器
shareInterpolator:集合中的动画是否和集合共享同一个插值器-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:shareInterpolator="true">
<!--平移:fromXDelta、toXDelta:水平方向移动的起始值和结束值
fromYDelta、toYDelta:竖直方向移动的起始值和结束值-->
<translate
android:duration="2000"
android:fromXDelta="0"
android:toXDelta="100%"
android:fromYDelta="0"
android:toYDelta="100%"></translate>
<!--缩放:fromXScale、toXScale:水平方向缩放的起始值和结束值
fromYScale、toYScale:竖直方向缩放的起始值和结束值
pivotX、pivotY:轴点的位置-->
<scale
android:duration="2000"
android:fromXScale="1"
android:toXScale="0.5"
android:fromYScale="1"
android:toYScale="0.5"
android:pivotX="50%"
android:pivotY="50%"></scale>
<!--旋转 fromDegrees:旋转的起始值;toDegrees:旋转的结束值
pivotX、pivotY:轴点的位置
fillAfter:动画结束后View是否停留在结束位置-->
<rotate
android:duration="2000"
android:fromDegrees="0"
android:toDegrees="360"
android:pivotX="50%"
android:pivotY="50%"
android:fillAfter="true"></rotate>

<!--渐变 fromAlpha:渐变的起始值,toAlpha:渐变的结束值-->
<alpha
android:duration="2000"
android:fromAlpha="1.0"
android:toAlpha="0"
></alpha>
</set>

<set>标签表示动画集合,对应AnimationSet类,它可以包含若干个动画,并且它的内部也是可以嵌套其他动画集合。

动画定义如上所示,如何应用上面的动画呢,如下所示:

1
2
3
Button button = (Button)findViewById(R.id.button);
Animation animation = AnimationUtils.loadAnimation(this, R.anim.view_anim);
button.startAnimation(animation)

2.2 在Java中定义动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
public class AnimationTest {

//平移动画
public void clickToTranslate(View targetView){
TranslateAnimation translateAnimation = new TranslateAnimation(
Animation.RELATIVE_TO_SELF, 0,
Animation.RELATIVE_TO_SELF, 1,
Animation.RELATIVE_TO_SELF, 0,
Animation.RELATIVE_TO_SELF, 1);
translateAnimation.setDuration(2000);
targetView.startAnimation(translateAnimation);
}

//缩放动画
public void clickToScale(View targetView){
ScaleAnimation scaleAnimation = new ScaleAnimation(
1, 0.5f,
1, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
scaleAnimation.setDuration(2000);
targetView.startAnimation(scaleAnimation);
}

//旋转动画
public void clickToRotate(View targetView){
RotateAnimation rotateAnimation = new RotateAnimation(
0, 360,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5F
);
rotateAnimation.setDuration(2000);
targetView.startAnimation(rotateAnimation);
}

//渐变动画
public void clickToAlpha(View targetView){
AlphaAnimation alphaAnimation = new AlphaAnimation(
1, 0.5f
);
alphaAnimation.setDuration(2000);
targetView.startAnimation(alphaAnimation);
}

//组合动画
public void clickToSet(View targetView){
AlphaAnimation alphaAnimation = new AlphaAnimation(
1, 0.5f
);
alphaAnimation.setDuration(2000);

RotateAnimation rotateAnimation = new RotateAnimation(
0, 360,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5F
);
rotateAnimation.setDuration(2000);

ScaleAnimation scaleAnimation = new ScaleAnimation(
1, 0.5f,
1, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
scaleAnimation.setDuration(2000);

TranslateAnimation translateAnimation = new TranslateAnimation(
Animation.RELATIVE_TO_SELF, 0,
Animation.RELATIVE_TO_SELF, 1,
Animation.RELATIVE_TO_SELF, 0,
Animation.RELATIVE_TO_SELF, 1);
translateAnimation.setDuration(2000);

AnimationSet animationSet = new AnimationSet(true);
animationSet.addAnimation(alphaAnimation);
animationSet.addAnimation(translateAnimation);
animationSet.addAnimation(rotateAnimation);
animationSet.addAnimation(scaleAnimation);
//设置动画过程监听
animationSet.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {

}

@Override
public void onAnimationEnd(Animation animation) {

}

@Override
public void onAnimationRepeat(Animation animation) {

}
});
targetView.startAnimation(animationSet);
}

//帧动画
public void clickToAnimationList(View targetView){
targetView.setBackgroundResource(R.drawable.fram_animation_list);
AnimationDrawable drawable = (AnimationDrawable) targetView.getBackground();
drawable.start();
}
}

View的基础使用就如上所述,当然除了系统提供的这四种View动画之外,我们还可以自定义View动画。这里简单说下它的用法:派生一个新动画需要继承Animation抽象类,然后重写它的initialize和applyTransformation方法,在initialize方法中做一些初始化工作,在applyTransformation中进行相应的矩形变换。

3. 帧动画

帧动画就是顺序播放一组预先设定好的图片,系统提供了AnimationDrawable类来使用帧动画,系统提供的标签有:

1
<animation-list><animation-rotate><animation-selector><animation-vector>

帧动画的使用比较简单,以<animation-list>标签为例使用如下:

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_launcher_background" android:duration="200"></item>
<item android:drawable="@drawable/ic_launcher_background" android:duration="200"></item>
<item android:drawable="@drawable/ic_launcher_background" android:duration="200"></item>
<item android:drawable="@drawable/ic_launcher_background" android:duration="200"></item>
</animation-list>

然后将上述Drawable作为view的背景并通过Drawable来播放即可:

1
2
3
4
5
6
//帧动画
public void clickToAnimationList(View targetView){
targetView.setBackgroundResource(R.drawable.fram_animation_list);
AnimationDrawable drawable = (AnimationDrawable) targetView.getBackground();
drawable.start();
}

需要注意的是帧动画比较容易引起内存溢出OOM,所以在使用帧动画时应尽量避免使用过多尺寸较大的图片

4. 属性动画

属性动画是对任意对象的属性进行动画而不仅仅是View,因此所有补间动画的内容都可以通过属性动画实现。

4.1 用法

先来看一下属性动画中常用的几个类和对应标签:

标签 子类
<set> AnimatorSet
<animator>` ValueAnimator
<objectAnimator> ObjectAnimator

4.1.1 在XML中定义动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="together">
<objectAnimator
android:propertyName="x"
android:duration="3000"
android:valueTo="200"
android:valueType="intType"/>
<objectAnimator
android:propertyName="y"
android:duration="3000"
android:valueTo="300"
android:valueType="intType"/>
</set>

如何使用上面的属性动画呢,如下所示:

1
2
3
4
5
private void animatorFroResourse(Context context, View view){
AnimatorSet animatorSet = (AnimatorSet) AnimatorInflater.loadAnimator(context, R.animator.property_anim);
animatorSet.setTarget(view);
animatorSet.start();
}

4.1.2 在Java中定义动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* 属性动画
*/
public class PropertyAnimation {

//旋转
private void rtateAnimation(View view){
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view, "rotation",
0f, 360f);
objectAnimator.setDuration(2000);
objectAnimator.start();
}

//渐变
private void alphaAnimation(View view){
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view, "alpha",
1.0f, 0.8f, 0.6f, 0.4f, 0.2f, 0f);
//setRepeatCount:设置动画的重复次数(-1表示无限重复)
objectAnimator.setRepeatCount(-1);
//setRepeatMode:设置动画的重复模式(reverse:逆向重复,restart:连续重复)
objectAnimator.setRepeatMode(ObjectAnimator.REVERSE);
objectAnimator.setDuration(2000);
objectAnimator.start();
}

//组合
private void setAnimation(View view){
ObjectAnimator alpha = ObjectAnimator.ofFloat(view, "alpha",
1.0f, 0.8f, 0.6f, 0.4f, 0.2f, 0f);
ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 0.0f, 1.0f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 0.0f, 1.0f);
ObjectAnimator rotation = ObjectAnimator.ofFloat(view, "rotation", 0f, 360f);
ObjectAnimator translateX = ObjectAnimator.ofFloat(view, "translationX", 100, 400);
ObjectAnimator translateY = ObjectAnimator.ofFloat(view, "translationY", 100, 750);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(scaleX, scaleY, rotation, translateX, translateY);
animatorSet.setDuration(2000);
animatorSet.start();
}
}

4.2 工作原理

属性动画要求动画作用的对象提供该属性的get和set方法,属性动画根据外界传递的该属性的初始值和最终值,以动画的效果多次去调用set方法,每次传递给set方法的值都不一样,确切来说是随着时间的推移,所传递的值越来越接近最终值。

属性动画的运行机制是通过不断地对值进行操作来实现的。而初始值和结束值之间的动画过渡则由ValueAnimator这个类来负责计算的。它的内部使用一种时间循环的机制来计算值与值之间的动画过渡,我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长,那么ValueAnimator就会自动帮我们完成从初始值平滑的过渡到结束值这样的效果。

ValueAnimator是怎么样实现从初始值平滑过渡到结束值的呢,这个就是由类型估值器TypeEvaluator和时间插值器TimeInterpolator共同决定的。

4.2.1 插值器和估值器

  1. TimeInterpolator(时间插值器),它的作用是根据时间流逝的百分比来计算出当前属性值改变的百分比,它决定了动画从初始值过渡到结束值的节奏,系统预置的有LinearInterpolator(线性插值器:匀速动画)、AccelerateDecelerateInterpolator(加速减速插值器:动画两头慢中间快)和DecelerateInterpolator(减速插值器:动画越来越慢)
  2. TypeInterpolator(类型估值器),它的作用是根据当前属性改变的百分比来计算改变后的属性值,它决定了动画如何从初始值过渡到结束值,系统预置的有IntEvaluator(针对整型属性)、FloatEvaluator(针对浮点型属性)和ArgbEvaluator(针对Color属性)

对于属性动画也可以自定义方式实现所需效果。

5. 使用动画的注意实现

  1. OOM问题:这个问题主要出现在帧动画中,当图片数量较多且图片较大时就极易出现OOM。
  2. 内存泄漏:在属性动画中有一类无限循环的动画,这类动画需要在Activity退出时及时停止,否则将导致Activity无法释放从而导致内存泄漏;而View动画并不存在此问题
  3. 兼容性问题:动画在3.0以下的系统上有兼容性问题
  4. View动画的问题:View动画是对View的影像做动画,并不是真正的改变View的状态,因此有时候会出现动画完成后View无法隐藏的现象,即setVisibility(View.Gone)失效了,这个时候只要调用view.clearAnimation()清除View动画即可解决问题
  5. 不要使用px:尽量使用dp
  6. 动画元素的交互:将view移动后,在Android3.0以前,不管是View动画还是属性动画,新位置均无法触发单击事件,同时老位置仍可触发单击事件;在Android3.0以后,属性动画的单击事件触发位置为移动后的位置,但是View动画仍在老位置。
  7. 硬件加速