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();
            // Choreographer 通过 postCallback 提交一个任务,mTraversalRunnable是要执行的回调
            // 有了同步屏障mTraversalRunnable就会被优先执行
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

Choreographer 通过 postCallback 提交一个任务,mTraversalRunnable是要执行的回调,有了同步屏障mTraversalRunnable就会被优先执行,至于为何有了同步屏障mTraversalRunnable就会被优先执行?可以查看分析Handler之同步屏障机制与Android的屏幕刷新机制在源码中的应用

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() 方法的源码流程分析完毕;

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
作者aliouswang,码FriendRefreshView,微信朋友圈我们都经常用,朋友圈的下拉刷新比较有意思,我们今天将要模仿打造微信朋友圈的下拉刷新控件,当然微信的这种刷新设计可能不是最好的,实际项目中你可以用V4包里面的SwipeRefreshView或者Chris Banes的AndroidPullRerfresh,看产品经理的设计。 思路 我们初步分析下,界面上主要有二个控件,一个彩虹状的圆形LoadingView,一个是ListView,那么我大致可以有下面三个步骤: 第一步:需要自定义一个ViewGroup,把上面的2个控件add进来。 第二步:利用ViewDragHelper处理控件拖动。当ListView处于顶部时,如果继续向下拖动,就拦截触摸事件,将触摸事件传递给ViewDragHelper处理,这里比较关键,主要是是否拦截触摸事件的判断条件要处理好,否则如果ListView的点击和滚动事件被我们拦截了,那就悲剧了。 第三步:在ViewDragHelper的拖动回调方法里面,设置listView和彩虹LoadingView的位置,调用requestLayout。 第四步:手势松开后,开始刷新,LoadingView在固定位置做旋转动画。 第五步:如果设置了onRefreshListener,执行onRefresh接口。 第六步:调用stopRefresh,完成刷新,这一步需要控件使用者手动去调用,控件本身不自动触发。 文/ALIOUS(简书作者) 原文链接:http://www.jianshu.com/p/1ca0caf5fd8b 著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值