Android 动画总结

目录

  1. View Animation
    1. 1. Tween Animation
    2. 2. Drawable Animation
  2. Property Animation
  3. 系统提供的应用场景
    1. Activity切换动画
    2. Fragment切换动画
    3. Layout Changes Animation
    4. 使用SVG动画
  4. 参考

View Animation

1. Tween Animation

针对View的动画,其相关类在android.view.animation中,
由四种动画组成:平移动画、缩放动画、旋转动画和透明度动画,分别对应Animation四个子类:

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

多种动画组合使用AnimationSet,对应<set>标签;建议使用XML定义动画,文件放在res/anim/下。XML常用语法如下:

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
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@[package:]anim/interpolator_resource"
android:shareInterpolator=["true"|"false"] >
<alpha
android:fromAlpha="float"
android:toAlpha="float" />
<scale
android:fromXScale="float"
android:toXScale="float"
android:fromYScale="float"
android:toYScale="float"
android:pivotX="float"
android:pivotY="float" />
<translate
android:fromXDelta="float"
android:toXDelta="float"
android:fromYDelta="float"
android:toYDelta="float" />
<rotate
android:fromDegrees="float"
android:toDegrees="float"
android:pivotX="float"
android:pivotY="float" />
<set>
...
</set>
</set>

Tips:
0. Tween Animation最大的缺陷是不具备交互性,其响应事件发生在动画发生前的地方,这与其实现原理相关。
1. 根标签必须是<alpha>,<scale>,<translate>,<rotate><set>
2. android:interpolator默认值是@android:anim/accelerate_decelerate_interpolator;android:shareInterpolator表示动画集合是否共享同一插值器,子动画可以单独指定插值器。
3. <alpha> 取值范围[0.0,1.0],其中,0.0表示透明,1.0表示完全不透明
4. <scale> 默认缩放中心是View中心点,可以通过android:pivotXandroid:pivotY进行调整;fromXScale等值取1.0表示没有缩放。
5. <translate> 取值格式有三种:绝对值、[-100%, 100%] 和 [-100%p, 100%p];第二种是相对于本身width计算的,第三种是相对于父View的width进行计算的。
6. <rotate> 默认旋转中心是View中心点,可以通过android:pivotXandroid:pivotY进行调整,取值有三种:浮点数表示相对于left或者top的px值,x%表示相对于left或者top的相对值,x%p表示相对于父控件left或者top的相对值
7. 其他常用标签: android:repeatMode=["restart"|"reverse"]如果动画重复时的模式;android:repeatCount=[infinite|int]动画重复次数,默认值0,表示不重复;android:duration表示持续时间,单位ms;android:startOffset动画延迟多少时间执行;android:fillAfter="true" android:fillBefore="false"表示动画结束时,停留在最后一帧;android:fillAfter="false" android:fillBefore="true"表示动画结束时,停留在第一帧。
8. 使用Demo

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

9. 监听动画事件

1
2
3
4
5
6
7
8
9
10
11
12
13
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}

@Override
public void onAnimationEnd(Animation animation) {
}

@Override
public void onAnimationRepeat(Animation animation) {
}
});

10. 系统提供的Interpolators和自定义参数

Interpolator class Resource ID 可以自定义的参数
AccelerateDecelerateInterpolator @android:anim/accelerate_decelerate_interpolator No
AccelerateInterpolator @android:anim/accelerate_interpolator android:factor=[float],默认值1
AnticipateInterpolator @android:anim/anticipate_interpolator android:tension=[float],默认值2
AnticipateOvershootInterpolator @android:anim/anticipate_overshoot_interpolator android:tension=[float],默认值2; android:extraTension=[float],默认值1.5
BounceInterpolator @android:anim/bounce_interpolator No
CycleInterpolator @android:anim/cycle_interpolator android:cycles=[int],默认值1
DecelerateInterpolator @android:anim/decelerate_interpolator android:factor=[float],默认值1
LinearInterpolator @android:anim/linear_interpolator No
OvershootInterpolator @android:anim/overshoot_interpolator android:tension=[float],默认值2

