一系宜、來(lái)源
1. performTraversals()相關(guān)
performTraversals()是ViewRootImpl的一個(gè)方法.
每個(gè)ViewRootImpl都會(huì)管理一條View鏈中所有View前翎,一個(gè)Window可能存在多個(gè)View鏈,所以可能存在多個(gè)ViewRootImpl全蝶。比如以DecorView為根的View鏈,在DecorView中增刪改子View都會(huì)用到ViewRootImpl署尤。
搜索performTraversals(),就只有一個(gè)引用什往。
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 對(duì)應(yīng)下面的 postSyncBarrier()
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
// 這里
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
在一個(gè)runnable的run()實(shí)現(xiàn)中扳缕。
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
看出來(lái)performTraversals()是在一個(gè)runnable中被調(diào)用的,通過(guò)將這個(gè)runnable加入隊(duì)列來(lái)執(zhí)行别威。scheduleTraversals()這個(gè)方法被調(diào)用的地方就很多了躯舔。
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//暫停了handler的后續(xù)消息處理,防止界面刷新的時(shí)候出現(xiàn)同步問(wèn)題
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//將runnable發(fā)送給handler執(zhí)行
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
performTraversals()作為三大流程的起點(diǎn)省古,創(chuàng)建粥庄、參數(shù)改變、界面刷新等時(shí)都有可能會(huì)需要從根部開(kāi)始measure豺妓、layout惜互、draw布讹,就會(huì)調(diào)用到它。
2. performTraversals()起點(diǎn)
在創(chuàng)建了ViewRootImpl后训堆,需要將其與DecorView關(guān)聯(lián)起來(lái)描验,會(huì)調(diào)用到ViewRootImpl中的setView()。setView()中會(huì)調(diào)用到requestLayout()坑鱼。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
mAttachInfo.mDisplayState = mDisplay.getState();
// ...
mAdded = true;
int res; /* = WindowManagerImpl.ADD_OKAY; */
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
// ...
}
}
}
requestLayout()中調(diào)用scheduleTraversals()從而開(kāi)始了traversals的過(guò)程膘流。
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
二、實(shí)現(xiàn)
因?yàn)榉椒ū容^長(zhǎng)鲁沥,所以分成以下三個(gè)部分呼股。
1、計(jì)算窗口期望尺寸
開(kāi)始的條件
final View host = mView;
// mAdded指DecorView是否被成功加入到window中画恰,在setView()中被賦值為true
if (host == null || !mAdded)
return;
初始化窗口的期望寬高彭谁,并初始幾個(gè)標(biāo)記
- 如果是第一次traversals
- 設(shè)置需要重新draw和layout
- 根據(jù)layoutParams的type去判斷是否需要使用屏幕完全尺寸
- Y:設(shè)置窗口尺寸為屏幕真實(shí)尺寸,不剪去任何裝飾的尺寸
- N:設(shè)置窗口尺寸為可用的最大尺寸
// mFirst在構(gòu)造器中被賦值true阐枣,表示第一次traversals
// 在后面的代碼中被賦值false
if (mFirst) {
// 設(shè)置需要全部重新draw并且重新layout
mFullRedrawNeeded = true;
mLayoutRequested = true;
final Configuration config = mContext.getResources().getConfiguration();
// 初始化期望窗口長(zhǎng)寬
if (shouldUseDisplaySize(lp)) {
// NOTE -- system code, won't try to do compat mode.
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
// 設(shè)置窗口尺寸為可用的最大尺寸
desiredWindowWidth = dipToPx(config.screenWidthDp);
desiredWindowHeight = dipToPx(config.screenHeightDp);
}
}
- 如果不是第一次traversals
- 設(shè)置窗口寬高為全局變量存儲(chǔ)的寬高马靠。
- 如果這個(gè)值和上次traversals時(shí)的值不同了
- Y:設(shè)置需要重新layout和draw奄抽。
- N:默認(rèn)不需要重新進(jìn)行蔼两。
- 如果這個(gè)值和上次traversals時(shí)的值不同了
- 設(shè)置窗口寬高為全局變量存儲(chǔ)的寬高马靠。
// 全局變量
Rect frame = mWinFrame;
if () {
// ......
} else {
// 如果不是第一次traversals,就直接使用之前存儲(chǔ)的mWinFrame的寬高
desiredWindowWidth = frame.width();
desiredWindowHeight = frame.height();
// mWidth和mHeight是上一次traversals時(shí)賦frame的值的逞度。
// 如果現(xiàn)在的值不一樣了额划,那么就需要重新draw和layout
if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
mFullRedrawNeeded = true;
mLayoutRequested = true;
windowSizeMayChange = true;
}
}
mWidth和mHeight也是用來(lái)描述Activity窗口當(dāng)前寬度和高度的,它們的值是由應(yīng)用程序進(jìn)程上一次主動(dòng)請(qǐng)求WindowManagerService計(jì)算得到的档泽,并且會(huì)一直保持不變到應(yīng)用程序進(jìn)程下一次再請(qǐng)求WindowManagerService重新計(jì)算為止(這個(gè)在下文的代碼中也有所體現(xiàn))俊戳。
如果Activity窗口不是第一次被請(qǐng)求執(zhí)行測(cè)量、布局和繪制操作馆匿,計(jì)算得到的寬度mWidth和高度mHeight不等于Activity窗口的當(dāng)前寬度desiredWindowWidth和當(dāng)前高度desiredWindowHeight抑胎,那么就說(shuō)明Activity窗口的大小發(fā)生了變化,這時(shí)候重新標(biāo)記渐北,以便接下來(lái)可以對(duì)Activity窗口的大小變化進(jìn)行處理阿逃。
再賦值窗口期望大小并開(kāi)始測(cè)量流程
要求重新測(cè)量時(shí)。
- 如果是第一次traversals
- 確保窗口的觸摸模式已經(jīng)打開(kāi)赃蛛。
boolean insetsChanged = false;
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
if (layoutRequested) {
final Resources res = mView.getContext().getResources();
// 如果是第一次
if (mFirst) {
// make sure touch mode code executes by setting cached value
// to opposite of the added touch mode.
mAttachInfo.mInTouchMode = !mAddedTouchMode;
// 確保window的觸摸模式已經(jīng)打開(kāi)
// 內(nèi)部 mAttachInfo.mInTouchMode = inTouchMode
ensureTouchModeLocally(mAddedTouchMode);
} else {
// ...
}
// ...
}
- 如果不是第一次traversals恃锉。
- 比較mPending..Insets和上次存在mAttachInfo中的是否改變。
- 如果改變:insetsChanged置為true呕臂。
- 未改變:insetsChanged默認(rèn)false破托。
- 如果窗口寬高有設(shè)置為wrap_content
- 設(shè)置windowSizeMayChange為true。
- 重新計(jì)算窗口期望寬高歧蒋。
- 調(diào)用measureHierarchy()去測(cè)量窗口寬高土砂,賦值窗口尺寸是否改變州既。
- 比較mPending..Insets和上次存在mAttachInfo中的是否改變。
insets是屏幕中顯示東西區(qū)域周?chē)囊蝗Γ?lèi)似于狀態(tài)欄瘟芝,WMS實(shí)際上就是需要根據(jù)屏幕以及可能出現(xiàn)的狀態(tài)欄和輸入法窗口的大小來(lái)計(jì)算出Activity窗口的整體大小及其過(guò)掃描區(qū)域邊襯和可見(jiàn)區(qū)域邊襯的大小易桃。
boolean insetsChanged = false;
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
if (layoutRequested) {
final Resources res = mView.getContext().getResources();
if (mFirst) {
// ...
} else {
// mPending...Insets是這一次請(qǐng)求traversals還未生效的值
// mAttachInfo中的值是上一次traversals時(shí)保存的值
// 比較兩者看是否有變化,如果有變化就將insetsChanged置為true锌俱。
if (!mPendingOverscanInsets.equals(mAttachInfo.mOverscanInsets)) {
insetsChanged = true;
}
if (!mPendingContentInsets.equals(mAttachInfo.mContentInsets)) {
insetsChanged = true;
}
if (!mPendingStableInsets.equals(mAttachInfo.mStableInsets)) {
insetsChanged = true;
}
if (!mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) {
mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: "
+ mAttachInfo.mVisibleInsets);
}
if (!mPendingOutsets.equals(mAttachInfo.mOutsets)) {
insetsChanged = true;
}
if (mPendingAlwaysConsumeNavBar != mAttachInfo.mAlwaysConsumeNavBar) {
insetsChanged = true;
}
// 如果將窗口的寬或高設(shè)置為wrap_content了晤郑,最終還是會(huì)變?yōu)槠聊淮笮? if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
|| lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
// 窗口大小可能改變,windowSizeMayChange設(shè)置為true
windowSizeMayChange = true;
// 和前面一樣贸宏,判斷Activity是否含有狀態(tài)欄造寝,相應(yīng)的賦值窗口的期望寬高
if (shouldUseDisplaySize(lp)) {
// NOTE -- system code, won't try to do compat mode.
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
Configuration config = res.getConfiguration();
desiredWindowWidth = dipToPx(config.screenWidthDp);
desiredWindowHeight = dipToPx(config.screenHeightDp);
}
}
}
// Ask host how big it wants to be
// 會(huì)調(diào)用performMeasure()去確定window的大小,返回窗口大小是否會(huì)改變
// 這里其實(shí)就是測(cè)量流程的入口
// host: Decor lp: window attr rs: decor res
// desiredWindowWidth/Height: 上面初始的窗口期望寬高
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
}
判斷是否重置了窗口尺寸吭练,是否需要重新計(jì)算insets
- 清空mLayoutRequested诫龙,方便后面用來(lái)做判斷。
- 設(shè)置windowShouldResize鲫咽。
- 設(shè)置computesInternalInsets签赃。
if (layoutRequested) {
// 暫時(shí)清空這個(gè)標(biāo)記,在下面再次需要的時(shí)候再重新賦值分尸。
// Clear this now, so that if anything requests a layout in the
// rest of this function we will catch it and re-run a full
// layout pass.
mLayoutRequested = false;
}
// 同時(shí)滿(mǎn)足三個(gè)條件
// layoutRequested為true锦聊,已經(jīng)發(fā)起了一次新的layout。
// 上面賦值的窗口尺寸可能發(fā)生改變
// 上面measureHierarchy()中測(cè)量的值和上一次保存的值不同 或
// 寬或高設(shè)置為wrap_content并且這次請(qǐng)求WMS的值和期望值箩绍、上次的值都不同
boolean windowShouldResize = layoutRequested && windowSizeMayChange
&& ((mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight())
|| (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT &&
frame.width() < desiredWindowWidth && frame.width() != mWidth)
|| (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
frame.height() < desiredWindowHeight && frame.height() != mHeight));
windowShouldResize |= mDragResizing && mResizeMode == RESIZE_MODE_FREEFORM;
// 如果activity重新啟動(dòng)
windowShouldResize |= mActivityRelaunched;
// 設(shè)置是否需要計(jì)算insets孔庭,設(shè)置了監(jiān)聽(tīng)或存在需要重新設(shè)置的空insets
final boolean computesInternalInsets =
mAttachInfo.mTreeObserver.hasComputeInternalInsetsListeners()
|| mAttachInfo.mHasNonEmptyGivenInternalInsets;
2、 確定窗口尺寸材蛛,調(diào)用WMS計(jì)算并保存
// 第一次traversals 或 窗口尺寸有變化 或 insets有變化 或 窗口visibility有變化
// 或 窗口屬性有變化 或 強(qiáng)迫窗口下一次重新layout
if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
// 清空這個(gè)標(biāo)記位
mForceNextWindowRelayout = false;
// ...
} else {
// ...
}
進(jìn)入if代碼塊
進(jìn)入條件(或)
- 第一次traversals
- 窗口尺寸有變化
- insets有變化
- 窗口visibility有變化
- 窗口屬性有變化
- 設(shè)置了強(qiáng)迫下一次必須重新layout
1.設(shè)置insetsPending
if (isViewVisible) {
// 如果insets發(fā)生改變 并且 是第一次traversals或窗口從不可見(jiàn)變?yōu)榭梢?jiàn)
// 就置insetsPending為true
insetsPending = computesInternalInsets && (mFirst || viewVisibilityChanged);
}
2.調(diào)用WMS重新計(jì)算并保存
try {
// ...
// 調(diào)用relayoutWindow()重新計(jì)算窗口尺寸以及insets大小
// 會(huì)使用IPC去請(qǐng)求 WMS
// params: window attr view可見(jiàn)性 是否有額外的insets
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
//...
// 比較這次計(jì)算和上次計(jì)算的值是否發(fā)生了改變
final boolean overscanInsetsChanged = !mPendingOverscanInsets.equals(
mAttachInfo.mOverscanInsets);
contentInsetsChanged = !mPendingContentInsets.equals(
mAttachInfo.mContentInsets);
final boolean visibleInsetsChanged = !mPendingVisibleInsets.equals(
mAttachInfo.mVisibleInsets);
// ...
final boolean alwaysConsumeNavBarChanged =
mPendingAlwaysConsumeNavBar != mAttachInfo.mAlwaysConsumeNavBar;
// 如果發(fā)生了改變圆到,就會(huì)進(jìn)行重新賦值等操作
if (contentInsetsChanged) {
mAttachInfo.mContentInsets.set(mPendingContentInsets);
}
if (overscanInsetsChanged) {
mAttachInfo.mOverscanInsets.set(mPendingOverscanInsets);
contentInsetsChanged = true;
}
// ...
if (visibleInsetsChanged) {
mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
}
// ...
} catch (RemoteException e) {
}
// frame指向的是mWinFrame, 此時(shí)已經(jīng)是上面重新請(qǐng)求WMS計(jì)算后的值了
// 將值保存在mAttachInfo中
mAttachInfo.mWindowLeft = frame.left;
mAttachInfo.mWindowTop = frame.top;
// 如果前一次計(jì)算的值和這次計(jì)算的值有變化就重新賦值
if (mWidth != frame.width() || mHeight != frame.height()) {
mWidth = frame.width();
mHeight = frame.height();
}
3.是否需要重新測(cè)量
- 如果窗口不處于停滯狀態(tài)或提交了下一次的繪制
- 如果需要重新測(cè)量窗口尺寸
- 執(zhí)行測(cè)量操作
- 如果有額外分配空間
- 計(jì)算寬高上的額外空間加到上次計(jì)算的寬高上
- 重新測(cè)量
- layoutRequested賦值true;
- 如果需要重新測(cè)量窗口尺寸
// 如果窗口不處于停止?fàn)顟B(tài)或者提交了下一次的繪制
if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
// 判斷是否需要重新測(cè)量窗口尺寸
// 窗口觸摸模式發(fā)生改變,焦點(diǎn)發(fā)生改變
// 或 測(cè)量寬高與WMS計(jì)算的寬高不相等
// 或 insets改變了
// 或 配置發(fā)生改變,mPendingMergedConfiguration有變化
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
// 重新計(jì)算decorView的MeasureSpec
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
// 執(zhí)行測(cè)量操作
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;
// 判斷是否需要重新測(cè)量
// 如果需要在水平方向上分配額外的像素
if (lp.horizontalWeight > 0.0f) {
// 測(cè)量寬度加上額外的寬度
width += (int) ((mWidth - width) * lp.horizontalWeight);
// 重新計(jì)算MeasureSpec
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
// 設(shè)置需要重新測(cè)量
measureAgain = true;
}
// 同上
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}
// 如果需要重新測(cè)量了,就載調(diào)用一次performMeasure
if (measureAgain) {
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
layoutRequested = true;
}
}
進(jìn)入else代碼塊
進(jìn)入條件(且)
- 不是第一次traversals
- 各種參數(shù)都沒(méi)有改變
這里主要是來(lái)判斷窗口可能移動(dòng)的情況。
// Not the first pass and no window/insets/visibility change but the window
// may have moved and we need check that and if so to update the left and right
// in the attach info. We translate only the window frame since on window move
// the window manager tells us only for the new frame but the insets are the
// same and we do not want to translate them more than once.
// 檢查窗口發(fā)生移動(dòng)的情況
maybeHandleWindowMove(frame);
maybeHandleWindowMove()的實(shí)現(xiàn)
- 判斷窗口是否發(fā)生移動(dòng)
- 判斷是否有動(dòng)畫(huà)
- 執(zhí)行動(dòng)畫(huà)
- 改變mAttachInfo中的參數(shù)
- 判斷是否有動(dòng)畫(huà)
private void maybeHandleWindowMove(Rect frame) {
// frame和窗口做了同樣的移動(dòng), 這樣就會(huì)導(dǎo)致很多參數(shù)沒(méi)有改變, 但是我們還是需要做一些工作
// 比較檢查窗口是否發(fā)生移動(dòng)
final boolean windowMoved = mAttachInfo.mWindowLeft != frame.left
|| mAttachInfo.mWindowTop != frame.top;
if (windowMoved) {
if (mTranslator != null) {
// 如果窗口發(fā)生了移動(dòng), 并且存在動(dòng)畫(huà), 就執(zhí)行動(dòng)畫(huà)
mTranslator.translateRectInScreenToAppWinFrame(frame);
}
// 改變位置
mAttachInfo.mWindowLeft = frame.left;
mAttachInfo.mWindowTop = frame.top;
}
// ...
}
3卑吭、 調(diào)用layout和draw流程
layout
- 如果需要layout芽淡,并且窗口不是停止?fàn)顟B(tài)或提交了下一次的draw
- 調(diào)用performLayout()開(kāi)始layout流程
- 處理透明區(qū)域
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
// 開(kāi)始layout流程
performLayout(lp, mWidth, mHeight);
// 處理透明區(qū)域
if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
// ...
}
}
處理insets
if (computesInternalInsets) {
final ViewTreeObserver.InternalInsetsInfo insets = mAttachInfo.mGivenInternalInsets;
// 重置insets樹(shù), 清空狀態(tài)
insets.reset();
// 遍歷計(jì)算
mAttachInfo.mTreeObserver.dispatchOnComputeInternalInsets(insets);
mAttachInfo.mHasNonEmptyGivenInternalInsets = !insets.isEmpty();
if (insetsPending || !mLastGivenInsets.equals(insets)) {
mLastGivenInsets.set(insets);
// 轉(zhuǎn)換成屏幕坐標(biāo)
final Rect contentInsets;
final Rect visibleInsets;
final Region touchableRegion;
if (mTranslator != null) {
contentInsets = mTranslator.getTranslatedContentInsets(insets.contentInsets);
visibleInsets = mTranslator.getTranslatedVisibleInsets(insets.visibleInsets);
touchableRegion = mTranslator.getTranslatedTouchableArea(insets.touchableRegion);
} else {
contentInsets = insets.contentInsets;
visibleInsets = insets.visibleInsets;
touchableRegion = insets.touchableRegion;
}
// 遠(yuǎn)程調(diào)用WMS去設(shè)置insets
try {
mWindowSession.setInsets(mWindow, insets.mTouchableInsets,
contentInsets, visibleInsets, touchableRegion);
} catch (RemoteException e) {
}
}
}
收尾工作
mFirst賦值為false,判斷是否需要設(shè)置mReportNextDraw豆赏,這些工作可以看作是為了下一次調(diào)用traversals準(zhǔn)備的挣菲。
// 設(shè)置不是第一次, 之后再次調(diào)用traversals
mFirst = false;
mWillDrawSoon = false;
mNewSurfaceNeeded = false;
mActivityRelaunched = false;
mViewVisibility = viewVisibility;
mHadWindowFocus = hasWindowFocus;
// ...
if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
reportNextDraw();
}
draw
在第一次traverslas時(shí)并不會(huì)調(diào)用performDraw(),因?yàn)閯?chuàng)建了新的平面河绽。scheduleTraversals()中會(huì)post一個(gè)runnable己单,會(huì)再次調(diào)用performTraversals(),等到那次調(diào)用時(shí)才會(huì)去執(zhí)行draw流程耙饰。
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
// 沒(méi)有取消draw也沒(méi)有創(chuàng)建新的平面 第一次traversals時(shí)newSurface為true
if (!cancelDraw && !newSurface) {
// 如果還存在等待執(zhí)行的動(dòng)畫(huà), 就遍歷執(zhí)行它們
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
// 開(kāi)始draw流程
performDraw();
} else {
if (isViewVisible) {
// Try again
// 如果是可見(jiàn)的, 就再調(diào)用一次traversals
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
// 執(zhí)行等待執(zhí)行的動(dòng)畫(huà)
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
}
mIsInTraversal = false;