requestLayout源码分析

1,434 阅读3分钟

invalidate是用来进行view的重绘的,它一般会导致onDraw的调用(对于ViewGroup容器来说它并不一定会调用onDraw)以使View改变自身内容,但是如果当view的大小尺寸发生了变化,此时就需要requestLayout对view进行布局请求。比如当view设置了布局参数后就需要进行布局请求。但需要注意的是requestLayout并不保证onDraw会调用,它只负责完成布局请求,调不调用onDraw取决于view的内容是否改变。所以一般情况下requestLayout和invalidate是结合着使用的。

public void setLayoutParams(ViewGroup.LayoutParams params) {
    ……
    mLayoutParams = params;
    ……
    requestLayout();
}
// /frameworks/base/core/java/android/view/View.java
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()) {//如果正在layout 发送请求layout
            if (!viewRoot.requestLayoutDuringLayout(this)) {
                return;
            }
        }
        mAttachInfo.mViewRequestingLayout = this;
    }
    //设置FLAG_FORCE_LAYOUT 和 PFLAG_INVALIDATED标记
    mPrivateFlags |= PFLAG_FORCE_LAYOUT;
    mPrivateFlags |= PFLAG_INVALIDATED;

    if (mParent != null && !mParent.isLayoutRequested()) {
        mParent.requestLayout();
    }
    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
        mAttachInfo.mViewRequestingLayout = null;
    }
}

requestLayout是从View开始,它首先判断的当前viewRoot是否正在进行layout,如果是,则发送请求给ViewRoot,告诉它当前view需要进行layout。否则为view打上PFLAG_FORCE_LAYOUT和PFLAG_INVALIDATED标记,然后调用parent的requestLayout,这里parent即它的父view,可以是ViewGroup也可以是ViewRoot,其中ViewGroup没有覆盖requestLayout,那么依然是调用View的requestLayout。这实际上是一个递归调用为父view打上请求布局的标记。直到ViewRootImpl。

///frameworks/base/core/java/android/view/ViewRootImpl.java
 @Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {//如果layout请求没有在执行 
        checkThread();
        mLayoutRequested = true;//设置layout请求标记
        scheduleTraversals();
    }
}

ViewRootImpl的requestLayout中设置mLayoutRequested为true,然后开启请求布局。

private void performTraversals() {
    ……
    boolean layoutRequested = mLayoutRequested && !mStopped;
    ……
    if (!mStopped) {
        boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
        if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                || mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
            
            //获得view宽高的测量规格,lp.width和lp.height表示DecorView根布局宽和高
            int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
            int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

            // Ask host how big it wants to be
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//开始执行测量操作

            ……
        }
    }       


    final boolean didLayout = layoutRequested && !mStopped;//layoutRequested 为true并且actitivy并非暂停则需要执行layout
    boolean triggerGlobalLayoutListener = didLayout
            || attachInfo.mRecomputeGlobalAttributes;
    if (didLayout) {
        performLayout(lp, desiredWindowWidth, desiredWindowHeight);
        ……

    }
    ……
     boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw() ||
                viewVisibility != View.VISIBLE;
    if (!cancelDraw && !newSurface) {//既没有取消绘制,也没有创建新的平面
        if (!skipDraw || mReportNextDraw) {//
            ……
            performDraw();//开始执行绘制操作
        }
    } 
    ……
}

mLayoutRequested 在requestLayout中设置为true,mStopped表示当前view树所在的window状态不是停止的,一般为false,那么layoutRequested为true,didLayout为true。那么分别执行performMeasure和performLayout,下面我们分别分析。

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

ViewRootImpl的performMeasure执行view的measure方法进行测量。

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    ……
    if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
            widthMeasureSpec != mOldWidthMeasureSpec ||
            heightMeasureSpec != mOldHeightMeasureSpec) {

        // first clears the measured dimension flag
        mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;//清除meaure set 标记 用来检测是否调用了setMeasuredDimension

       ……

        mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
    }

    mOldWidthMeasureSpec = widthMeasureSpec;
    mOldHeightMeasureSpec = heightMeasureSpec;

    mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
            (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}

由于mPrivateFlags在requestLayout中设置了PFLAG_FORCE_LAYOUT,所有清除PFLAG_MEASURED_DIMENSION_SET的标记,这个标记用于检测是否调用setMeasuredDimension,同时打上PFLAG_LAYOUT_REQUIRED标记,表示请求布局。

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
    mLayoutRequested = false;
    ...
    mInLayout = true;
    final View host = mView;
    try {
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
        mInLayout = false;
        int numViewsRequestingLayout = mLayoutRequesters.size();
        if (numViewsRequestingLayout > 0) {
            ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
                        false);
            if (validLayoutRequesters != null) {
                mHandlingLayoutInLayoutRequest = true;//表示正在进行layout请求操作

                // Process fresh layout requests, then measure and layout
                int numValidRequests = validLayoutRequesters.size();
                for (int i = 0; i < numValidRequests; ++i) {
                    final View view = validLayoutRequesters.get(i);
                    view.requestLayout();
                }
                measureHierarchy(host, lp, mView.getContext().getResources(),
                        desiredWindowWidth, desiredWindowHeight);
                mInLayout = true;
                host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

                mHandlingLayoutInLayoutRequest = false;
                ……
            }       
        }
    }
    ……
}

performLayout负责执行viewlayout过程,同时对请求布局的view也执行requstLayout。这里host就是view树的根节点,即DecorView,它是个FrameLayout。所以我们看看ViewGroup的layout。

@Override
public final void layout(int l, int t, int r, int b) {
    if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
        if (mTransition != null) {
            mTransition.layoutChange(this);
        }
        super.layout(l, t, r, b);
    } else {
        // record the fact that we noop'd it; request layout when transition finishes
        mLayoutCalledWhileSuppressed = true;
    }
}

viewGroup调用view的layout

public void layout(int l, int t, int r, int b) {
    ……
    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;

    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);//setFrame中可能会触发invalidate进行重绘置

    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {//如果布局发生了变化 则还需要对子视图进行重新布局
        onLayout(changed, l, t, r, b);
        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

        ……
    }

    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
    mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}

由于在measure过程中设置了PFLAG_LAYOUT_REQUIRED标记,那么就会调用onLayout来进行view的布局过程,这个过程完成后,清理PFLAG_LAYOUT_REQUIRED和PFLAG_FORCE_LAYOUT标记表示布局过程完成了。这里需要注意的是view在测量后大小可能发生变化,这时候通过setFrame设置其边框时会调用invalidate的调用,因此可能会导致onDraw的调用。