关于ViewRootImpl中measure相关代码的分析

首先先看下在一个Activity创建到DecorView的measure函数被调用过程的时序图:


可以看到这些主要在ViewRootImpl的performTraversals()函数中完成。如下是preformTraversals()函数,我们关注一下标红的这几行代码

 private void performTraversals() {
        // cache mView since it is used so much below...
        final View host = mView;
        if (DEBUG_LIFECYCLE || DEBUG_DEFAULT) {
            Xlog.v(TAG, "ViewRoot performTraversals+ : mFirst = " + mFirst + ", this = " + this);
        }

        if (DBG) {
            System.out.println("======================================");
            System.out.println("performTraversals");
            host.debug();
        }

        if (host == null || !mAdded)
            return;

        mIsInTraversal = true;
        mWillDrawSoon = true;
        boolean windowSizeMayChange = false;
        boolean newSurface = false;
        boolean surfaceChanged = false;
<span style="background-color: rgb(255, 0, 0);">        WindowManager.LayoutParams lp = mWindowAttributes;</span>

 <span style="white-space:pre">	</span>.............

            // Ask host how big it wants to be
<span style="background-color: rgb(255, 0, 0);">            windowSizeMayChange |= measureHierarchy(host, lp, res,
                    desiredWindowWidth, desiredWindowHeight);</span>
       ......................
    }

可以看到View的measure工作最后主要由measureHierarchy()完成,并将Window的LayoutParameter传入到函数内:

 private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
            final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
        int childWidthMeasureSpec;
        int childHeightMeasureSpec;
        boolean windowSizeMayChange = false;

        if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
            Xlog.v(TAG, "ViewRoot measure+ " + host + " in display " + desiredWindowWidth + "x"
                    + desiredWindowHeight + ", lp = " + lp + ", this = " + this);
        }

        boolean goodMeasure = false;
        if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
            // On large screens, we don't want to allow dialogs to just
            // stretch to fill the entire width of the screen to display
            // one line of text.  First try doing the layout at a smaller
            // size to see if it will fit.
            final DisplayMetrics packageMetrics = res.getDisplayMetrics();
            res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
            int baseSize = 0;
            if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
                baseSize = (int)mTmpValue.getDimension(packageMetrics);
            }
            if (DEBUG_DIALOG) {
                Xlog.v(TAG, "Window " + mView + ": baseSize=" + baseSize + ", this = " + this);
            }
            if (baseSize != 0 && desiredWindowWidth > baseSize) {
<span style="background-color: rgb(255, 0, 0);">                childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);</span>
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                if (DEBUG_DIALOG) {
                    Xlog.v(TAG, "Window " + mView + ": measured (" + host.getMeasuredWidth() + ","
                            + host.getMeasuredHeight() + ") " + this);
                }
                if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                    goodMeasure = true;
                } else {
                    // Didn't fit in that size... try expanding a bit.
                    baseSize = (baseSize+desiredWindowWidth)/2;
                    if (DEBUG_DIALOG) {
                        Xlog.v(TAG, "Window " + mView + ": next baseSize=" + baseSize + " " + this);
                    }
                    childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
<span style="background-color: rgb(255, 0, 0);">                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);</span>
                    if (DEBUG_DIALOG) {
                        Xlog.v(TAG, "Window " + mView + ": measured (" + host.getMeasuredWidth()
                                + "," + host.getMeasuredHeight() + ") " + this);
                    }
                    if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                        if (DEBUG_DIALOG) {
                            Xlog.v(TAG, "Good!");
                        }
                        goodMeasure = true;
                    }
                }
            }
        }

        if (!goodMeasure) {
            childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
                windowSizeMayChange = true;
            }
        }

        if (DBG) {
            System.out.println("======================================");
            System.out.println("performTraversals -- after measure");
            host.debug();
        }

        if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
            Xlog.v(TAG, "ViewRoot measure-: host measured size = (" + host.getMeasuredWidth()
                    + "x" + host.getMeasuredHeight() + "), windowSizeMayChange = "
                    + windowSizeMayChange + ", this = " + this);
        }
        return windowSizeMayChange;
    }


这里,我们分析一下传进去给DecorView的measure函数的widthMeasureSpec和heightMeasureSpec到底是怎么样的?特别是他的mode到底是怎么确定的:

<span style="white-space:pre">	</span>childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
显然childWidthMeasureSpec由如下函数确定getRootMeasureSpec(),而这里的lp就是刚刚说的windown的LayoutParam。现在看一下getRootMeasureSpec():

        

 private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {

        case ViewGroup.LayoutParams.MATCH_PARENT:
            // Window can't resize. Force root view to be windowSize.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // Window can resize. Set max size for root view.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            // Window wants to be an exact size. Force root view to be that size.
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }

从这个函数可以看出如果Window的lp是ViewGroup.LayoutParams.MATCH_PARENT那么传入DecorView的measure函数的MeasureSpec的mode就是EXACTLY。而如果如果Window的lp是ViewGroup.LayoutParams.WRAP_CONTENT那么传入DecorView的measure函数的MeasureSpec的mode就是AT_MOST。


通过分析Framework的源代码,特别是FrameLayout的onMeasure()函数,还可以得出如果下结论:

ViewGroup的measure()和onMeasure()的确可能被多次调用。以FrameLayout的onMeasure为例,首先遍历所有的child,调用measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);测量子View的尺寸。这里的measureSpec采用父View给他的measureSpec。.之后如果child的LayoutParameter的高或者宽有一个为match_parent会调整measureSpec再次重新对对应的子View测量一遍,即调用子View的measure函数重新计算一遍: child.measure(childWidthMeasureSpec, childHeightMeasureSpec);。这个是完全可以理解的,因为child为match_parent,而上次测量的时候还不知道父View的尺寸,自然需要重新测量一遍了。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值