android动画实现原理——补间动画

概要

android的动画可以分为三种:补间动画、帧动画和属性动画。

  • 补间动画(Tween Animation)

    通过对场景里的对象不断做图像变换(平移、缩放、旋转、透明度)从而产生动画效果,它是一种渐进式动画,并且支持自定义。

  • 帧动画(Drawable Animation)

    通过顺序播放一些列图像从而产生动画效果,可以简单理解为图片切换动画,但是在图片过多过大时会导致OOM。

  • 属性动画(Property Animation)

    通过动态改变对象的属性从而达到动画效果,属性动画是API 11的新特性,在低版本需要通过兼容库来使用它。

补间动画的实现原理

1、基础使用

补间动画的作用对象是View,他支持四种动画效果,分别是平移动画、缩放动画、旋转动画和透明度动画。补间动画的四种变换效果对应这Animation的四个子类:TranslateAnimation、ScaleAnimation、RotateAnimation和AlphaAnimation,这四种动画既可以通过XML来定义,也可以通过代码来动态创建。

补间动画的使用步骤

  • 创建一个animationSet对象
  • 增加需要创建对应的animation对象
  • 根据项目的需求,为animation对象设置相应的数据
  • 将animation对象添加到animationSet中
  • View控件开始执行animation

2、自定义补间动画

自定义补间动画是一件既简单又复杂的事情,派生一种新动画只需要继承Animation这个抽象类,然后重写它的initialize和applyTransformation方法,在initialize方法中做一些初始化工作,在applyTransformation中进行相应的矩阵变换即可,很多时候需要采用Canvas来简化矩阵变换的过程,而自定义补间动画的过程主要是矩阵变化的过程,矩阵是数学上的概念。

3、工作原理

android的补间动画中最重要的类是Animation类,一个抽象类,是所有动画的基类,它定义了Animation的公共属性和方法。属性中最重要的是:AnimationListener和Transformation,AnimationListener是动画监听器,监听动画的开始,执行过程,结束,可以实现一些自己的逻辑,Transformation是每一帧动画中包含的信息(平移、旋转、缩放、透明度),在Transformation类中最重要的属性就是alpha和Matrix,这两个是真正存放一帧动画的所有信息的载体。方法中最重要的是:getTransformation()和applyTransformation(),第一个方法是由系统调用,根据动画当前时间计算出此时的Transformation信息,不必重写次方法,第二个方法是必须重写的,根据系统由第一个方法计算出的Transformation进行实际的动画实现。

首先来看Animation对象,当要设置动画的时候,一般都是先构建好Animation对象,然后使用下列形式的代码:

1
View.startAnimation(animation);

追踪View的startAnimation()方法,可以发现:

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

可以看到先是调用了View的setAnimation()方法给自己设置一个Animation对象,这个对象是View类中的一个名为mCurrentAnimation的成员变量,然后调用invalidate()来重绘自己。既然View给自己设置了Animation对象,就会有需要拿出来使用,在View.draw(Canvas, ViewGroup, long)方法中发现了它的调用:

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
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
...
Transformation transformToApply = null;
boolean concatMatrix = false;
final boolean scalingRequired = mAttachInfo != null && mAttachInfo.mScalingRequired;
final Animation a = getAnimation();
if (a != null) {
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
concatMatrix = a.willChangeTransformationMatrix();
if (concatMatrix) {
mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
}
transformToApply = parent.getChildTransformation();
} else {
if ((mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_TRANSFORM) != 0) {
mRenderNode.setAnimationMatrix(null);
mPrivateFlags3 &= ~PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
}
if (!drawingWithRenderNode
&& (parentFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
final Transformation t = parent.getChildTransformation();
final boolean hasTransform = parent.getChildStaticTransformation(this, t);
if (hasTransform) {
final int transformType = t.getTransformationType();
transformToApply = transformType != Transformation.TYPE_IDENTITY ? t : null;
concatMatrix = (transformType & Transformation.TYPE_MATRIX) != 0;
}
}
}
...
}

