前言:不要在努力拼搏的年紀去選擇安逸享樂,爲了不讓生活留下遺憾和後悔,我們要抓住一切改變生活的機會,生命不止,奮鬥不息。
一、概述
在前面的文章中我們詳細講解了補間動畫(Tween Animation)的使用,系統爲我們封裝了幾個基本的動畫,也就是ScaleAnimation(縮放)、AlphaAnimation(透明)、RotateAnimation(旋轉)、TranslateAnimation(平移),它們都是繼承了Animation類,然後實現了applyTransformation()方法,然後通過Transformation轉換類和Matrix矩形類實現了各種各樣的動畫效果。
那麼補間動畫執行的原理是怎麼樣的呢?我們從源碼的角度查看這個問題,查看源碼我們帶着問題去看去查找,避免大海撈針被轉暈。我們先來看看動畫的使用,這裏以RotateAnimation(旋轉)爲例:
//創建RotateAnimation實例
RotateAnimation rotateAnimation = new RotateAnimation(0f, -650f,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
//設置動畫時間2000毫秒
rotateAnimation.setDuration(2000);
//設置保持動畫結束時的狀態
rotateAnimation.setFillAfter(true);
//控件綁定動畫
mTextView.startAnimation(rotateAnimation);
效果如下:
這裏圍繞自身旋轉逆時針方向650度,從代碼看到是通過mTextView.startAnimation(rotateAnimation)將動畫實例通過參數傳給控件,然後控件就可以動畫了。
二、動畫執行源碼分析
1、View樹遍歷(動畫執行前)
我們順藤摸瓜點擊mTextView.startAnimation(rotateAnimation)方法進入源碼,
//View.java
public void startAnimation(Animation animation) {
animation.setStartTime(Animation.START_ON_FIRST_FRAME);
setAnimation(animation);
invalidateParentCaches();
invalidate(true);
}
這裏有四個方法,我們一個個來看,首先是通過setStartTime()設置了動畫的開始時間,
//View.java
public void setStartTime(long startTimeMillis) {
mStartTime = startTimeMillis;
mStarted = mEnded = false;
mCycleFlip = false;
mRepeated = 0;
mMore = true;
}
這裏只是對一些變量進行賦值,再來看看下一個方法,設置動畫setAnimation(animation),點擊進去看源碼:
//View.java
public void setAnimation(Animation animation) {
mCurrentAnimation = animation;
if (animation != null) {
if (mAttachInfo != null && mAttachInfo.mDisplayState == Display.STATE_OFF
&& animation.getStartTime() == Animation.START_ON_FIRST_FRAME) {
animation.setStartTime(AnimationUtils.currentAnimationTimeMillis());
}
animation.reset();
}
}
這裏面也是將動畫實例賦值給當前的成員變量,還沒有動畫的邏輯,我們回到上一層startAnimation()方法裏查看下一個方法invalidateParentCaches(),
//View.java
protected void invalidateParentCaches()
if (mParent instanceof View) {
((View) mParent).mPrivateFlags |= PFLAG_INVALIDATED;
}
}
可以看到這裏僅僅是設置動畫標記,在視圖構建或者屬性改變時是必要的,再回到startAnimation()方法裏面invalidate(true)
//View.java
public 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) {
if (mGhostView != null) {
mGhostView.invalidate(true);
return;
}
.................
// 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);
}
}
}
這裏代碼太多我就貼出重要的一部分,這裏着重看p.invalidateChild(this, damage),
//ViewGroup.java
@Deprecated
@Override
public final void invalidateChild(View child, final Rect dirty) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null && attachInfo.mHardwareAccelerated) {
// HW accelerated fast path
onDescendantInvalidated(child, child);
return;
}
ViewParent parent = this;
.........
do {
View view = null;
if (parent instanceof View) {
view = (View) parent;
}
.........
parent = parent.invalidateChildInParent(location, dirty);
} while (parent != null);
}
}
因爲ViewParent p = mParent,this是View的子類ViewGroup,所以p.invalidateChild(this, damage)裏面其實是調用了ViewGroup的invalidateChild(),這裏有一個do{}while()循環,第一次的時候parent = this即ViewGroup,然後調用parent.invalidateChildInParent(location, dirty)方法,當parent == null的時候結束循環。
在public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {}方法中,只要條件成立就會返回mParent
//ViewGroup.java
@Deprecated
@Override
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) {
.......
return mParent;
}
return null;
}
((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0)是保持成立的,所以會一直返回mParent,那麼說明View的mParent是ViewGroup,ViewGroup的mParent也是ViewGroup,而do{}while()循環一直找mParent,而一顆View最頂端的mParent是ViewRootImpl,所以最後走到ViewRootImpl的invalidateChildInParent()裏面。
我們知道在onCreate()方法裏面通過setContentView()將佈局添加到以DecorView爲根佈局的一個ViewGroup裏面,因爲在onResume()執行完成後,WindowManager會執行addView()方法,然後會創建一個ViewRootImpl對象,與DecorView綁定起來,DecorView的mParent設置成ViewRootImpl,ViewRootImpl實現了ViewParent接口,所以ViewRootImpl雖然沒有繼承View或者ViewGroup,但是DecorView的mParent。我們找到ViewRootImpl的invalidateChildInParent()方法中,
//ViewRootImpl.java
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();
if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);
if (dirty == null) {
invalidate();
return null;
} else if (dirty.isEmpty() && !mIsAnimating) {
return null;
}
.......
invalidateRectOnScreen(dirty);
return null;
}
從下面的源碼得知,這裏所有的返回值都變爲null了,之前執行的do{}while()循壞也會停止,我們點擊invalidateRectOnScreen(dirty)方法進去看看,接着進入 scheduleTraversals()方法中,
//ViewRootImpl.java
private void invalidateRectOnScreen(Rect dirty) {
......
if (!mWillDrawSoon && (intersected || mIsAnimating)) {
scheduleTraversals();
}
}
//ViewRootImpl.java
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
主要看mTraversalRunnable,我們找到mTraversalRunnable這個類,
//ViewRootImpl.java
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
我們打開doTraversal()方法,
//ViewRootImpl.java
void doTraversal() {
.......
performTraversals();
.......
}
}
所以說,scheduleTraversals()是將 performTraversals()放到一個Runnable裏面,在Choreographer的帶執行對列裏面,這些待執行的Runable會在最近的一個16.6ms屏幕刷新信號到來的時候執行,而performTraversals()是View的三大操作:測量、佈局、繪製的發起者。
在Android屏幕刷新機制裏,View樹裏面不管哪個View發起的繪製請求或者佈局請求都會走到ViewRootImpl的scheduleTraversals()裏面,然後在最新的一個屏幕刷新信號來到時,再通過ViewRootImpl的performTraversals()從根佈局DecorView依次遍歷View樹去執行測量、佈局、繪製三大操作,這也是爲什麼要求佈局層次不能太深,因爲每一次的刷新都會走到ViewRootImpl裏面,然後在層層遍歷到發生改變的VIew裏去執行相應的佈局和繪製操作。
所以在mTextView.startAnimation(rotateAnimation)源碼分析的這個過程中,執行動畫會內部會調用View的重繪操作invalidate(),最終走到ViewRootImpl的scheduleTraversals(),在下一個屏幕刷新信號到來的時候遍歷View樹刷新屏幕。得出結論:
在調用View.startAnimation(rotateAnimation)後,並沒有立即執行動畫,而是做了一下變量初始化操作,將View和Animation綁定起來,調用重繪操作,內部層層尋找mPartent,最終在ViewRootImpl的scheduleTraversals()發起一個遍歷View樹的請求,在最近一個屏幕信號刷新到來時執行這個請求,調用performTraversals()從根佈局去遍歷View樹。
2、動畫的執行
那麼真正動畫的執行是在ViewRootImpl發起的遍歷View樹的過程中,View顯示在屏幕的過程中的測量、佈局、繪製都是有ViewRootImpl的performTraversals()控制,這個View樹的三個操作,只能通過層層遍歷,所以基本操作的執行都會是一次遍歷股過程。
//View.java
public void draw(Canvas canvas) {
/*
* 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)
*/
}
View遍歷的實現是在View#Draw()方法裏面執行的,這個方法大體上就做了六件事,當前View需要繪製,就會去調用自己的onDraw()方法,如果有子View就會去調用dispatchDraw()將繪製事件通知子View,ViewGroup重寫了dispatchDraw(),調用了drawChild(),然後drawChild()調用子View的draw(Canvas, ViewGroup, long),而這個方法又會調用到draw(Canvas)方法,所以達到了遍歷的效果。在測量和佈局的方法中都沒有動畫的相關代碼,但是在draw(Canvas, ViewGroup, long)發現了動畫的相關代碼:
//View.java
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
......
boolean more = false;
......
Transformation transformToApply = null;
......
final Animation a = getAnimation();
if (a != null) {
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
concatMatrix = a.willChangeTransformationMatrix();
if (concatMatrix) {
mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
}
......
return more;
}
這裏的getAnimation()其實就是上面View.startAnimation(Animation)時傳進來的成員變量,
//View.java
public Animation getAnimation() {
return mCurrentAnimation;
}
我們點進去applyLegacyAnimation()方法中,
//View.java
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;
}
.....
return more;
}
從上面看到a.initialize()和onAnimationStart()有動畫的初始化和開始,我們跟着getTransformation()方法看到動畫的核心就在這裏,
View.java
這個方法主要做了記錄動畫第一幀的時間,計算動畫進度值,控制動畫進度在0-1之間,根據插值器計算動畫的實際進度(可以參考《Android動畫篇(四)—— 屬性動畫ValueAnimator的高級進階》),最後纔在applyTransformation()執行動畫的具體邏輯,在子類的具體實現相應的動畫的邏輯。我們來看看ScaleAnimation的applyTransformation()具體做了什麼。
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);
}
}
在applyTransformation()根據傳入的比例參數計算需要操作的動畫參數,然後由Transformation 做動畫操作。其他的子類AlphaAnimation、RotateAnimation、TranslateAnimation都是會重寫這個方法並實現具體的動畫邏輯,這裏就不一一舉例了。
但是問題來了,applyTransformation()是動畫不斷回調的地方,根據傳入的參數實現動畫在運行中,而applyTransformation()被調用的地方沒有循環,那麼一次View樹的遍歷繪製也就執行一次動畫?它是怎麼被多次回調的?
在上面知道applyTransformation()在getTransformation()裏面調用,而這個方法有一個boolean返回值,我們看看返回的邏輯是什麼,
View.java
這裏的mMore其實是動畫的完成情況,true表示動畫未完成,false表示動畫已完成或者已取消。我們去看看那裏使用了這個方法的返回值,在applyLegacyAnimation()中找到mMore的使用,applyLegacyAnimation()中返回的more決定了動畫是否執行完,如果返回爲false,那麼將會結束動畫。
View.java
當動畫還沒執行完,就會在調用invalidate()方法,層層通知ViewRootImpl在發起一次遍歷請求,當下一個屏幕刷新信號的時候,再通過performTraversals()遍歷View樹繪製,view的draw()方法被執行,會再次調用applyLegacyAnimation()方法執行相關動畫操作,包括getTransformation()計算動畫進度,applyTransformation()執行動畫邏輯等。
動畫的執行流程這裏就基本完了,文章篇幅過長,我們來總結一下:
1、當調用View.startAnimation(Animation)時,並沒有立即執行動畫,而是通過invalidate()層層通過到ViewRootImpl發起一次遍歷View樹的請求,在接收到下一個(16ms)屏幕信號刷新時才發起遍歷View樹的繪製操作,從DecorView開始遍歷,繪製時會調用draw()方法,如果View有綁定動畫則執行applyLegacyAnimation()方法處理相關動畫邏輯。
2、在applyLegacyAnimation()裏面,先執行初始化initialize(),再通知動畫開始onAnimationStart(),然後通過getTransformation()計算動畫進度,並且它的返回值和動畫是否結束決定是否繼續通知ViewRootImpl發起遍歷請求,view樹繪製,如此重複這個步驟,並且調用applyTransformation()方法執行動畫的邏輯,直到動畫結束。
其實補間動畫僅僅改變的是控件的顯示位置,並沒有改變控件本身的值,View Animation動畫是通過Parent View實現的,在view被draw時Parent View改變它的繪製參數,這樣view的大小位置角度等雖然變化了,但是view的實際屬性並沒有改變。可參考《Android動畫篇(三)—— 屬性動畫ValueAnimator的使用》
至此,本文結束!
請尊重原創者版權,轉載請標明出處: https://blog.csdn.net/m0_37796683/article/details/90904394 謝謝!
動畫系列文章:
1、 Android動畫篇(一)—— alpha、scale、translate、rotate、set的xml屬性及用法
- 補間動畫的XML用法以及屬性詳解
2、Android動畫篇(二)—— 代碼實現alpha、scale、translate、rotate、set及插值器動畫
- 代碼動態實現補間動畫以及屬性詳解
3、 Android動畫篇(三)—— 屬性動畫ValueAnimator的使用
- ValueAnimator的基本使用
4、 Android動畫篇(四)—— 屬性動畫ValueAnimator的高級進階
- 插值器(Interpolator)、計算器(Evaluator)、ValueAnimator的ofObject用法等相關知識
5、 Android動畫篇(五)—— 屬性動畫ObjectAnimator基本使用
- ObjectAnomator的基本使用以及屬性詳解
6、 Android動畫篇(六)—— 組合動畫AnimatorSet和PropertyValuesHolder的使用
- AnimatorSet動畫集合和PropertyValuesHolder的使用
以上幾篇動畫文章是一定要掌握的,寫的不好請多多指出!