Android View的measure过程详解

注意

阅读本文至少需要先了解MeasureSpec的工作原理,可以参见网上其他相关博客,本文不做解释。

measure介绍

顾名思义,measure方法用于测量View的大小,由View所在的ViewGroup调用:

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

其中的 childWidthMeasureSpec 和 childHeightMeasureSpec 参数一般受ViewGroup的MeasureSpec及children的LayoutParam影响。
下面主要分两部分介绍该过程。

View的Measure过程

View的measure为final方法,其中会调用onMeasure方法,如下:

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

此处涉及四个方法:setMeasuredDimension,getDefaultSize,getSuggestedMinimumWidth,getSuggestedMinimumHeight下面一一介绍:
其中的setMeasureDimension方法会设置View的测量值,此处的测量值可以大致理解为View的最终大小,之所以这么说是因为View的最终大小是在layout过程中确定的,但他们基本上是相同的。

getMeasuredHeight();//measure中确定的高度
getHeight();//View的最终高度
getMeasuredWidth();//measure中确定的宽度
getWidth();//View的最终宽度

getDefaultSize方法如下:

/**
 * Utility to return a default size. Uses the supplied size if the
 * MeasureSpec imposed no constraints. Will get larger if allowed
 * by the MeasureSpec.
 *
 * @param size Default size for this view
 * @param measureSpec Constraints imposed by the parent
 * @return The size this view should be.
 */
public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);
    switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
    }
    return result;
}

注释已经写的很明确了,该方法接受两个参数,第一个参数size为View的默认大小,第二个参数为父ViewGroup传过来的大小,然后在MeasureSpec的约束下获取到View的最终大小。
可以看到,View的SpecMode无论是AT_MOST还是EXACTLY,最终结果都是SpecSize。也就是说,View默认情况下,match_parent与wrap_content的值相等,也就是父布局的剩余大小最大值。
所以当我们自定义View时需要注意,如果需要让View支持wrap_content就需要重写onMeasure方法。

getSuggestedMinimumWidth与getSuggestedMinimumHeight方法工作原理相同,这里只介绍getSuggestedMinimumWidth方法:

/**
 * Returns the suggested minimum width that the view should use. This
 * returns the maximum of the view's minimum width)
 * and the background's minimum width
 *  ({@link android.graphics.drawable.Drawable#getMinimumWidth()}).
 * <p>
 * When being used in {@link #onMeasure(int, int)}, the caller should still
 * ensure the returned width is within the requirements of the parent.
 *
 * @return The suggested minimum width of the view.
 */
protected int getSuggestedMinimumWidth() {
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

方法很简答,如果该View有背景则返回背景与mMinWidth的最大值,如果没有背景就返回mMinWidth。其中的mMinWidth通过setMinimumWidth或者android:minWidth指定。

ViewGroup的Measure过程:

ViewGroup除了会对自身measure外,还会对所有的子View进行measure。
ViewGroup是一个抽象类,其中并没有重写onMeasure方法,所以不同的ViewGroup中对onMeasure的重写方法都有所不同。
但是ViewGroup中给我们提供了一个用于measure所有子View的方法,我们可以使用此方法measure,也可以自己写:

/**
 * Ask all of the children of this view to measure themselves, taking into
 * account both the MeasureSpec requirements for this view and its padding.
 * We skip children that are in the GONE state The heavy lifting is done in
 * getChildMeasureSpec.
 *
 * @param widthMeasureSpec The width requirements for this view
 * @param heightMeasureSpec The height requirements for this view
 */
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    final int size = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < size; ++i) {
        final View child = children[i];
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }
}

参数为当前ViewGroup的widthMeasureSpec和heightMeasureSpec。
此方法很简单,遍历所有子View,如果View不为GONE,就对其进行measure,measureChild(child, widthMeasureSpec, heightMeasureSpec)方法如下:

/**
 * Ask one of the children of this view to measure itself, taking into
 * account both the MeasureSpec requirements for this view and its padding.
 * The heavy lifting is done in getChildMeasureSpec.
 *
 * @param child The child to measure
 * @param parentWidthMeasureSpec The width requirements for this view
 * @param parentHeightMeasureSpec The height requirements for this view
 */
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
    final LayoutParams lp = child.getLayoutParams();
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom, lp.height);
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

先获取子View的LayoutParam及ViewGroup的padding,然后通过getChildMeasureSpec方法获取到大小,重点在于getChildMeasureSpec方法:

/**
 * Does the hard part of measureChildren: figuring out the MeasureSpec to
 * pass to a particular child. This method figures out the right MeasureSpec
 * for one dimension (height or width) of one child view.
 *
 * The goal is to combine information from our MeasureSpec with the
 * LayoutParams of the child to get the best possible results. For example,
 * if the this view knows its size (because its MeasureSpec has a mode of
 * EXACTLY), and the child has indicated in its LayoutParams that it wants
 * to be the same size as the parent, the parent should ask the child to
 * layout given an exact size.
 *
 * @param spec The requirements for this view
 * @param padding The padding of this view for the current dimension and
 *        margins, if applicable
 * @param childDimension How big the child wants to be in the current
 *        dimension
 * @return a MeasureSpec integer for the child
 */
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 = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
    }
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

上面的代码逻辑很清晰,主要是通过ViewGroup的specMode,specSize及View的LayoutParam获得View的MeasureSpec。

猜你喜欢

转载自blog.csdn.net/u013872857/article/details/60485843