在View中有两个draw方法,一个有一个参数,另一个有三个参数,分别是:

1
2
3
public void draw(Canvas canvas) {
...
}
1
2
3
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
...
}

其中第一个参数的draw方法是具体的绘制过程(绘制背景、绘制内容、绘制前景……),含有三个参数的draw方法是由父布局调用的(ViewGroup.drawChild),因为android绘制的过程是从根布局开始的,子View是否能够绘制由父布局决定,这个方法就是提前处理绘制的地方,补间动画也是在此进行了处理以实现动画的效果。

从View三个参数的方法中可以看到首先是通过getAnimation()方法获取之前设置的animation对象,然后用它作为参数调用了applyLegacyAnimation()方法,接下里看看applyLegacyAnimation的实现:

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
private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
Animation a, boolean scalingRequired) {
Transformation invalidationTransform;
final int flags = parent.mGroupFlags;
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 (scalingRequired && mAttachInfo.mApplicationScale != 1f) {
if (parent.mInvalidationTransformation == null) {
parent.mInvalidationTransformation = new Transformation();
}
invalidationTransform = parent.mInvalidationTransformation;
// 获取变换
a.getTransformation(drawingTime, invalidationTransform, 1f);
} else {
invalidationTransform = t;
}
if (more) {
if (!a.willChangeBounds()) {
if ((flags & (ViewGroup.FLAG_OPTIMIZE_INVALIDATE | ViewGroup.FLAG_ANIMATION_DONE)) ==ViewGroup.FLAG_OPTIMIZE_INVALIDATE) {
parent.mGroupFlags |= ViewGroup.FLAG_INVALIDATE_REQUIRED;
} else if ((flags & ViewGroup.FLAG_INVALIDATE_REQUIRED) == 0) {
parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
parent.invalidate(mLeft, mTop, mRight, mBottom);
}
} else {
if (parent.mInvalidateRegion == null) {
parent.mInvalidateRegion = new RectF();
}
final RectF region = parent.mInvalidateRegion;
// 获取绘制无效区域
a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region,
invalidationTransform);
parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
final int left = mLeft + (int) region.left;
final int top = mTop + (int) region.top;
parent.invalidate(left, top, left + (int) (region.width() + .5f),
top + (int) (region.height() + .5f));
}
}
return more;
}

其中主要调用了Animation的getTransformation()方法,这个三个参数的方法最后会调用重载它的两个参数的方法。

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
public boolean getTransformation(long currentTime, Transformation outTransformation) {
if (mStartTime == -1) {
mStartTime = currentTime;
}
final long startOffset = getStartOffset();
final long duration = mDuration;
float normalizedTime;
if (duration != 0) {
normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
(float) duration;
} else {
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)) {
if (!mStarted) {
fireAnimationStart();
mStarted = true;
if (NoImagePreloadHolder.USE_CLOSEGUARD) {
guard.open("cancel or detach or getTransformation");
}
}
if (mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
if (mCycleFlip) {
normalizedTime = 1.0f - normalizedTime;
}
final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
applyTransformation(interpolatedTime, outTransformation);
}
...
}

这个方法先将参数currentTime处理成一个float表示当前动画进度,然后将进度值传入插值器得到新的进度值,前者是均匀的,随着时间是一个直线的线性关系,而通过插值器计算后得到的是一个曲线的关系,然后将新的进度值和Transformation对象传入applyTransformation()方法中,接下来看看applyTransformation()方法的实现:

1
2
protected void applyTransformation(float interpolatedTime, Transformation t) {
}

可以看到Animation的applyTransformation方法是空实现,具体实现它的是Animation的四个子类,而该方法正是真正的处理动画变化的过程。下面看一下四个子类的applyTransformation()方法的具体实现:

  • ScaleAnimation

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @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);
    }
    }
  • AlphaAnimation

    1
    2
    3
    4
    5
    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
    final float alpha = mFromAlpha;
    t.setAlpha(alpha + ((mToAlpha - alpha) * interpolatedTime));
    }
  • RotateAnimation

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @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);
    }
    }
  • TranslateAnimation

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @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);
    }

