美文网首页
Android View的测量大小与 wrap_content、

Android View的测量大小与 wrap_content、

作者: Yi__Lin | 来源:发表于2018-04-11 13:05 被阅读0次

    先说结论:默认情况下,当父布局为 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);
    }
    

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

    相关文章

      网友评论

          本文标题:Android View的测量大小与 wrap_content、

          本文链接:https://www.haomeiwen.com/subject/fvbahftx.html