requestLayout()的執(zhí)行流程

每次更新View都會想到requestLayout()方法洽糟,所以想看看它的流程是怎樣的。
我一直覺得堕战,先理清楚步驟再去看源碼會好很多坤溃,所以先給出requestLayout()的一個調(diào)用流程:

  • View#requestLayout()
  • ViewGroup#requestLayout()
  • ViewRootImpl#requestLayout()
  • ViewRootImpl#scheduleTraversals()
  • ViewRootImpl#doTraversal()
  • ViewRootImpl#performTraversals()

最后ViewRootImpl#performTraversals()會依次調(diào)用performMeasure() , performLayout() , performDraw()。

requestLayout()是在View中定義的践啄,View#requestLayout()

public void requestLayout() {
    if (mMeasureCache != null) mMeasureCache.clear();
    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
        // Only trigger request-during-layout logic if this is the view requesting it,
        // not the views in its parent hierarchy
        ViewRootImpl viewRoot = getViewRootImpl();
        if (viewRoot != null && viewRoot.isInLayout()) {
            if (!viewRoot.requestLayoutDuringLayout(this)) {
                return;
            }
        }
        mAttachInfo.mViewRequestingLayout = this;
    }
    /**記住這里設(shè)置的標(biāo)志位浇雹,非常重要...**/
    mPrivateFlags |= PFLAG_FORCE_LAYOUT;
    mPrivateFlags |= PFLAG_INVALIDATED;
    if (mParent != null && !mParent.isLayoutRequested()) {
        /**畫重點,這里調(diào)用父類的requestLayout屿讽,一直往上循環(huán)...**/
        mParent.requestLayout();
    }
    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
        mAttachInfo.mViewRequestingLayout = null;
    }
}

ViewParent是一個接口昭灵,定義了view的一些操作等方法。ViewGroup就是一個ViewParent伐谈,所以mParent.requestLayout會一直往上遍歷烂完,而終點是ViewRootImpl,ViewRootImpl#requestLayout()

// 入口方法诵棵,接下來執(zhí)行 scheduleTraversals();
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

// 會執(zhí)行mTraversalRunnable抠蚣,它是一個Runnable
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        ......
    }
}

// 諾,TraversalRunnable 的定義履澳。所以最后執(zhí)行了doTraversal();
final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}

// 重點還不在這嘶窄,重點是 performTraversals();!>啻1濉!
void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        performTraversals();
        ......
    }
}

performTraversals()真的是一個很長很長的方法...忠蝗,我平時寫的一個類一般都不會有這么多现横。所以非常難去理解里面到底做了什么,所以我只是看了個大概的流程。

private void performTraversals() {
    final View host = mView;
    if (host == null || !mAdded)
        return;
        ...... // 此處省略了很多行戒祠。骇两。。
       
        if (!mStopped || mReportNextDraw) {
            boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                    (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
            if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                    || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                    updatedConfiguration) {
                int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
                /**終于出現(xiàn)了:performMeasure**/
                // Ask host how big it wants to be
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                ......
            }
        }
    } else {
        maybeHandleWindowMove(frame);
    }
    final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
    boolean triggerGlobalLayoutListener = didLayout
            || mAttachInfo.mRecomputeGlobalAttributes;
    if (didLayout) {
        /**這里是performLayout**/
        performLayout(lp, mWidth, mHeight);
        ......
    }
    ...... // 此處省略了很多行姜盈。低千。。
    if (!cancelDraw && !newSurface) {
        if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
            for (int i = 0; i < mPendingTransitions.size(); ++i) {
                mPendingTransitions.get(i).startChangingAnimations();
            }
            mPendingTransitions.clear();
        }
        /**這里是performDraw()**/
        performDraw();
    } else {
        ......
    }
    mIsInTraversal = false;
}