如此一来,动画的变化,就隐藏在Transformation之中了,既然已经知道了如何变化,就需要系统使用这种变化即应用到绘制中了。回到View的draw(Canvas canvas, ViewGroup parent, long drawingTime)方法,其中applyLegacyAnimation方法是用来获取变换的,其中有两个部分需要注意:

1
a.getTransformation(drawingTime, invalidationTransform, 1f);
1
2
a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region,
invalidationTransform);

第一个是获取变换,第二个是获取绘制无效区域。

在View的draw方法中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if (transformToApply != null) {
if (concatMatrix) {
if (drawingWithRenderNode) {
renderNode.setAnimationMatrix(transformToApply.getMatrix());
} else {
canvas.translate(-transX, -transY);
canvas.concat(transformToApply.getMatrix());
canvas.translate(transX, transY);
}
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
float transformAlpha = transformToApply.getAlpha();
if (transformAlpha < 1) {
alpha *= transformAlpha;
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
}

在这里分别从硬件加速和软件绘制上,对canvas进行矩阵变换。

可见applyTransformation()方法就是动画具体的实现,系统会以一个比较高的频率来调用这个方法,一般情况下是60FPS,是一个非常流畅的画面了,也就是16ms。而系统是怎样高频率调用这个方法的,就要得益于Choreographer机制了。下面来看Choreographer类,其是在ViewRootImpl的构造方法中被调用,代码如下:

1
2
3
4
5
public ViewRootImpl(Context context, Display display) {
...
mChoreographer = Choreographer.getInstance();
...
}

再看到ViewRootImpl的scheduleTraversals()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}

可以看到调用了mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null),该方法辗转调用了两个内部方法,最终调用了postCallbackDelayedInternal()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
if (DEBUG_FRAMES) {
Log.d(TAG, "PostCallback: type=" + callbackType
+ ", action=" + action + ", token=" + token
+ ", delayMillis=" + delayMillis);
}
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}

这个方法中,首先拿到当前的时间,然后将要执行的内容加入到一个mCallbackQueues中,再执行scheduleFrameLocked()方法或者发送一个Message。先看scheduleFrameLocked()方法:

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
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame on vsync.");
}
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
final long nextFrameTime = Math.max(
mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
}
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}

if判断进去的部分是否使用垂直同步,暂时不考虑,else进去的部分还是把消息发送到mHandler对象中,接下来就看看mHandler对象,mHandler实例的类型是FrameHandler,它的定义就在Choreographer类中,源码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private final class FrameHandler extends Handler {
public FrameHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_DO_FRAME:
doFrame(System.nanoTime(), 0);
break;
case MSG_DO_SCHEDULE_VSYNC:
doScheduleVsync();
break;
case MSG_DO_SCHEDULE_CALLBACK:
doScheduleCallback(msg.arg1);
break;
}
}
}

它的处理方法中有三个分支,但最终都会调用这个doFrame()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void doFrame(long frameTimeNanos, int frame) {
...
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
mFrameInfo.markInputHandlingStart();
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
AnimationUtils.unlockAnimationClock();
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
...
}

