Android 动画之 View 动画

View 动画又叫补间动画,只适用于 View,相关的类在 android.view.animation 包下。View 动画作用的是 View 的影像,原始 View 的位置没有变化。

四种常用的 View 动画:

  1. TranslationAnimation:位移动画
  2. RotateAnimation:旋转动画
  3. ScaleAnimation:缩放动画
  4. AlphaAnimation:透明度动画

基本用法:

1
2
3
4
5
6
7
8
9
10
TranslateAnimation animation = new TranslateAnimation(0, 0, 0, 500);
animation.setDuration(2000);
animation.setRepeatCount(Animation.INFINITE);
animation.setRepeatMode(Animation.REVERSE);
view.startAnimation(animation);

View mView = findViewById(R.id.mView);
// xml文件地址 res/anim/slide_bottom_in.xml
Animation mAnimation = AnimationUtils.loadAnimation(this,R.anim.slide_bottom_in);
mView.startAnimation(mAnimation);

源码基于 sdk-30/11.0/R

Animation

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
/**
* Abstraction for an Animation that can be applied to Views, Surfaces, or
* other objects.
*/
public abstract class Animation implements Cloneable {
/**
* Creates a new animation with a duration of 0ms, the default interpolator, with
* fillBefore set to true and fillAfter set to false
*/
public Animation() {
ensureInterpolator();
}

protected void ensureInterpolator() {
if (mInterpolator == null) {
mInterpolator = new AccelerateDecelerateInterpolator();
}
}
}

public boolean getTransformation(long currentTime, Transformation outTransformation) {
// 记录动画启动时间
if (mStartTime == -1) {
mStartTime = currentTime;
}

final long startOffset = getStartOffset();
final long duration = mDuration;
float normalizedTime;
// 计算动画进度 [0, 1]
if (duration != 0) {
normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
(float) duration;
} else {
// time is a step-change with a zero duration
normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f;
}

// 动画是否结束
final boolean expired = normalizedTime >= 1.0f || isCanceled();
mMore = !expired;

// 越界检测
if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);

if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
// ...
// 根据插值器计算动画进度
final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
// 更新动画,子类重写
applyTransformation(interpolatedTime, outTransformation);
}
return mMore;
}
  1. 默认是 AccelerateDecelerateInterpolator 插值器。
  2. 记录动画第一帧的时间;
  3. 根据当前时间、动画开始时间和动画时长计算动画进度;
  4. 根据插值器计算动画的实际进度;
  5. 调用子类的 applyTransformation()方法,应用动画效果。

Animation#applyTransformation() 是空实现,由具体的子类实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
// TranslateAnimation.java
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float dx = mFromXDelta;
float dy = mFromYDelta;
if (mFromXDelta != mToXDelta) {
dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime);
}
if (mFromYDelta != mToYDelta) {
dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime);
}
t.getMatrix().setTranslate(dx, dy);
}
1
2
3
4
5
6
7
8
9
10
11
12
// RotateAnimation.java
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float degrees = mFromDegrees + ((mToDegrees - mFromDegrees) * interpolatedTime);
float scale = getScaleFactor();

if (mPivotX == 0.0f && mPivotY == 0.0f) {
t.getMatrix().setRotate(degrees);
} else {
t.getMatrix().setRotate(degrees, mPivotX * scale, mPivotY * scale);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ScaleAnimation.java
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float sx = 1.0f;
float sy = 1.0f;
float scale = getScaleFactor();

if (mFromX != 1.0f || mToX != 1.0f) {
sx = mFromX + ((mToX - mFromX) * interpolatedTime);
}
if (mFromY != 1.0f || mToY != 1.0f) {
sy = mFromY + ((mToY - mFromY) * interpolatedTime);
}

if (mPivotX == 0 && mPivotY == 0) {
t.getMatrix().setScale(sx, sy);
} else {
t.getMatrix().setScale(sx, sy, scale * mPivotX, scale * mPivotY);
}
}
1
2
3
4
5
6
// AlphaAnimation.java
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
final float alpha = mFromAlpha;
t.setAlpha(alpha + ((mToAlpha - alpha) * interpolatedTime));
}

通过 View 绘制应用动画

1
2
3
4
5
6
7
// View.java
public void startAnimation(Animation animation) {
animation.setStartTime(Animation.START_ON_FIRST_FRAME);
setAnimation(animation);
invalidateParentCaches();
invalidate(true);
}

设置 Animation 对象,清除父 View 缓存,发起重绘操作。

  1. 调用 View.startAnimation() 方法,设置 Animation 对象,通过 invalidate() 发起重绘请求;
  2. 从当前 View 向父 View 遍历,调用 ViewParent#invalidateChildInParent() 方法;
  3. 最后调用到 ViewRootImpl#invalidateChildInParent(),接着调用 scheduleTraversals() 调度 View 树的重绘;