本來想看看performTraversals()具體執(zhí)行了些什么贩据,后來我放棄了...栋操,我只是找出了跟View繪制相關(guān)的三個方法:performMeasure() performLayout() performDraw()。
ViewRootImpl#performMeasure()

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    if (mView == null) {
        return;
    }
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
    try {
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

直接就調(diào)用了view的measure方法饱亮,從ViewRootImpl回到了View矾芙。View#measure()

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    ......
    /**還記得requestLayout方法里有把mPrivateFlags  = PFLAG_FORCE_LAYOUT**/
    final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
    final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
            || heightMeasureSpec != mOldHeightMeasureSpec;
    final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
            && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
    final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
            && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
    final boolean needsLayout = specChanged
            && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
    /**依據(jù)forceLayout 和needsLayout決定是否要執(zhí)行onMeasure**/
    if (forceLayout || needsLayout) {
        // first clears the measured dimension flag
        mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
        resolveRtlPropertiesIfNeeded();
        int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
        if (cacheIndex < 0 || sIgnoreMeasureCache) {
            // measure ourselves, this should set the measured dimension flag back
            /**出現(xiàn)了onMeasure**/
            onMeasure(widthMeasureSpec, heightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        } 
        /**記住這里設(shè)置的標(biāo)識位**/
        mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        ......
}

調(diào)用requestLayout時,設(shè)置了標(biāo)識位mPrivateFlags = PFLAG_FORCE_LAYOUT近上,最終這個view會根據(jù)mPrivateFlags 來判斷是否要執(zhí)行onMeasure方法剔宪。
那么最后還mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;,這個接下來onLayout會需要壹无。
ViewRootImpl#performLayout()

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
        int desiredWindowHeight) {
    ......
    final View host = mView;
    if (host == null) {
        return;
    }
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
    try {
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
        ......
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
    mInLayout = false;
}

host是view葱绒,那么performLayout就執(zhí)行了view的layout(),View#layout()

public void layout(int l, int t, int r, int b) {
    ......
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        onLayout(changed, l, t, r, b);
       ......
    }
    ......
}

這里根據(jù)條件執(zhí)行onLayout斗锭,上一步執(zhí)行measure時地淀,mPrivateFlags |= PFLAG_LAYOUT_REQUIRED。
ViewRootImpl#performDraw()

private void performDraw() {
    ......
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
    try {
        draw(fullRedrawNeeded);
    } finally {
        mIsDrawing = false;
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
    ......
}

// 省略了很多行的代碼岖是,心好累帮毁。。豺撑。
private void draw(boolean fullRedrawNeeded) {
    ......
    mAttachInfo.mDrawingTime =
            mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;
    if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
        ......
        } else {
            ......
            /**你敢信onDraw的邏輯竟然隱藏在這里烈疚?**/
            if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                return;
            }
        }
    }
    ......
}

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
        boolean scalingRequired, Rect dirty) {
    final Canvas canvas;
    try {
        .....
        // 意外找到了Canvas的賦值地點
        canvas = mSurface.lockCanvas(dirty);
        .....
    } catch (Surface.OutOfResourcesException e) {
        handleOutOfResourcesException(e);
        return false;
    } catch (IllegalArgumentException e) {
        mLayoutRequested = true;    // ask wm for a new surface next time.
        return false;
    }
    try {
        ......
        try {
            ......
            /**終于找到你**/
            mView.draw(canvas);
            drawAccessibilityFocusedDrawableIfNeeded(canvas);
        } finally {
           ......
        }
    } finally {
       ......
    }
    return true;
}