Demo:

XML file saved at res/anim/my_overshoot_interpolator.xml:

1
2
3
<?xml version="1.0" encoding="utf-8"?>
<overshootInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
android:tension="7.0"/>

This animation XML will apply the interpolator:

1
2
3
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@anim/my_overshoot_interpolator"
.../>

2. Drawable Animation

像电影相似的帧动画,注意容易OOM。
建议XML定义,文件位于res/drawable/下,对应AnimationDrawable类。语法如下:

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"
android:oneshot=["true"|"false"] >
<item
android:drawable="@[package:]drawable/drawable_resource_name"
android:duration="integer" />
</animation-list>

Tips:
0. 根标签必须是<animation-list>android:oneshottrue表示执行一次,false表示循环
1. Demo:
XML file saved at res/anim/rocket.xml:

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"
android:oneshot="false">
<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>

This application code will set the animation as the background for a View, then play the animation:

1
2
3
4
5
ImageView rocketImage = (ImageView) findViewById(R.id.rocket_image);
rocketImage.setBackgroundResource(R.drawable.rocket_thrust);

rocketAnimation = (AnimationDrawable) rocketImage.getBackground();
rocketAnimation.start();

2. AnimationDrawable不能在onCreate()中调用start(),因为有可能没有完全attached to window,如果没有交互要求,建议在onWindowFocusChanged()中进行调用。

Property Animation

Android 3.0引入属性动画,对于任何对象的属性都可以进行动画,相对Tween Animation优点是真正改变View属性,具有交互性;但是Tween Animation效率好,建议能用Tween Animation实现的就用Tween Animation实现,代码量小。属性动画建议使用代码实现,其相关API主要在android.animation下。属性动画默认duration是300ms,帧率默认10ms,可以调整。其工作原理可以参考官方示意图:

ValueAnimator记录动画时间,计算出elapsed fraction(已动画的时间/动画总时间);然后调用TimeInterpolator计算interpolated fraction,再通过TypeEvaluator计算出属性变化值并进行改变,最后一直循环至动画结束。其中,TimeInterpolator实现类是Tween Animation中系统提供的Interpolators,也可以实现TimeInterpolator接口进行自定义;TypeEvaluator实现类有IntEvaluator、FloatEvaluator和ArgbEvaluator,分别对应int、float和表示颜色的十六进制属性,也可以实现TypeEvaluator接口进行自定义。属性动画关键类是Animator的三个子类ValueAnimator、ObjectAnimator和AnimatorSet,其中ObjectAnimator继承自ValueAnimator。

0. ValueAnimator 通常配合ValueAnimator.AnimatorUpdateListener才能实现动画,Demo如下:

1
2
3
4
5
6
7
8
9
10
ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f);
animation.setDuration(1000);
animation.start();
animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Float value = animation.getAnimatedValue();
...
}
});

系统提供Animator.AnimatorListener实现对动画过程监听,其回调方法:onAnimationStart()、onAnimationEnd()、onAnimationRepeat() 和onAnimationCancel(),其中,onAnimationCancel()调用时一定会调用onAnimationEnd(),系统也提供了适配类AnimatorListenerAdapter。

1. ObjectAnimator 简化属性动画创建过程,Demo如下:

1
2
3
ObjectAnimator anim = ObjectAnimator.ofFloat(foo, "alpha", 0f, 1f);
anim.setDuration(1000);
anim.start();

有几点注意:
a.属性动画对象必须提供属性的setter方法(in camel case),如果没有提供则需要增加该方法,或者进行包装(wrapper)。
b.如果属性values只提供一个参数,比如Demo中只传1f,则表示结束值,此时必须需要getter方法。
c.getter和setter方法必须针对动画操作的属性。
d.必要时需要调用invalidate()进行重绘。
e.PropertyValuesHolder可以实现同一对象多个属性动画的效果,相比下面AnimatorSet效率较高,Demo如下:

