performTraversals()分析

一系宜、來(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)行蔼两。
// 全局變量
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è)量窗口寬高土砂,賦值窗口尺寸是否改變州既。

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;
// 如果窗口不處于停止?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ù)
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;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末纹笼,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子苟跪,更是在濱河造成了極大的恐慌廷痘,老刑警劉巖蔓涧,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異笋额,居然都是意外死亡元暴,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)兄猩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)茉盏,“玉大人,你說(shuō)我怎么就攤上這事枢冤○蹋” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵淹真,是天一觀的道長(zhǎng)讶迁。 經(jīng)常有香客問(wèn)我,道長(zhǎng)核蘸,這世上最難降的妖魔是什么巍糯? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮客扎,結(jié)果婚禮上祟峦,老公的妹妹穿的比我還像新娘。我一直安慰自己虐唠,他們只是感情好搀愧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布惰聂。 她就那樣靜靜地躺著疆偿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪搓幌。 梳的紋絲不亂的頭發(fā)上杆故,一...
    開(kāi)封第一講書(shū)人閱讀 51,541評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音溉愁,去河邊找鬼处铛。 笑死,一個(gè)胖子當(dāng)著我的面吹牛拐揭,可吹牛的內(nèi)容都是我干的撤蟆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼堂污,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼家肯!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起盟猖,我...
    開(kāi)封第一講書(shū)人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤讨衣,失蹤者是張志新(化名)和其女友劉穎换棚,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體反镇,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡固蚤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了歹茶。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片夕玩。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖惊豺,靈堂內(nèi)的尸體忽然破棺而出风秤,到底是詐尸還是另有隱情,我是刑警寧澤扮叨,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布缤弦,位于F島的核電站,受9級(jí)特大地震影響彻磁,放射性物質(zhì)發(fā)生泄漏碍沐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一衷蜓、第九天 我趴在偏房一處隱蔽的房頂上張望累提。 院中可真熱鬧,春花似錦磁浇、人聲如沸斋陪。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)无虚。三九已至,卻和暖如春衍锚,著一層夾襖步出監(jiān)牢的瞬間友题,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工戴质, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留度宦,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓告匠,卻偏偏與公主長(zhǎng)得像戈抄,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子后专,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容