Android View的测量大小与 wrap_content、

2018-04-11  本文已影响0人  Yi__Lin

先说结论:默认情况下,当父布局为 wrap_content 或者 match_parent 时,无论子 view(view 或者 viewgroup) 是wrap_content 还是 match_parent,最终的效果都是 match_parent。也就是 子 view 会占据父布局中剩下的所有空间。

父布局剩余的空间为 size - padding

why?

我们看看ViewGroup 是如何测量子View的。

android.view.ViewGroup#measureChildren

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    final int size = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < size; ++i) {//依次遍历,调用子 view 的测量方法
        final View child = children[i];
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }
}

android.view.ViewGroup#measureChild

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

android.view.ViewGroup#getChildMeasureSpec

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:
        //代码省略……
        break;
    }
    //noinspection ResourceType
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

getChildMeasureSpec 方法中,无论父布局的测量模式是 EXACTLY 还是 AT_MOST, resultSize 取值都为 size。测量模式要么是 AT_MOST,要么是 EXACTLY。那为什么说最终效果都是一样的呢?回答这个问题,需要看看子 view 是如何处理接收到的 resultSize 和 resultMode 的。

~注:size = Math.max(0, specSize - padding);即,原有的空间减去 padding~。

View 的onMeasure 方法默认会调用 getDefaultSize 方法来获取 view 的大小。

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

View#getDefaultSize

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://结果尺寸为 exactly
        result = specSize;//大小为父布局所给的大小
        break;
    }
    return result;
}

从 view 的getDefaultSize方法可以看到,它对AT_MOST模式以及EXACTLY模式的处理是相同的。所以,也就说明了结论是成立的。

附:最顶层的 View 大小是由谁确定的呢?

「最上层的 root_view」的大小是 WindowManager 赋予的。

ViewRootImpl 中的 view 实际上是 DecorView

WindowManager.LayoutParams#LayoutParams(),超类为 ViewGroup.LayoutParams

也就是 DecorView 的大小是充满父布局,也就是充满 Window。

public LayoutParams() {
    super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    type = TYPE_APPLICATION;
    format = PixelFormat.OPAQUE;
}

开发者也可以通过 Window#setAttributes 方法设置窗口的大小。

public void setAttributes(WindowManager.LayoutParams a) {
    mWindowAttributes.copyFrom(a);
    dispatchWindowAttributesChanged(mWindowAttributes);
}

由于本人水平有限,可能出于误解或者笔误难免出错,如果发现有问题或者对文中内容存在疑问欢迎在下面评论区告诉我,谢谢!

上一篇下一篇

猜你喜欢

热点阅读