1
2
3
PropertyValuesHolder pvh1 = PropertyValuesHolder.ofFloat("translateX",300f);
PropertyValuesHolder pvh2 = PropertyValuesHolder.ofFloat("alpha", 0f, 1f);
ObjectAnimator.ofPropertyValuesHolder(view,pvh1,pvh2).setDuration(1000).start();

2. AnimatorSet 把多个属性动画进行组合,优势是可以指定相应动画的执行次序,Demo如下:

1
2
3
4
5
6
7
8
9
10
11
AnimatorSet bouncer = new AnimatorSet();
bouncer.play(bounceAnim).before(squashAnim1);
bouncer.play(squashAnim1).with(squashAnim2);
bouncer.play(squashAnim1).with(stretchAnim1);
bouncer.play(squashAnim1).with(stretchAnim2);
bouncer.play(bounceBackAnim).after(stretchAnim2);
ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
fadeAnim.setDuration(250);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(bouncer).before(fadeAnim);
animatorSet.start();

3. Keyframes使用 keyframes对象包含time/value对,用来表示在动画指定时间的特殊值,每个keyframe也可以拥有前一个keyframe到自身的插值器,Demo如下:

1
2
3
4
5
6
Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
Keyframe kf1 = Keyframe.ofFloat(.5f, 360f);
Keyframe kf2 = Keyframe.ofFloat(1f, 0f);
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2);
ObjectAnimator rotationAnim = ObjectAnimator.ofPropertyValuesHolder(target, pvhRotation)
rotationAnim.setDuration(5000ms);

4. 针对View对象的属性动画可以操作的属性值有:translationX/translateY,rotation/rotationX/rotationY,scaleX/scaleY,pivotX/pivotY,x/y,alpha;View本身增加animate()方法可以直接驱动属性动画。

5. XML定义属性动画,文件在res/animator中。对应关系如下:

Class Tag
ValueAnimator <animator>
ObjectAnimator <objectAnimator>
AnimatorSet <set>

常用标签:

1
2
3
4
5
6
7
8
9
10
11
12
<set android:ordering=["sequentially"|"together"]>
<objectAnimator
android:propertyName="string"
android:duration="int"
android:valueFrom="float|int|color"
android:valueTo="float|int|color"
android:repeatCount="[infinite|int]"
android:repeatMode="[restart|reverse]"
android:startOffset="int"
android:valueType=["intType"|"floatType"]/>
...
</set>

使用Demo:

1
2
3
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,R.anim.property_animator);
set.setTarget(myObject);
set.start();

系统提供的应用场景

Activity切换动画

0. public void overridePendingTransition (int enterAnim, int exitAnim),必须在startActivity(Intent intent)和finish()之后调用才生效。
1. 定义在XML中,通过主题windowAnimationStyle属性进行定义,注意是Tween Animation,其Demo如下:

1
2
3
4
5
6
7
8
9
10
<style name="AnimActivityTheme">
<item name="android:windowAnimationStyle">@style/FeelyouWindowAnimTheme</item>
</style>

<style name="FeelyouWindowAnimTheme" parent="@android:style/Animation.Activity">
<item name="android:activityOpenEnterAnimation">@anim/in_from_left</item>
<item name="android:activityOpenExitAnimation">@anim/out_from_right</item>
<item name="android:activityCloseEnterAnimation">@anim/in_from_right</item>
<item name="android:activityCloseExitAnimation">@anim/out_from_left</item>
</style>

Fragment切换动画

现在App基本上都是支持API 11以上,所以这里Fragment指android.app.Fragment,使用属性动画。
0. Fragment标准切换动画可通过FragmentTransaction.setTransition()实现,其参数取值如下:

参数 意义
TRANSIT_NONE 无动画
TRANSIT_FRAGMENT_OPEN 动画打开
TRANSIT_FRAGMENT_CLOSE 动画关闭

1. 自定义动画使用setCustomAnimations(int enter, int exit, int popEnter, int popExit)方法,API 11时该方法只有前两个参数,API 13后才引入后两个参数,标识Fragment进栈和出栈动画。该方法调用须在add()、remove()、replace()调用之前,否则不生效。