ViewGroup 通过 drawChild() 方法绘制子 View,达到动画的效果。

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
// View.java
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
final Animation a = getAnimation();
if (a != null) {
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
transformToApply = parent.getChildTransformation();
}
return more;
}

private boolean applyLegacyAnimation(ViewGroup parent,...) {
Transformation invalidationTransform;
final boolean initialized = a.isInitialized();
if (!initialized) {
a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight());
a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop);
if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler);
onAnimationStart();
}

final Transformation t = parent.getChildTransformation();
boolean more = a.getTransformation(drawingTime, t, 1f);
// 动画正在运行
if (more) {
// a.willChangeBounds() 返回 true
if (!a.willChangeBounds()) {
// ...
} else {
// 重新发起绘制流程
parent.invalidate(left, top, left + (int) (region.width() + .5f),
top + (int) (region.height() + .5f));
}
}
return more;
}
  1. View 绘制时回调用 Animation;
  2. 如果动画没有执行完毕,就会调用 invalidate() 方法,递归向父 View 请求重绘,直到 ViewRootImpl 发起绘制流程;
  3. 当下一帧屏幕刷新信号来的时候,通过 ViewRootImpl#performTraversals() 遍历 View 树绘制,在 View#draw() 方法内调用 View#applyLegacyAnimation() 方法执行动画相关操作,包括调用 Animation#getTransformation() 计算动画进度和 Animation#applyTransformation() 应用动画。
  4. 在动画很流畅的情况下,每隔 16.6ms 即一帧到来的时候,执行一次 applyTransformation(),直到动画完成。所以 applyTransformation() 会被回调多次,而且这个回调次数并没有办法人为进行设定。

Transformation

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
/**
* Defines the transformation to be applied at
* one point in time of an Animation.
*
*/
public class Transformation {
protected Matrix mMatrix;
protected float mAlpha;

/**
* Creates a new transformation with alpha = 1 and the identity matrix.
*/
public Transformation() {
clear();
}

public void clear() {
if (mMatrix == null) {
mMatrix = new Matrix();
} else {
mMatrix.reset();
}
mClipRect.setEmpty();
mHasClipRect = false;
mAlpha = 1.0f;
mTransformationType = TYPE_BOTH;
}
}

Interpolator 插值器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* A time interpolator defines the rate of change of an animation. This allows animations
* to have non-linear motion, such as acceleration and deceleration.
*/
public interface TimeInterpolator {
float getInterpolation(float input);
}

/**
* An interpolator defines the rate of change of an animation. This allows
* the basic animation effects (alpha, scale, translate, rotate) to be
* accelerated, decelerated, repeated, etc.
*/
public interface Interpolator extends TimeInterpolator {
// A new interface, TimeInterpolator, was introduced for the new android.animation
// package. This older Interpolator interface extends TimeInterpolator so that users of
// the new Animator-based animations can use either the old Interpolator implementations or
// new classes that implement TimeInterpolator directly.
}

LinearInterpolator

线性插值器,匀速运动。

1
2
3
public float getInterpolation(float input) {
return input;
}

AccelerateInterpolator

加速插值器,动画越来越快。

1
2
3
4
5
6
7
public float getInterpolation(float input) {
if (mFactor == 1.0f) {
return input * input;
} else {
return (float)Math.pow(input, mDoubleFactor);
}
}

DecelerateInterpolator

减速插值器,动画越来越慢。

1
2
3
4
5
6
7
8
9
public float getInterpolation(float input) {
float result;
if (mFactor == 1.0f) {
result = (float)(1.0f - (1.0f - input) * (1.0f - input));
} else {
result = (float)(1.0f - Math.pow((1.0f - input), 2 * mFactor));
}
return result;
}

AccelerateDecelerateInterpolator

加速减速插值器,动画两头慢,中间快。

1
2
3
public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}

TypeEvaluator 估值器

1
2
3
4
5
6
7
8
9
10
/**
* Interface for use with the ValueAnimator#setEvaluator(TypeEvaluator) function. Evaluators
* allow developers to create animations on arbitrary property types, by allowing them to supply
* custom evaluators for types that are not automatically understood and used by the animation
* system.
*/
public interface TypeEvaluator<T> {
public T evaluate(float fraction, T startValue, T endValue);

}

IntEvaluator

1
2
3
4
5
6
7
8
9
/**
* This evaluator can be used to perform type interpolation between <code>int</code> values.
*/
public class IntEvaluator implements TypeEvaluator<Integer> {
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}
}

