View的requestLayout()方法的源碼分析

首先來看一下requestLayout()方法是做什麼的?
View#requestLayout():

    /**
     * Call this when something has changed which has invalidated the
     * layout of this view. This will schedule a layout pass of the view
     * tree. This should not be called while the view hierarchy is currently in a layout
     * pass ({@link #isInLayout()}. If layout is happening, the request may be honored at the
     * end of the current layout pass (and then layout will run again) or after the current
     * frame is drawn and the next layout occurs.
     *
     * <p>Subclasses which override this method should call the superclass method to
     * handle possible request-during-layout errors correctly.</p>
     */
    @CallSuper
    public void requestLayout() {
        if (mMeasureCache != null) mMeasureCache.clear();
		// AttachInfo 當View依附到其父Window時,父Window提供給View的一組信息,一般不會爲null
		// mViewRequestingLayout 在佈局期間調用requestLayout()時使用,用於跟蹤哪個View發起了requestLayout()請求,默認值爲null
        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();
            // 判斷當前這個View樹是否在進行佈局流程
            if (viewRoot != null && viewRoot.isInLayout()) {
            	// 如果正在佈局就調用requestLayoutoutDuringLayout(this)讓這一次的佈局延時進行
                if (!viewRoot.requestLayoutDuringLayout(this)) {
                    return;
                }
            }
            // mViewRequestingLayout 賦值爲當前發起requestLayout()的View
            mAttachInfo.mViewRequestingLayout = this;
        }
		// 設置mPrivateFlags的兩個標誌位,重要的兩個標誌位
		// PFLAG_FORCE_LAYOUT,通過表面的意思可以知道這是一個佈局標誌位,就會執行View的mearsure()和layout()方法
        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        mPrivateFlags |= PFLAG_INVALIDATED;

        if (mParent != null && !mParent.isLayoutRequested()) {
        	// 重要代碼,這裏調用父類的requestLayout,一直往上循環...
            mParent.requestLayout();
        }
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
        	// mViewRequestingLayout 清掉之前的賦值,重新置爲null
            mAttachInfo.mViewRequestingLayout = null;
        }
    }

方法的英文註釋大致意思是: 當View發生改變使得這個View的佈局無效的時候,調用該方法將會調整View樹的佈局。當View的層次結構正處於佈局中時,不應調用該方法。如果View樹正在進行佈局,那麼請求會在當前的佈局流程完成時,或則在繪製流程完成且開始下一次佈局之後執行。
注意: 重寫此方法的子類應該調用父類的方法來正確處理請求佈局期間可能的錯誤;

		// 重要代碼,這裏調用父類的requestLayout,一直往上循環...
        if (mParent != null && !mParent.isLayoutRequested()) {
            mParent.requestLayout();
        }

這個是最重要的一個判斷,如果父容器不爲空,並且父容器沒有在LayoutRequest就調用父容器的requestLayout(),因爲父容器是ViewGroup沒有重寫requestLayout(),但是ViewGroup的父類也是View就又會調用它父容器的requestLayout(),這樣就會不斷上傳並且爲父容器設置PFLAG_FORCE_LAYOUT和PFLAG_INVALIDATED兩個標誌位,最後到頂級最外層容器DecorView,這裏DecorView的mParent是ViewRootImpl對象(爲何DecorView的mParent是ViewRootImpl?答案可以參考上一篇關於invalidate()方法的源碼分析),它也設置兩個標誌位,然後就調用ViewRootImpl#requestLayout()。
ViewRootImpl#requestLayout():

    @Override
    public void requestLayout() {
    	// mHandlingLayoutInLayoutrequest是一個boolean類型
    	// 在performLayout中被置爲true,這裏表示的意思就是當前並不處於Layout過程中,即當前爲false
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            // 劃重點:又走到這個熟悉的方法
            scheduleTraversals();
        }
    }

ViewRootImpl#scheduleTraversals():

    @UnsupportedAppUsage
    void scheduleTraversals() {
    	// 注意這個標誌位,多次調用 requestLayout,只有當這個標誌位爲 false 時纔有效
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            // 通過postSyncBarrier()設置Handler消息的同步屏障
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            // Handler發送消息,有了同步屏障mTraversalRunnable就會被優先執行,下篇博客跟蹤記錄一下爲何就能被優先執行
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

ViewRootImpl#TraversalRunnable:

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
        	// 在先前同步屏障的作用下,TraversalRunnable會優先執行
            doTraversal();
        }
    }