2. 可以使用ViewAnimator及其子类实现Fragment动画切换,同样针对ViewGroup和View切换动画也可使用。

3. ViewPager提供自定义View之间转换动画功能,使用ViewPager.setPageTransformer()方法,参考官网Demo:

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
ViewPager mPager = (ViewPager) findViewById(R.id.pager);
...
mPager.setPageTransformer(true, new ZoomOutPageTransformer());

public class ZoomOutPageTransformer implements ViewPager.PageTransformer {
private static final float MIN_SCALE = 0.85f;
private static final float MIN_ALPHA = 0.5f;

public void transformPage(View view, float position) {
int pageWidth = view.getWidth();
int pageHeight = view.getHeight();

if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
view.setAlpha(0);

} else if (position <= 1) { // [-1,1]
// Modify the default slide transition to shrink the page as well
float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position));
float vertMargin = pageHeight * (1 - scaleFactor) / 2;
float horzMargin = pageWidth * (1 - scaleFactor) / 2;
if (position < 0) {
view.setTranslationX(horzMargin - vertMargin / 2);
} else {
view.setTranslationX(-horzMargin + vertMargin / 2);
}

// Scale the page down (between MIN_SCALE and 1)
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);

// Fade the page relative to its size.
view.setAlpha(MIN_ALPHA +
(scaleFactor - MIN_SCALE) /
(1 - MIN_SCALE) * (1 - MIN_ALPHA));

} else { // (1,+Infinity]
// This page is way off-screen to the right.
view.setAlpha(0);
}
}
}

Layout Changes Animation

0. 官方建议API>=14时可以使用android:animateLayoutChanges="true"开启默认动画:增加或者删除View时会产生动画;还可以通过LayoutTransition进行自定义动画,其实API 11已经引入该API了。而ViewGroup.setLayoutAnimation()方法只是设置View第一次layout时的动画,API 1就存在该方法。

1. API>=19时建议使用Transitions Framework实现,有以下注意事项:
a) SurfaceView、TextureView和AdapterView不建议使用
b) 含有text的View不建议执行resize动画

####Scene概念:
保存View hierarchy的状态,通过Scene.getSceneForLayout()获得对象,其中scene root必须是ViewGroup。

####Transition概念:
定义start scene到end scene之间的转化动画,系统提供内置三种类型如下:

Class Tag Attributes Effect
AutoTransition <autoTransition/> 默认
Fade <fade/> android:fadingMode=”[fade_in | fade_out | fade_in_out]” 默认fade_in_out
ChangeBounds <changeBounds/> Moves and resizes views

可以通过XMl或者代码构建实例,xml的demo如下:
res/transition/fade_transition.xml

1
<fade xmlns:android="http://schemas.android.com/apk/res/android" />

The following code snippet shows how to inflate a Transition instance inside your activity from a resource file:

1
2
3
Transition mFadeTransition =
TransitionInflater.from(this).
inflateTransition(R.transition.fade_transition);

上述使用代码实现的Demo如下:

1
Transition mFadeTransition = new Fade();

使用时:

1
TransitionManager.go(mEndingScene, mFadeTransition);

通过removeTarget()、addTarget()方法可以指定动画目标;同时提供Transition.TransitionListener监听动画过程。如果需要组合则使用TransitionSet,其xml位于res/transitions/下,使用TransitionInflater.from()读取配置文件,以下Demo实现AutoTransition的效果:

1
2
3
4
5
6
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
android:transitionOrdering="sequential">
<fade android:fadingMode="fade_out" />
<changeBounds />
<fade android:fadingMode="fade_in" />
</transitionSet>

可以使用 TransitionManager.beginDelayedTransition() 替代Scene

自定义Transitions:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class CustomTransition extends Transition {

@Override
public void captureStartValues(TransitionValues values) {}

@Override
public void captureEndValues(TransitionValues values) {}

@Override
public Animator createAnimator(ViewGroup sceneRoot,
TransitionValues startValues,
TransitionValues endValues) {}
}