ArgbEvaluator

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
/**
* This evaluator can be used to perform type interpolation between integer
* values that represent ARGB colors.
*/
public class ArgbEvaluator implements TypeEvaluator {
private static final ArgbEvaluator sInstance = new ArgbEvaluator();

public static ArgbEvaluator getInstance() {
return sInstance;
}

public Object evaluate(float fraction, Object startValue, Object endValue) {
int startInt = (Integer) startValue;
float startA = ((startInt >> 24) & 0xff) / 255.0f;
float startR = ((startInt >> 16) & 0xff) / 255.0f;
float startG = ((startInt >> 8) & 0xff) / 255.0f;
float startB = ( startInt & 0xff) / 255.0f;

int endInt = (Integer) endValue;
float endA = ((endInt >> 24) & 0xff) / 255.0f;
float endR = ((endInt >> 16) & 0xff) / 255.0f;
float endG = ((endInt >> 8) & 0xff) / 255.0f;
float endB = ( endInt & 0xff) / 255.0f;

// convert from sRGB to linear
startR = (float) Math.pow(startR, 2.2);
startG = (float) Math.pow(startG, 2.2);
startB = (float) Math.pow(startB, 2.2);

endR = (float) Math.pow(endR, 2.2);
endG = (float) Math.pow(endG, 2.2);
endB = (float) Math.pow(endB, 2.2);

// compute the interpolated color in linear space
float a = startA + fraction * (endA - startA);
float r = startR + fraction * (endR - startR);
float g = startG + fraction * (endG - startG);
float b = startB + fraction * (endB - startB);

// convert back to sRGB in the [0..255] range
a = a * 255.0f;
r = (float) Math.pow(r, 1.0 / 2.2) * 255.0f;
g = (float) Math.pow(g, 1.0 / 2.2) * 255.0f;
b = (float) Math.pow(b, 1.0 / 2.2) * 255.0f;

return Math.round(a) << 24 | Math.round(r) << 16 | Math.round(g) << 8 | Math.round(b);
}
}

PointFEvaluator

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
/**
* This evaluator can be used to perform type interpolation between <code>PointF</code> values.
*/
public class PointFEvaluator implements TypeEvaluator<PointF> {
private PointF mPoint;

public PointFEvaluator() {
}

public PointFEvaluator(PointF reuse) {
mPoint = reuse;
}

@Override
public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
float x = startValue.x + (fraction * (endValue.x - startValue.x));
float y = startValue.y + (fraction * (endValue.y - startValue.y));

if (mPoint != null) {
mPoint.set(x, y);
return mPoint;
} else {
return new PointF(x, y);
}
}
}

估值器是依赖插值器的,需要根据插值器的值才能计算新的值。

布局动画

1
2
3
4
5
<LinearLayout android:id="@+id/container"
android:animateLayoutChanges="true"
android:layoutAnimation="@anim/anim_layoutanimation"
...
/>

将 android:animateLayoutChanges 属性设置为 true。当布局添加、移除或更新项目,系统就会自动对这些项目进行动画处理:

anim/anim_layoutanimation.xml 文件定义如下:

1
2
3
4
5
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
android:animation="@anim/alpha_appear"
android:animationOrder="normal"
android:delay="30%"
android:interpolator="@android:interpolator/accelerate_decelerate" />

动画效果

  • Ripple (波纹效果)

    1
    2
    3
    4
    5
    6
    <ripple xmlns:android="http://schemas.android.com/apk/res/android"
    android:color="#ffe5e5e5">
    <item
    android:id="@android:id/mask"
    android:drawable="@color/white" />
    </ripple>
  • Circular Reveal (揭露效果)

    1
    Animator animator = ViewAnimationUtils.createCircularReveal();

相关 API

  • AnimationUtils

总结

  1. 当调用了 View#startAnimation() 时动画并没有马上就执行,而是通过 invalidate() 向上通知到 ViewRootImpl 发起一次遍历 View 树的请求,而这次请求会等到接收到最近一帧到了的信号时才去发起遍历 View 树绘制操作。
  2. 从 DecorView 开始遍历,绘制流程在遍历时会调用到 View#draw() 方法,当该方法被调用时,如果 View 有绑定动画,那么会去调用 applyLegacyAnimation(),这个方法是专门用来处理动画相关逻辑的。
  3. 在 applyLegacyAnimation() 这个方法里,如果动画还没有执行过初始化,先调用动画的初始化方法 initialized(),同时调用 onAnimationStart() 通知动画开始了,然后调用 getTransformation() 来根据当前时间计算动画进度,紧接着调用 applyTransformation() 并传入动画进度来应用动画。
  4. getTransformation() 方法有返回值,如果动画还没结束会返回 true,动画已经结束或者被取消了返回 false。所以 applyLegacyAnimation() 会根据 getTransformation() 的返回值来决定是否通知 ViewRootImpl 再发起一次遍历请求,返回值是 true 表示动画没结束,那么就去通知 ViewRootImpl 再次发起一次遍历请求。然后当下一帧到来时,再从 DecorView 开始遍历 View 树绘制,重复上面的步骤,这样直到动画结束。
  5. 有一点需要注意,动画是在每一帧的绘制流程里被执行,所以动画并不是单独执行的,也就是说,如果这一帧里有一些 View 需要重绘,那么这些工作同样是在这一帧里的这次遍历 View 树的过程中完成的。每一帧只会发起一次 perfromTraversals() 操作。

参考

[1] View 动画 Animation 运行原理解析