简介

官方文档:http://developer.android.com/guide/topics/graphics/index.html

  • Android 3.0 之前,支持两种动画模式 Tween Animation Frame Animation
  • Android 3.0 之后,又引入了一个新的动画系统:Property Animation
  • 这三种动画模式在新的 SDK 中被称为
    • View Animation
    • Drawable Animation
    • Property Animation
      • 可通过 NineOldAndroids 项目在 3.0 之前的系统中使用 Property Animation

View Animation(Tween Animation)

补间动画,给出两个关键帧,通过一些算法将给定属性值在给定的时间内在两个关键帧间渐变。

View animation 只能应用于 View 对象,而且只支持一部分属性,如支持缩放旋转而不支持背景颜色的改变。

而且对于 View animation,它只是改变了 View 对象绘制的位置,而没有改变 View 对象本身,比如,你有一个 Button,坐标(100,100),Width:200,Height:50,而你有一个动画使其变为 Width:100,Height:100,你会发现动画过程中触发按钮点击的区域仍是(100,100)-(300,150)。

View Animation 就是一系列 View 形状的变换,如大小的缩放,透明度的改变,位置的改变,动画的定义既可以用代码定义也可以用 XML 定义,当然,建议用 XML 定义。

可以给一个 View 同时设置多个动画,比如从透明至不透明的淡入效果,与从小到大的放大效果,这些动画可以同时进行,也可以在一个完成之后开始另一个。

用 XML 定义的动画放在/res/anim/文件夹内,XML 文件的根元素可以为,,,,interpolator 元素或(表示以上几个动画的集合,set 可以嵌套)。默认情况下,所有动画是同时进行的,可以通过 startOffset 属性设置各个动画的开始偏移(开始时间)来达到动画顺序播放的效果。

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
<set android:shareInterpolator="false">
<scale
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:fromXScale="1.0"
android:toXScale="1.4"
android:fromYScale="1.0"
android:toYScale="0.6"
android:pivotX="50%"
android:pivotY="50%"
android:fillAfter="false"
android:duration="700" />
<set android:interpolator="@android:anim/decelerate_interpolator">
<scale
android:fromXScale="1.4"
android:toXScale="0.0"
android:fromYScale="0.6"
android:toYScale="0.0"
android:pivotX="50%"
android:pivotY="50%"
android:startOffset="700"
android:duration="400"
android:fillBefore="false" />
<rotate
android:fromDegrees="0"
android:toDegrees="-45"
android:toYScale="0.0"
android:pivotX="50%"
android:pivotY="50%"
android:startOffset="700"
android:duration="400" />
</set>
</set>

可以通过设置 interpolator 属性改变动画渐变的方式,如 AccelerateInterpolator,开始时慢,然后逐渐加快。默认为 AccelerateDecelerateInterpolator。

定义好动画的 XML 文件后,可以通过类似下面的代码对指定 View 应用动画。

1
2
3
ImageView spaceshipImage = (ImageView)findViewById(R.id.spaceshipImage);
Animation hyperspaceJumpAnimation=AnimationUtils.loadAnimation(this, R.anim.hyperspace_jump);
spaceshipImage.startAnimation(hyperspaceJumpAnimation);

Drawable Animation(Frame Animation)

帧动画,就像 GIF 图片,通过一系列 Drawable 依次显示来模拟动画的效果。在 XML 中的定义方式如下:

1
2
3
4
5
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="true">
<item android:drawable="@drawable/rocket_thrust1" android:duration="200" />
<item android:drawable="@drawable/rocket_thrust2" android:duration="200" />
<item android:drawable="@drawable/rocket_thrust3" android:duration="200" />
</animation-list>

必须以为根元素,以表示要轮换显示的图片,duration 属性表示各项显示的时间。XML 文件要放在/res/drawable/目录下。示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
imageView = (ImageView) findViewById(R.id.imageView1);
imageView.setBackgroundResource(R.drawable.drawable_anim);
anim = (AnimationDrawable) imageView.getBackground();
}

public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
anim.stop();
anim.start();
return true;
}
return super.onTouchEvent(event);
}

我在实验中遇到两点问题:

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

Property Animation