其中,captureStartValues()和captureEndValues()需要把属性保存在values中,values本质是Map,Key建议格式为:

1
package_name:transition_name:property_name

createAnimator返回根据startValues和endValues创建的属性动画对象。官方提供的Demo:

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
public class ChangeColor extends Transition {

/** Key to store a color value in TransitionValues object */
private static final String PROPNAME_BACKGROUND = "customtransition:change_color:background";

// BEGIN_INCLUDE (capture_values)
/**
* Convenience method: Add the background Drawable property value
* to the TransitionsValues.value Map for a target.
*/
private void captureValues(TransitionValues values) {
// Capture the property values of views for later use
values.values.put(PROPNAME_BACKGROUND, values.view.getBackground());
}

@Override
public void captureStartValues(TransitionValues transitionValues) {
captureValues(transitionValues);
}

// Capture the value of the background drawable property for a target in the ending Scene.
@Override
public void captureEndValues(TransitionValues transitionValues) {
captureValues(transitionValues);
}
// END_INCLUDE (capture_values)

// BEGIN_INCLUDE (create_animator)
// Create an animation for each target that is in both the starting and ending Scene. For each
// pair of targets, if their background property value is a color (rather than a graphic),
// create a ValueAnimator based on an ArgbEvaluator that interpolates between the starting and
// ending color. Also create an update listener that sets the View background color for each
// animation frame
@Override
public Animator createAnimator(ViewGroup sceneRoot,
TransitionValues startValues, TransitionValues endValues) {
// This transition can only be applied to views that are on both starting and ending scenes.
if (null == startValues || null == endValues) {
return null;
}
// Store a convenient reference to the target. Both the starting and ending layout have the
// same target.
final View view = endValues.view;
// Store the object containing the background property for both the starting and ending
// layouts.
Drawable startBackground = (Drawable) startValues.values.get(PROPNAME_BACKGROUND);
Drawable endBackground = (Drawable) endValues.values.get(PROPNAME_BACKGROUND);
// This transition changes background colors for a target. It doesn't animate any other
// background changes. If the property isn't a ColorDrawable, ignore the target.
if (startBackground instanceof ColorDrawable && endBackground instanceof ColorDrawable) {
ColorDrawable startColor = (ColorDrawable) startBackground;
ColorDrawable endColor = (ColorDrawable) endBackground;
// If the background color for the target in the starting and ending layouts is
// different, create an animation.
if (startColor.getColor() != endColor.getColor()) {
// Create a new Animator object to apply to the targets as the transitions framework
// changes from the starting to the ending layout. Use the class ValueAnimator,
// which provides a timing pulse to change property values provided to it. The
// animation runs on the UI thread. The Evaluator controls what type of
// interpolation is done. In this case, an ArgbEvaluator interpolates between two
// #argb values, which are specified as the 2nd and 3rd input arguments.
ValueAnimator animator = ValueAnimator.ofObject(new ArgbEvaluator(),
startColor.getColor(), endColor.getColor());
// Add an update listener to the Animator object.
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Object value = animation.getAnimatedValue();
// Each time the ValueAnimator produces a new frame in the animation, change
// the background color of the target. Ensure that the value isn't null.
if (null != value) {
view.setBackgroundColor((Integer) value);
}
}
});
// Return the Animator object to the transitions framework. As the framework changes
// between the starting and ending layouts, it applies the animation you've created.
return animator;
}
}
// For non-ColorDrawable backgrounds, we just return null, and no animation will take place.
return null;
}
// END_INCLUDE (create_animator)
}

使用SVG动画

Google官方支持包中提供了AnimatedVectorDrawableCompat对svg动画支持,向下兼容至API 11,具体使用参见官方https://developer.android.com/reference/android/graphics/drawable/AnimatedVectorDrawable.html

参考

[1] https://developer.android.com/guide/topics/graphics/prop-animation.html
[2] https://developer.android.com/training/transitions/index.html
[3] https://developer.android.com/training/animation/index.html
[4] 《Android开发艺术探索》 任玉刚 著
[5] 《Android群英传》 徐宜生 著