ViewRootImpl#doTraversal():

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            // 移除通過postSyncBarrier()設置的Handler消息的同步屏障
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
			// 走到這裏,這裏也是很多博客開始分析View的繪製流程時,選擇的切入入口
            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

performTraversals() 這個方法非常重要,方法非常多,簡單講我們需要關注以下幾個方法:
ViewRootImpl#performTraversals() :

private void performTraversals() {
        // cache mView since it is used so much below...
        final View host = mView;
        mIsInTraversal = true;
        ......省略代碼
        
        if (mFirst || windowShouldResize || insetsChanged ||
                viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
            mForceNextWindowRelayout = false;
            
            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);

                    // Ask host how big it wants to be
                    // 關注方法 1 performMeasure 測量方法
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

                    // Implementation of weights from WindowManager.LayoutParams
                    // We just grow the dimensions as needed and re-measure if
                    // needs be
                    int width = host.getMeasuredWidth();
                    int height = host.getMeasuredHeight();
                    boolean measureAgain = false;
					......省略代碼
					// 由measureAgain判斷是否需要再次測量
                    if (measureAgain) {
                    	// 關注方法 1 performMeasure 測量方法
                        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    }
                    layoutRequested = true;
                }
            }
        } else {
            maybeHandleWindowMove(frame);
        }

        final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
        boolean triggerGlobalLayoutListener = didLayout
                || mAttachInfo.mRecomputeGlobalAttributes;
        if (didLayout) {
        	// 關注方法 2 performLayout 位置擺放方法
            performLayout(lp, mWidth, mHeight);
            ......省略代碼
        }
        ......省略代碼
        boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;

        if (!cancelDraw) {
            if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                for (int i = 0; i < mPendingTransitions.size(); ++i) {
                    mPendingTransitions.get(i).startChangingAnimations();
                }
                mPendingTransitions.clear();
            }
            // 關注方法 3 performDraw 繪製方法
            performDraw();
        } else {
            ......省略代碼
        }
        mIsInTraversal = false;
    }

關注方法 1:
ViewRootImpl#performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) :

    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
        	// 調用View的measure方法,measure方法內部又會調用View的onMeasure方法對View進行測量
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

View#measure(int widthMeasureSpec, int heightMeasureSpec):

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
		......省略代碼
		// requestLayout()方法裏把mPrivateFlags標示位設置爲 PFLAG_FORCE_LAYOUT,所以此時forceLayout值爲true
        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;

        // Optimize layout by avoiding an extra EXACTLY pass when the view is
        // already measured as the correct size. In API 23 and below, this
        // extra pass is required to make LinearLayout re-distribute weight.
        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);
		// 由forceLayout和needsLayout判斷是否要執行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
                // View調用onMeasure方法測量自己
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }

            // flag not set, setMeasuredDimension() was not invoked, we raise
            // an exception to warn the developer
            if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
                throw new IllegalStateException("View with id " + getId() + ": "
                        + getClass().getName() + "#onMeasure() did not set the"
                        + " measured dimension by calling"
                        + " setMeasuredDimension()");
            }
			// 設置mPrivateFlags標識位
            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }
        ......省略代碼
    }

該方法中,由於調用requestLayout()方法時,設置了標識位mPrivateFlags = PFLAG_FORCE_LAYOUT,最終這個View會根據mPrivateFlags來判斷是否要執行View的onMeasure方法。
方法最後設置mPrivateFlags |= PFLAG_LAYOUT_REQUIRED,這個是View的onLayout方法需要用到的。

關注方法 2:
ViewRootImpl#performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight):

    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        mLayoutRequested = false;
        mScrollMayChange = true;
        mInLayout = true;

        final View host = mView;
        if (host == null) {
            return;
        }
  
        try {
        	// host就是當前調用requestLayout()方法的View,也即調用了View的layout方法
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
            mInLayout = false;
            int numViewsRequestingLayout = mLayoutRequesters.size();
            if (numViewsRequestingLayout > 0) {
                ......省略代碼
                if (validLayoutRequesters != null) {
             		......省略代碼
                    measureHierarchy(host, lp, mView.getContext().getResources(),
                            desiredWindowWidth, desiredWindowHeight);
                    mInLayout = true;
                    // host就是當前調用requestLayout()方法的View,也即調用了View的layout方法
                    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

                    ......省略代碼
                }
            }
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        mInLayout = false;
    }