属性动画,这个是在 Android 3.0 中才引进的,以前学 WPF 时里面的动画机制好像就是这个,它更改的是对象的实际属性,在 View Animation(Tween Animation)中,其改变的是 View 的绘制效果,真正的 View 的属性保持不变,比如无论你在对话中如何缩放 Button 的大小,Button 的有效点击区域还是没有应用动画时的区域,其位置与大小都不变。而在 Property Animation 中,改变的是对象的实际属性,如 Button 的缩放,Button 的位置与大小属性值都改变了。而且 Property Animation 不止可以应用于 View,还可以应用于任何对象。Property Animation 只是表示一个值在一段时间内的改变,当值改变时要做什么事情完全是你自己决定的。

在 Property Animation 中,可以对动画应用以下属性:

  • Duration:动画的持续时间
  • TimeInterpolation:属性值的计算方式,如先快后慢
  • TypeEvaluator:根据属性的开始、结束值与 TimeInterpolation 计算出的因子计算出当前时间的属性值
  • Repeat Count and behavoir:重复次数与方式,如播放 3 次、5 次、无限循环,可以此动画一直重复,或播放完时再反向播放
  • Animation sets:动画集合,即可以同时对一个对象应用几个动画,这些动画可以同时播放也可以对不同动画设置不同开始偏移
  • Frame refreash delay:多少时间刷新一次,即每隔多少时间计算一次属性值,默认为 10ms,最终刷新时间还受系统进程调度与硬件的影响

使用 ValueAnimator

ValueAnimator 用来设置一些确定的值,int,float,或者 color values,你可以调用 ValueAnimator 的工厂方法:ofInt(), ofFloat(), or ofObject()。比如:

1
2
3
ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f);
animation.setDuration(1000);
animation.start();

也可以自定义类型,比如:

1
2
3
ValueAnimator animation = ValueAnimator.ofObject(new MyTypeEvaluator(), startPropertyValue, endPropertyValue);
animation.setDuration(1000);
animation.start();

应用 Property Animation 有两个步聚:

  • 计算属性值
  • 根据属性值执行相应的动作,如改变对象的某一属性。

ValuAnimiator 只完成了第一步工作,如果要完成第二步,需要实现 ValueAnimator.onUpdateListener 接口,这个接口只有一个函数 onAnimationUpdate(),在这个函数中会传入 ValueAnimator 对象做为参数,通过这个 ValueAnimator 对象的 getAnimatedValue()函数可以得到当前的属性值如:

1
2
3
4
5
6
7
8
9
10
11
ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f);
animation.setDuration(1000);
animation.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 此示例中只是向Logcat输出了一些信息,可以改为想做的工作。
Log.i("update", ((Float) animation.getAnimatedValue()).toString());
}
});
animation.setInterpolator(new CycleInterpolator(3));
animation.start();

使用 ObjectAnimator

继承自 ValueAnimator,要指定一个对象及该对象的一个属性,当属性值计算完成时自动设置为该对象的相应属性,即完成了 Property Animation 的全部两步操作。实际应用中一般都会用 ObjectAnimator 来改变某一对象的某一属性,但用 ObjectAnimator 有一定的限制,要想使用 ObjectAnimator,应该满足以下条件:

  • 对象应该有一个 setter 函数:set(驼峰命名法)
  • 如上面的例子中,像 ofFloat 之类的工场方法,第一个参数为对象名,第二个为属性名,后面的参数为可变参数,如果 values…参数只设置了一个值的话,那么会假定为目的值,属性值的变化范围为当前值到目的值,为了获得当前值,该对象要有相应属性的 getter 方法:get
  • 如果有 getter 方法,其应返回值类型应与相应的 setter 方法的参数类型一致。
  • 如果上述条件不满足,则不能用 ObjectAnimator,应用 ValueAnimator 代替。
1
2
3
ObjectAnimator anim = ObjectAnimator.ofFloat(foo, "alpha", 0f, 1f);
anim.setDuration(1000);
anim.start();

通过 AnimationSet 应用多个动画

AnimationSet 提供了一个把多个动画组合成一个组合的机制,并可设置组中动画的时序关系,如同时播放,顺序播放等。

以下例子同时应用 5 个动画:

  • 播放 anim1;
  • 同时播放 anim2,anim3,anim4;
  • 播放 anim5。
