Android Animation動畫原理原始碼分析
Android 平臺提供了三類動畫,一類是 Tween 動畫-Animation,即通過對場景裡的物件不斷做影象變換 ( 平移、縮放、旋轉 ) 產生動畫效果;第二類是 Frame 動畫,即順序播放事先做好的影象,跟電影類似。最後一種就是3.0之後才出現的屬性動畫PropertyAnimator ,這個分享的是第一類動畫原理。
Animation動畫有4種,TranslateAnimation、ScaleAnimation、RotateAnimation、AlphAnimation,其都繼承了Animation這個抽象類,實現了applyTransformation(float interpolatedTime, Transformation t)函式,如下ScaleAnimation的實現:
根據函式的命名來看就是對Transformation進行設定,通過傳來interpolatedTime浮點值不斷改變Transformation的矩陣Matrix來實現動畫的縮放。那這個函式什麼時候被呼叫,如何被呼叫,一步步來分析下:
可以看到在Animation中getTransformation(long currentTime, Transformation outTransformation)進行了呼叫,並且判斷了動畫是否繼續下去,還有其他變化幀是否完成,如下:
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 {
// 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)) {
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);
//對transformation進行改變
applyTransformation(interpolatedTime, outTransformation);
}
******省略程式碼******
if (!mMore && mOneMoreTime) {
mOneMoreTime = false;
return true;
}
return mMore;
}
那getTransformation函式又是在哪裡被呼叫呢。我們一步步從動畫api使用源頭開始分析:
我們知道使用Animation執行動畫從view.startAnimation(Animation animation)開始,其實現如下:
public void startAnimation(Animation animation) {
animation.setStartTime(Animation.START_ON_FIRST_FRAME);
setAnimation(animation);
invalidateParentCaches();
invalidate(true);
}
一看沒有啥,就是設定賦值以及清父View Caches的標籤設定,沒有看出啥是如何讓動畫執行起來,呼叫了Animation的getTransformation函式。一般會說看到了invalidate函式呼叫了,就是重新整理了View,那具體是怎麼重新整理View繪製的呢,那先講下invalidate(true)執行流程和邏輯是啥,到底怎麼樣導致了重新整理。沒有別的辦法,看原始碼,gogogo……詳見我的另一遍文章《Android invalidate()和postInvalidate()重新整理原理》
1、invalidate最後呼叫到invalidateInternal函式,把view的相對尺寸和相關狀態設定傳遞
void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate)
2、invalidateInternal函式中邏輯不少,主要部分如下,有段呼叫父view進行重新整理:
// Propagate the damage rectangle to the parent view.
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
p.invalidateChild(this, damage);
}
那ViewParent.invalidateChild實現是在哪裡呢,猜到ViewGroup,發現其實現了ViewParent的介面,並且在ViewGroup.addView時新增子View的邏輯中會最後呼叫到`addViewInner方法:
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
......
if (child.getParent() != null) {
throw new IllegalStateException("The specified child already has a parent. " +
"You must call removeView() on the child's parent first.");
}
if (mTransition != null) {
mTransition.addChild(this, child);
}
if (!checkLayoutParams(params)) {
params = generateLayoutParams(params);
}
if (preventRequestLayout) {
child.mLayoutParams = params;
} else {
child.setLayoutParams(params);
}
if (index < 0) {
index = mChildrenCount;
}
addInArray(child, index);
// tell our children
//設定父view
if (preventRequestLayout) {
child.assignParent(this);
} else {
child.mParent = this;
}
if (child.hasFocus()) {
requestChildFocus(child, child.findFocus());
}
.....
}
對child的ViewPrarent變數mParent進行賦值,把自己傳遞給子View,那麼定位到ViewGroup對invalidateChild方法實現,發現裡面有迴圈查詢父View邏輯,如下:
do {
View view = null;
if (parent instanceof View) {
view = (View) parent;
}
**********
省略程式碼
// If the parent is dirty opaque or not dirty, mark it dirty with the opaque
**********
parent = parent.invalidateChildInParent(location, dirty);
if (view != null) {
// Account for transform on current parent
********
}
} while(parent!=null)
這個迴圈直到parent!=null才停止,那什麼時候parent.invalidateChildInParent(location, dirty)會返回null呢。這裡就要分析View樹形結構了,佈局結構中最上層的ViewParent是誰,什麼時候賦值的。這個要從setContentView函式設定佈局檔案開始講,有點長,但也許都是到佈局的最頂層view就是DecorView(可以查原始碼),那DecorView的ViewParent又是誰,這個就必須從新增decorView定位。參考http://blog.csdn.net/luoshengyang/article/details/6689748這邊老羅的文章,分析應用啟動以及View顯示載入過程,從中可以知道在AMS通知PerformResumeActivity命令時開始顯示介面,會呼叫activity的makeVisible(),該函式添加了mDecor(DecorView)
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
ViewManager.addView的實現在WindowManagerGlobal的addView方法中:
會看到一個ViewRootImpl,看起來很想最最頂層 的View,查詢原始碼發現:併發View,但是其實現了ViewParent,這就差不多連起來了,然後其呼叫了setView方法檢視實現如下
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
************
很多程式碼省略
*************
view.assignParent(this);
mAddedTouchMode = (res & WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE) != 0;
mAppVisible = (res & WindowManagerGlobal.ADD_FLAG_APP_VISIBLE) != 0;
************
很多程式碼省略
*************
}
}
}
發現 mView = view進行賦值,並呼叫assignParent(this),把這個ViewParent實現付給DecorView,從持有父View。既然ViewRootImp實現了ViewParent,並且付給了DecorView,那在DecorView查詢父parent時(parent.invalidateChildInParent(location, dirty))就可以定位到ViewRootImpl實現了,找到ViewRootImp.invalidateChildInParent:發現終於返回了null,結束了迴圈。
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();
if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);
if (mCurScrollY != 0 || mTranslator != null) {
mTempRect.set(dirty);
dirty = mTempRect;
if (mCurScrollY != 0) {
dirty.offset(0, -mCurScrollY);
}
if (mTranslator != null) {
mTranslator.translateRectInAppWindowToScreen(dirty);
}
if (mAttachInfo.mScalingRequired) {
dirty.inset(-1, -1);
}
}
invalidateRectOnScreen(dirty);
return null;
}
invalidateRectOnScreen(dirty);又呼叫了誰,繼續定位
可以查原始碼分析就不一步步寫了,schuduleTraversals()呼叫是固定最後呼叫到performTraversals函式,這個是view繪製的源頭開始處,程式碼很多,裡面呼叫了mView.measure() mView.layout 、mView.draw等方法。終於差不多看到希望了,mView.draw()就是呼叫了DecorView的draw(),然後就把這個佈局遍歷繪製了一遍。那麼走到執行動畫的View的其父View繪製draw方法時候,會走到dispatchDraw,在View.draw(canvas)裡面按步驟分別回執如下1、2、3、4、5、6步,其中有dispatchDraw來繪製子View。
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
ViewGroup過載了dispatchDraw,實現繪製子View的內容,原始碼如下:
protected void dispatchDraw(Canvas canvas) {
boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
int flags = mGroupFlags;
//執行佈局動畫
if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate(){
final boolean buildCache = !isHardwareAccelerated();
for (int i = 0; i < childrenCount; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
final LayoutParams params = child.getLayoutParams();
attachLayoutAnimationParameters(child, params, i, childrenCount);
bindLayoutAnimation(child);
}
}
final LayoutAnimationController controller = mLayoutAnimationController;
if (controller.willOverlap()) {
mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;
}
controller.start();
mGroupFlags &= ~FLAG_RUN_ANIMATION;
mGroupFlags &= ~FLAG_ANIMATION_DONE;
if (mAnimationListener != null) {
mAnimationListener.onAnimationStart(controller.getAnimation());
}
}
*******省略部分程式碼 *********
// We will draw our child's animation, let's reset the flag
//more為true表示動畫沒有執行完
boolean more = false;
final long drawingTime = getDrawingTime();
*******省略部分程式碼 *********
for (int i = 0; i < childrenCount; i++) {
*******省略部分程式碼 *********
int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null) ? children[childIndex] :
preorderedList.get(childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
//繪製子View,直接呼叫了child.draw(canvas, this(ViewParent), drawingTime);
// 動畫真正執行地方
more |= drawChild(canvas, child, drawingTime);
}
}
*******省略部分程式碼 *********
// mGroupFlags might have been updated by drawChild()
flags = mGroupFlags;
if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {
invalidate(true);
}
*******省略部分程式碼 *********
}
看到呼叫child.draw(Canvas canvas, ViewGroup parent, long drawingTime),這個draw(canvas)函式不一樣,不過前者又會呼叫到後者。看下child.draw(Canvas canvas, ViewGroup parent, long drawingTime)是如何執行動畫呼叫到animattion.applyTransformation()對Matrix進行矩陣變化,原始碼如下:
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
****************省略部分程式碼 ***************
boolean more = false;
Transformation transformToApply = null;
boolean concatMatrix = false;
final boolean scalingRequired = mAttachInfo != null && mAttachInfo.mScalingRequired;
final Animation a = getAnimation();//獲取ziView的動畫,在View.startAnimation是就開始賦值了。
if (a != null) {
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
concatMatrix = a.willChangeTransformationMatrix();
if (concatMatrix) {
mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
}
transformToApply = parent.getChildTransformation();// a.applyLegacyAnimation進行賦值
} else {
****************省略程式碼 ***************
}
****************省略程式碼 ***************
if (transformToApply != null || alpha < 1 || !
hasIdentityMatrix() || (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) {
if (transformToApply != null || !childHasIdentityMatrix) {
int transX = 0;
int transY = 0;
if (offsetForScroll) {
transX = -sx;
transY = -sy;
}
if (transformToApply != null) {
if (concatMatrix) {
if (drawingWithRenderNode) {
renderNode.setAnimationMatrix(transformToApply.getMatrix());
} else {
// Undo the scroll translation, apply the transformation matrix, // then redo the scroll translate to get the correct result.
canvas.concat(transformToApply.getMatrix());//傳遞給canvas進行矩陣實現動畫
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;
}
}
if (!childHasIdentityMatrix && !drawingWithRenderNode) {
canvas.translate(-transX, -transY);
canvas.concat(getMatrix());
canvas.translate(transX, transY);
}
}
****************省略程式碼 ***************
return more;
}
在這麼程式碼中動畫animation判斷不為空之後,進入more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);,這裡面傳入了animation,有沒有對動畫執行操作呢,如下原始碼:
private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
Animation a, boolean scalingRequired) {
Transformation invalidationTransform;
*********省略部分程式碼 **********
final Transformation t = parent.getChildTransformation();
boolean more = a.getTransformation(drawingTime, t, 1f);///呼叫了Animation的
///getTransformation方法,這個方法進而呼叫了applyTransformation
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) {//為true又重新重新整理,呼叫viewparent.invalidate
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) {
// The child need to draw an animation, potentially offscreen, so
// make sure we do not cancel invalidate requests
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);
// The child need to draw an animation, potentially offscreen, so
// make sure we do not cancel invalidate requests
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;
}
從 view.applyLegacyAnimation()原始碼可以看到呼叫了animation.getChildTransformation()並通過返回的boolean值覆蓋more變數,並且同時把對傳入animation.getTransformation(drawingTime, t, 1f)中的transformToApply(parent.getChildTransformation()獲取)進行變化矩陣,當more為true,看到又呼叫父View parent.invalidate(),重新重新整理view樹進行再一次繪製剩餘動畫內容。當applyLegacyAnimation執行完成之後draw(Canvas canvas, ViewGroup parent, long drawingTime)裡面繼續transformToApply = parent.getChildTransformation()獲取變化之後transformToApply,若不為空,又傳遞給canvas.concat(transformToApply.getMatrix())來對canvas進行矩陣變化操作,從而對繪製的view內容發生動畫。
到這裡就講完了view tween動畫的執行原理