View#layout(int l, int t, int r, int b):

    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);
		// View的measure方法裏把mPrivateFlags標示位設置爲 PFLAG_LAYOUT_REQUIRED,所以此時if的判斷條件是true
        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        	// View調用onLayout方法調整自己的擺放位置
            onLayout(changed, l, t, r, b);
            if (shouldDrawRoundScrollbar()) {
                if(mRoundScrollbarRenderer == null) {
                    mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
                }
            } else {
                mRoundScrollbarRenderer = null;
            }
			// 設置mPrivateFlags標識位
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
            ......省略代碼
        }
        ......省略代碼

關注方法 3:
ViewRootImpl#performDraw() :

private void performDraw() {
		......省略代碼
        try {
        	// 調用View#draw(boolean fullRedrawNeeded)
            boolean canUseAsync = draw(fullRedrawNeeded);
            if (usingAsyncReport && !canUseAsync) {
                mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
                usingAsyncReport = false;
            }
        } finally {
            mIsDrawing = false;
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

View#draw(boolean fullRedrawNeeded):

private void draw(boolean fullRedrawNeeded) {
    ......省略代碼
    mAttachInfo.mDrawingTime =
            mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;
    if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
        ......省略代碼
        } else {
            ......省略代碼
            // 調用View#drawSoftware(Surface surface, ......)
            if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                return;
            }
        }
    }
    ......省略代碼
}

View#drawSoftware(Surface surface, …):

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 {
            ......省略代碼
            // 當前的View調用draw(Canvas canvas)進行View的繪製
            mView.draw(canvas);
            drawAccessibilityFocusedDrawableIfNeeded(canvas);
        } finally {
           ......省略代碼
        }
    } finally {
       ......省略代碼
    }
    return true;
}

View#draw(Canvas canvas):

public void draw(Canvas canvas) {
    final int privateFlags = mPrivateFlags;
    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)
     */
    // Step 1, draw the background, if needed
    int saveCount;
    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 繪製自己的內容
        onDraw(canvas);

        // Step 4, draw the children 繪製子View
        dispatchDraw(canvas);
        // Step 5, draw the fade effect and restore layers
        final Paint p = scrollabilityCache.paint;
        final Matrix matrix = scrollabilityCache.matrix;
        final Shader fade = scrollabilityCache.shader;
        ......省略代碼
        canvas.restoreToCount(saveCount);
        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);

        if (debugDraw()) {
            debugDrawFocus(canvas);
        }
    }

ViewRootImpl#performTraversals() 方法依次可能會調用了performMeasure,performLayout,performDraw。跟蹤代碼看下來執行requestLayout() 後,onMeasure(),onLayout(),onDraw()方法會依次執行,onMeasure()由於requestLayout()中把mPrivateFlags標示位設置爲 PFLAG_FORCE_LAYOUT,使的判斷條件爲true,所以肯定會執行;onLayout()由於onMeasure()中把mPrivateFlags標示位設置爲 PFLAG_LAYOUT_REQUIRED,使的判斷條件爲true,所以肯定也會執行;但是onDraw()就不一定會執行到,有興趣的童鞋,可以跟蹤查看一下TextView在setText之後是如何重新調整View佈局的。

TextView#checkForRelayout():

private void checkForRelayout() {
        // If we have a fixed width, we can just swap in a new text layout
        // if the text height stays the same or if the view height is fixed.

        if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
                || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
                && (mHint == null || mHintLayout != null)
                && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
			......省略代碼
            if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
			......省略代碼
            // We lose: the height has changed and we have a dynamic height.
            // Request a new view layout using our new text layout.
            requestLayout();
            invalidate();
        } else {
            // Dynamic width, so we have no choice but to request a new
            // view layout with a new text layout.
            nullLayouts();
            requestLayout();
            invalidate();
        }
    }

該方法中,在調用了requestLayout()方法之後,緊跟着就調用了invalidate()方法;
我的理解是: requestLayout()方法肯定會走到onMeasure()和onLayout()這兩個方法,而invalidate()方法肯定會執行onDraw()方法的,相當於加了一個雙保險。

個人理解,如有大神指點討論,還望不吝賜教!!!

至此,View#requestLayout() 方法的源碼流程分析完畢;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章