1
2
3
4
5
6
AnimatorSet bouncer = new AnimatorSet();
bouncer.play(anim1).before(anim2);
bouncer.play(anim2).with(anim3);
bouncer.play(anim2).with(anim4)
bouncer.play(anim5).after(amin2);
animatorSet.start();

TypeEvalutors

根据属性的开始、结束值与 TimeInterpolation 计算出的因子计算出当前时间的属性值,android 提供了以下几个 evalutor:

  • IntEvaluator:属性的值类型为 int;
  • FloatEvaluator:属性的值类型为 float;
  • ArgbEvaluator:属性的值类型为十六进制颜色值;
  • TypeEvaluator:一个接口,可以通过实现该接口自定义 Evaluator。

自定义 TypeEvalutor 很简单,只需要实现一个方法,如 FloatEvalutor 的定义:

1
2
3
4
5
6
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);
}
}

TimeInterplator

Time interplator 定义了属性值变化的方式,如线性均匀改变,开始慢然后逐渐快等。在 Property Animation 中是 TimeInterplator,在 View Animation 中是 Interplator,这两个是一样的,在 3.0 之前只有 Interplator,3.0 之后实现代码转移至了 TimeInterplator。Interplator 继承自 TimeInterplator,内部没有任何其他代码。

  • AccelerateInterpolator 加速,开始时慢中间加速
  • DecelerateInterpolator 减速,开始时快然后减速
  • AccelerateDecelerateInterolator 先加速后减速,开始结束时慢,中间加速
  • AnticipateInterpolator 反向 ,先向相反方向改变一段再加速播放
  • AnticipateOvershootInterpolator 反向加回弹,先向相反方向改变,再加速播放,会超出目的值然后缓慢移动至目的值
  • BounceInterpolator 跳跃,快到目的值时值会跳跃,如目的值 100,后面的值可能依次为 85,77,70,80,90,100
  • CycleIinterpolator 循环,动画循环一定次数,值的改变为一正弦函数:Math.sin(2 _ mCycles _ Math.PI * input)
  • LinearInterpolator 线性,线性均匀改变
  • OvershottInterpolator 回弹,最后超出目的值然后缓慢改变到目的值
  • TimeInterpolator 一个接口,允许你自定义 interpolator,以上几个都是实现了这个接口

当 Layout 改变时应用动画

ViewGroup 中的子元素可以通过 setVisibility 使其 Visible、Invisible 或 Gone,当有子元素可见性改变时(VISIBLE、GONE),可以向其应用动画,通过 LayoutTransition 类应用此类动画:

1
transition.setAnimator(LayoutTransition.DISAPPEARING, customDisappearingAnim);

通过 setAnimator 应用动画,第一个参数表示应用的情境,可以以下 4 种类型:

APPEARING 当一个元素在其父元素中变为 Visible 时对这个元素应用动画
CHANGE_APPEARING 当一个元素在其父元素中变为 Visible 时,因系统要重新布局有一些元素需要移动,对这些要移动的元素应用动画
DISAPPEARING 当一个元素在其父元素中变为 GONE 时对其应用动画
CHANGE_DISAPPEARING 当一个元素在其父元素中变为 GONE 时,因系统要重新布局有一些元素需要移动,这些要移动的元素应用动画.
第二个参数为一 Animator。

1
mTransitioner.setStagger(LayoutTransition.CHANGE_APPEARING, 30);

此函数设置动画延迟时间,参数分别为类型与时间。

1
2
3
4
5
6
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:id="@+id/verticalContainer"
android:animateLayoutChanges="true" />

Keyframes

keyFrame 是一个 时间/值 对,通过它可以定义一个在特定时间的特定状态,即关键帧,而且在两个 keyFrame 之间可以定义不同的 Interpolator,就好像多个动画的拼接,第一个动画的结束点是第二个动画的开始点。KeyFrame 是抽象类,要通过 ofInt(),ofFloat(),ofObject()获得适当的 KeyFrame,然后通过 PropertyValuesHolder.ofKeyframe 获得 PropertyValuesHolder 对象,如以下例子:

1
2
3
4
5
6
7
8
Keyframe kf0 = Keyframe.ofInt(0, 400);
Keyframe kf1 = Keyframe.ofInt(0.25f, 200);
Keyframe kf2 = Keyframe.ofInt(0.5f, 400);
Keyframe kf4 = Keyframe.ofInt(0.75f, 100);
Keyframe kf3 = Keyframe.ofInt(1f, 500);
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("width", kf0, kf1, kf2, kf4, kf3);
ObjectAnimator rotationAnim = ObjectAnimator.ofPropertyValuesHolder(btn2, pvhRotation);
rotationAnim.setDuration(2000);

上述代码的意思为:设置 btn 对象的 width 属性值使其:

  • 开始时 Width=400
  • 动画开始 1/4 时 Width=200
  • 动画开始 1/2 时 Width=400
  • 动画开始 3/4 时 Width=100
  • 动画结束时 Width=500

第一个参数为时间百分比,第二个参数是在第一个参数的时间时的属性值。
定义了一些 Keyframe 后,通过 PropertyValuesHolder 类的方法 ofKeyframe 一个 PropertyValuesHolder 对象,然后通过 ObjectAnimator.ofPropertyValuesHolder 获得一个 Animator 对象。

Animating Views

在 View Animation 中,对 View 应用 Animation 并没有改变 View 的属性,动画的实现是通过其 Parent View 实现的,在 View 被 drawn 时 Parents View 改变它的绘制参数,draw 后再改变参数 invalidate,这样虽然 View 的大小或旋转角度等改变了,但 View 的实际属性没变,所以有效区域还是应用动画之前的区域,比如你把一按钮放大两倍,但还是放大这前的区域可以触发点击事件。为了改变这一点,在 Android 3.0 中给 View 增加了一些参数并对这些参数增加了相应的 getter/setter 函数(ObjectAnimator 要用这些函数改变这些属性):

translationX,translationY: View 相对于原始位置的偏移量
rotation,rotationX,rotationY: 旋转,rotation 用于 2D 旋转角度,3D 中用到后两个
scaleX,scaleY: 缩放比
x,y: View 的最终坐标,是 View 的 left,top 位置加上 translationX,translationY
alpha: 透明度

跟位置有关的参数有 3 个,以 X 坐标为例,可以通过 getLeft(),getX(),getTranslateX()获得,若有一 Button btn2,布局时其坐标为(40,0):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//应用动画之前
btn2.getLeft(); //40
btn2.getX(); //40
btn2.getTranslationX(); //0
//应用translationX动画
ObjectAnimator oa=ObjectAnimator.ofFloat(btn2,"translationX", 200);
oa.setDuration(2000);
oa.start();
/*应用translationX动画后
btn2.getLeft(); //40
btn2.getX(); //240
btn2.getTranslationX(); //200
*/
//应用X动画,假设没有应用之前的translationX动画
ObjectAnimator oa=ObjectAnimator.ofFloat(btn2, "x", 200);
oa.setDuration(2000);
oa.start();
/*应用X动画后
btn2.getLeft(); //40
btn2.getX(); //200
btn2.getTranslationX(); //160
*/

1.无论怎样应用动画,原来的布局时的位置通过 getLeft()获得,保持不变;
2.X 是 View 最终的位置;
3.translationX 为最终位置与布局时初始位置这差。 4.所以若就用 translationX 即为在原来基础上移动多少,X 为最终多少
5.getX()的值为 getLeft()与 getTranslationX()的和

ViewPropertyAnimator

如果需要对一个 View 的多个属性进行动画可以用 ViewPropertyAnimator 类,该类对多属性动画进行了优化,会合并一些 invalidate()来减少刷新视图,该类在 3.1 中引入。

以下两段代码实现同样的效果:

1
2
3
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvyY).start();
1
myView.animate().x(50f).y(100f);

XML 中定义 Property Animation

  • ValueAnimator -
  • ObjectAnimator -
  • AnimatorSet -
property_animator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<set android:ordering="sequentially">
<set>
<objectAnimator
android:propertyName="x"
android:duration="500"
android:valueTo="400"
android:valueType="intType"/>
<objectAnimator
android:propertyName="y"
android:duration="500"
android:valueTo="300"
android:valueType="intType"/>
</set>
<objectAnimator
android:propertyName="alpha"
android:duration="500"
android:valueTo="1f"/>
</set>
1
2
3
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext, R.anim.property_animator);
set.setTarget(myObject);
set.start();