可以看到这个方法多次调用了doCallbacks()方法,从入参可以看出分别是CALLBACK_INPUT(输入),CALLBACK_ANIMATION(动画),CALLBACK_TRAVERSAL(遍历)等。接下来就看看doCallbacks()方法:

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
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
synchronized (mLock) {
final long now = System.nanoTime();
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
if (callbacks == null) {
return;
}
mCallbacksRunning = true;
if (callbackType == Choreographer.CALLBACK_COMMIT) {
final long jitterNanos = now - frameTimeNanos;
Trace.traceCounter(Trace.TRACE_TAG_VIEW, "jitterNanos", (int) jitterNanos);
if (jitterNanos >= 2 * mFrameIntervalNanos) {
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos
+ mFrameIntervalNanos;
if (DEBUG_JANK) {
Log.d(TAG, "Commit callback delayed by " + (jitterNanos * 0.000001f)
+ " ms which is more than twice the frame interval of "
+ (mFrameIntervalNanos * 0.000001f) + " ms! "
+ "Setting frame time to " + (lastFrameOffset * 0.000001f)
+ " ms in the past.");
mDebugPrintNextFrameTimeDelta = true;
}
frameTimeNanos = now - lastFrameOffset;
mLastFrameTimeNanos = frameTimeNanos;
}
}
}
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
for (CallbackRecord c = callbacks; c != null; c = c.next) {
if (DEBUG_FRAMES) {
Log.d(TAG, "RunCallback: type=" + callbackType
+ ", action=" + c.action + ", token=" + c.token
+ ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
}
c.run(frameTimeNanos);
}
} finally {
synchronized (mLock) {
mCallbacksRunning = false;
do {
final CallbackRecord next = callbacks.next;
recycleCallbackLocked(callbacks);
callbacks = next;
} while (callbacks != null);
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}

先看mCallbackQueues[]这个成员变量,它是一个CallbackQueue对象数组,它的下标是指类型,参数callbackType传给了mCallbackQueues[callbackType]中,而callbackType就是前面说的常量:CALLBACK_INPUT(输入),CALLBACK_ANIMATION(动画),CALLBACK_TRAVERSAL(遍历)。只需要根据不同的callbackType,就可以从这个数组里面取出不同类型的CallbackQueue对象来。而这个CallbackQueue又是Choreographer的一个内部类,其中有两个很重要的方法,分别是:extractDueCallbacksLocked(long)和addCallbackLocked(long, Object, Object)。

先来看addCallbackLocked(long, Object, Object)方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void addCallbackLocked(long dueTime, Object action, Object token) {
CallbackRecord callback = obtainCallbackLocked(dueTime, action, token);
CallbackRecord entry = mHead;
if (entry == null) {
mHead = callback;
return;
}
if (dueTime < entry.dueTime) {
callback.next = entry;
mHead = callback;
return;
}
while (entry.next != null) {
if (dueTime < entry.next.dueTime) {
callback.next = entry.next;
break;
}
entry = entry.next;
}
entry.next = callback;
}

首先它通过一个内部方法构建了一个CallbackRecord对象,然后在后面将参数中的对象链接在CallbackRecord尾部,其实CallbackRecord就是一个链表结构的对象。

接着看extractDueCallbacksLocked(long)方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public CallbackRecord extractDueCallbacksLocked(long now) {
CallbackRecord callbacks = mHead;
if (callbacks == null || callbacks.dueTime > now) {
return null;
}
CallbackRecord last = callbacks;
CallbackRecord next = last.next;
while (next != null) {
if (next.dueTime > now) {
last.next = null;
break;
}
last = next;
next = next.next;
}
mHead = next;
return callbacks;
}

这个方法是根据当前的时间,选出执行链表中与该时间最近的一个操作来处理,实际上,可以通俗地理解为跳帧。

小结

到这里,可以得出以下结论:

  • 控制外部输入事件处理,动画执行,UI变化都是在同一个类中做的处理,即是Choreographer,其中它规定了理想的运行间隔为10ms,因为各种操作需要花费一定的时间,所以外部执行的间隔统计出来是大约16ms。
  • 在Choreographer对象中有三条链表,分别保存着待处理的输入事件,待处理的动画事件,代处理的遍历事件。
  • 每次执行的时候,Choreographer会根据当前的时间,只处理事件链表中最后一个事件,当有耗时操作在主线程中时,事件不能及时执行,就会出现所谓的跳帧,卡顿现象。
  • Choreographer的公有方法postCallback是往事件链表中放事件的方法,而doFrame是消耗这些事件的方法。