当前位置: 代码迷 >> Android >> Android View深入学习(1),View的测量(Measure)过程
  详细解决方案

Android View深入学习(1),View的测量(Measure)过程

热度:126   发布时间:2016-04-27 23:55:15.0
Android View深入学习(一),View的测量(Measure)过程

Android应用上面的View显示出来都必须经过测量,布局,和绘制这三个过程。我们知道PhoneWindow中的DecorView是界面最顶层的View,那么,最先绘制的View肯定是DecorView。在ViewRootImpl中的performTraversals方法中中,依次对DecorView进行测量,布局,和绘制:

 private void performTraversals() {    ...    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);    ...    performLayout(lp, desiredWindowWidth, desiredWindowHeight);    ...    performDraw();    ... }

最先开始对DecorView进行测量,然后对其子View进行测量,一直到最上层的View,直到所有的View都测量完毕。在ViewRootImpl中对DecorView进行测量时,调用的是performMeasure方法:

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);        }    }

在performMeasure方法里面,调用了View的measure方法:

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {        boolean optical = isLayoutModeOptical(this);        if (optical != isLayoutModeOptical(mParent)) {            Insets insets = getOpticalInsets();            int oWidth  = insets.left + insets.right;            int oHeight = insets.top  + insets.bottom;            widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);            heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);        }        // Suppress sign extension for the low bytes        long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;        final boolean isExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY &&                MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;        final boolean matchingSize = isExactly &&                getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec) &&                getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);        if (forceLayout || !matchingSize &&                (widthMeasureSpec != mOldWidthMeasureSpec ||                        heightMeasureSpec != mOldHeightMeasureSpec)) {            // 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                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("onMeasure() did not set the"                        + " measured dimension by calling"                        + " setMeasuredDimension()");            }            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;        }        mOldWidthMeasureSpec = widthMeasureSpec;        mOldHeightMeasureSpec = heightMeasureSpec;        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |                (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension    }

这个方法是final修饰,子类不能重写。在它的33行,调用了onMeasure(widthMeasureSpec, heightMeasureSpec),找到onMeasure方法:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));    }

这个方法子类是可以重写的,DecorView重写了方法,并在里面调用 super.onMeasure(widthMeasureSpec, heightMeasureSpec)方法,DecorView继承自FrameLayout,那么会调用FrameLayout的onMeasure方法:

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        int count = getChildCount();        final boolean measureMatchParentChildren =                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;        mMatchParentChildren.clear();        int maxHeight = 0;        int maxWidth = 0;        int childState = 0;        for (int i = 0; i < count; i++) {            final View child = getChildAt(i);            if (mMeasureAllChildren || child.getVisibility() != GONE) {                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);                final LayoutParams lp = (LayoutParams) child.getLayoutParams();                maxWidth = Math.max(maxWidth,                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);                maxHeight = Math.max(maxHeight,                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);                childState = combineMeasuredStates(childState, child.getMeasuredState());                if (measureMatchParentChildren) {                    if (lp.width == LayoutParams.MATCH_PARENT ||                            lp.height == LayoutParams.MATCH_PARENT) {                        mMatchParentChildren.add(child);                    }                }            }        }        // Account for padding too        maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();        maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();        // Check against our minimum height and width        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());        // Check against our foreground's minimum height and width        final Drawable drawable = getForeground();        if (drawable != null) {            maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());            maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());        }        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),                resolveSizeAndState(maxHeight, heightMeasureSpec,                        childState << MEASURED_HEIGHT_STATE_SHIFT));        count = mMatchParentChildren.size();        if (count > 1) {            for (int i = 0; i < count; i++) {                final View child = mMatchParentChildren.get(i);                final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();                int childWidthMeasureSpec;                int childHeightMeasureSpec;                if (lp.width == LayoutParams.MATCH_PARENT) {                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() -                            getPaddingLeftWithForeground() - getPaddingRightWithForeground() -                            lp.leftMargin - lp.rightMargin,                            MeasureSpec.EXACTLY);                } else {                    childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,                            getPaddingLeftWithForeground() + getPaddingRightWithForeground() +                            lp.leftMargin + lp.rightMargin,                            lp.width);                }                if (lp.height == LayoutParams.MATCH_PARENT) {                    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() -                            getPaddingTopWithForeground() - getPaddingBottomWithForeground() -                            lp.topMargin - lp.bottomMargin,                            MeasureSpec.EXACTLY);                } else {                    childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,                            getPaddingTopWithForeground() + getPaddingBottomWithForeground() +                            lp.topMargin + lp.bottomMargin,                            lp.height);                }                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);            }        }    }

13行遍历所有的子View,在16行调用了measureChildWithMargins方法在这个方法里面,调用child.measure(childWidthMeasureSpec, childHeightMeasureSpec),开始DecorView子View的测量过程。就这样,一直往下调用,直到所有的View都完成了测量过程。整个过程的流程图如下所示:

这里写图片描述

测量完成了,那么View经过测量之后,得到什么?在FrameLayout的onMeasure方法的47行,调用了View的setMeasuredDimension方法,setMeasuredDimension里面调用setMeasuredDimensionRaw方法:

 private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {        mMeasuredWidth = measuredWidth;        mMeasuredHeight = measuredHeight;        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;    }

得到了测量宽度mMeasuredWidth和测量高度mMeasuredHeight。
从上面的分析可以得到,调用View的onMeasure方法可以得到View的测量宽度mMeasuredWidth和测量高度mMeasuredHeight,那这个mMeasuredWidth和mMeasuredHeight是根据什么算来的?ViewGroup的measureChildWithMargins方法负责测量子View:

protected void measureChildWithMargins(View child,            int parentWidthMeasureSpec, int widthUsed,            int parentHeightMeasureSpec, int heightUsed) {        final MarginLayoutParams lp = (MarginLayoutParams) child                .getLayoutParams();        final int childWidthMeasureSpec = getChildMeasureSpec(                parentWidthMeasureSpec, mPaddingLeft + mPaddingRight                        + lp.leftMargin + lp.rightMargin + widthUsed, lp.width);        final int childHeightMeasureSpec = getChildMeasureSpec(                parentHeightMeasureSpec, mPaddingTop + mPaddingBottom                        + lp.topMargin + lp.bottomMargin + heightUsed,                lp.height);        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);    }

从第7行我们可以得知,调用ViewGroup的getChildMeasureSpec,传入三个参数,第一个是父View的widthMeasureSpec或者是heightMeasureSpec,第二个参数是父布局的Padding和子View的Margin之和,第三个参数是子View布局的大小类别(MATCH_PARENT,WRAP_CONTENT,或者是准确的大小):

 public static int getChildMeasureSpec(int spec, int padding,            int childDimension) {        int specMode = MeasureSpec.getMode(spec);        int specSize = MeasureSpec.getSize(spec);        int size = Math.max(0, specSize - padding);        int resultSize = 0;        int resultMode = 0;        switch (specMode) {        // Parent has imposed an exact size on us        case MeasureSpec.EXACTLY:            if (childDimension >= 0) {                resultSize = childDimension;                resultMode = MeasureSpec.EXACTLY;            } else if (childDimension == LayoutParams.MATCH_PARENT) {                // Child wants to be our size. So be it.                resultSize = size;                resultMode = MeasureSpec.EXACTLY;            } else if (childDimension == LayoutParams.WRAP_CONTENT) {                // Child wants to determine its own size. It can't be                // bigger than us.                resultSize = size;                resultMode = MeasureSpec.AT_MOST;            }            break;        // Parent has imposed a maximum size on us        case MeasureSpec.AT_MOST:            if (childDimension >= 0) {                // Child wants a specific size... so be it                resultSize = childDimension;                resultMode = MeasureSpec.EXACTLY;            } else if (childDimension == LayoutParams.MATCH_PARENT) {                // Child wants to be our size, but our size is not fixed.                // Constrain child to not be bigger than us.                resultSize = size;                resultMode = MeasureSpec.AT_MOST;            } else if (childDimension == LayoutParams.WRAP_CONTENT) {                // Child wants to determine its own size. It can't be                // bigger than us.                resultSize = size;                resultMode = MeasureSpec.AT_MOST;            }            break;        // Parent asked to see how big we want to be        case MeasureSpec.UNSPECIFIED:            if (childDimension >= 0) {                // Child wants a specific size... let him have it                resultSize = childDimension;                resultMode = MeasureSpec.EXACTLY;            } else if (childDimension == LayoutParams.MATCH_PARENT) {                // Child wants to be our size... find out how big it should                // be                resultSize = 0;                resultMode = MeasureSpec.UNSPECIFIED;            } else if (childDimension == LayoutParams.WRAP_CONTENT) {                // Child wants to determine its own size.... find out how                // big it should be                resultSize = 0;                resultMode = MeasureSpec.UNSPECIFIED;            }            break;        }        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);    }

从上面分析可以得出:子View的widthMeasureSpec和heightMeasureSpec是由父View的widthMeasureSpec和heightMeasureSpec,LayoutParams和子ViewMarginLayoutParams决定的,那么最顶层的DecorView的测量宽度和测量高度是由什么决定的?
在调用performMeasure的前面:

                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);                                    // Ask host how big it wants to be                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

从上面可以得知DecorView的测量宽度和测量高度是通过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;    }

从上面得知,DecorView的测量宽度和测量高度是由windowSize(窗口的大小)和WindowManager.LayoutParams决定的。

版权声明:本文为博主原创文章,未经博主允许不得转载。

  相关解决方案