因此,邏輯是:performDraw()->draw()->drawSoftware()->view.draw()聪轿。
View#draw()

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 (繪制子view)
    *      5. If necessary, draw the fading edges and restore layers
    *      6. Draw decorations (scrollbars for instance)
    */
    // Step 1, draw the background, if needed
    int saveCount;
    if (!dirtyOpaque) {
        drawBackground(canvas);
    }
    // skip step 2 & 5 if possible (common case)
    final int viewFlags = mViewFlags;
    boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
    boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
    if (!verticalEdges && !horizontalEdges) {
        // Step 3, draw the content
        if (!dirtyOpaque) onDraw(canvas);
        // Step 4, draw the children
        dispatchDraw(canvas);
        drawAutofilledHighlight(canvas);
        // Overlay is part of the content and draws beneath Foreground
        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }
        // Step 6, draw decorations (foreground, scrollbars)
        onDrawForeground(canvas);
        // Step 7, draw the default focus highlight
        drawDefaultFocusHighlight(canvas);
        if (debugDraw()) {
            debugDrawFocus(canvas);
        }
        // we're done...
        return;
    }

注釋真好爷肝,這個方法的邏輯一下子就看懂了。

總結(jié)

  1. 當(dāng)我們調(diào)用requestLayout時陆错,會執(zhí)行parent的requestLayout灯抛,最終執(zhí)行到ViewRootImpl的requestLayout。
  2. ViewRootImpl經(jīng)過一系列方法的調(diào)用執(zhí)行performTraversals()方法音瓷。
  3. performTraversals()會依次執(zhí)行performMeasure() performLayout() performDraw()方法牧愁。

看起來很簡單,但真的會被源碼轉(zhuǎn)暈外莲。而且實際調(diào)試過程中發(fā)現(xiàn),View的draw()方法不一定是drawSoftware()執(zhí)行的,可以看看ThreadedRenderer#draw()偷线。
mPrivateFlags 挺重要的磨确,會根據(jù)它的值來決定一些方法是否要調(diào)用。

其實忽略了的代碼中声邦,隱藏著很重要的邏輯乏奥,主要是對draw()方法的執(zhí)行判斷。因為代碼通篇看下來執(zhí)行requestLayout后亥曹,onMeasure(),onLayout(),onDraw()方法會依次執(zhí)行邓了,onMeasure()和onLayout()執(zhí)行是可以肯定的。但是onDraw()就不一定了媳瞪。這部分我還沒看懂骗炉,果然源碼不簡單。蛇受。句葵。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市兢仰,隨后出現(xiàn)的幾起案子乍丈,更是在濱河造成了極大的恐慌,老刑警劉巖把将,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件轻专,死亡現(xiàn)場離奇詭異,居然都是意外死亡察蹲,警方通過查閱死者的電腦和手機(jī)请垛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來递览,“玉大人叼屠,你說我怎么就攤上這事〗柿澹” “怎么了镜雨?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長儿捧。 經(jīng)常有香客問我荚坞,道長,這世上最難降的妖魔是什么菲盾? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任颓影,我火速辦了婚禮,結(jié)果婚禮上懒鉴,老公的妹妹穿的比我還像新娘诡挂。我一直安慰自己碎浇,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布璃俗。 她就那樣靜靜地躺著奴璃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪城豁。 梳的紋絲不亂的頭發(fā)上苟穆,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天,我揣著相機(jī)與錄音唱星,去河邊找鬼雳旅。 笑死,一個胖子當(dāng)著我的面吹牛间聊,可吹牛的內(nèi)容都是我干的攒盈。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼甸饱,長吁一口氣:“原來是場噩夢啊……” “哼沦童!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起叹话,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤偷遗,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后驼壶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體氏豌,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年热凹,在試婚紗的時候發(fā)現(xiàn)自己被綠了泵喘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡般妙,死狀恐怖纪铺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情碟渺,我是刑警寧澤鲜锚,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站苫拍,受9級特大地震影響芜繁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜绒极,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一骏令、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧垄提,春花似錦榔袋、人聲如沸周拐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽速妖。三九已至,卻和暖如春聪黎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背备恤。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工稿饰, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人露泊。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓喉镰,卻偏偏與公主長得像,于是被迫代替她去往敵國和親惭笑。 傳聞我的和親對象是個殘疾皇子侣姆,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,044評論 2 355

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