MeasureSpec

MeasureSpec——View测量过程中的建桥


一.什么是MeasureSpec

在View工作的流程中,measure过程决定view的宽高,在view的measure过程中,MeasureSpec起到了至关重要的作用,它参与了Measure的测量过程。
我们知道,一个view的宽高有时受到父容器的影响,在测量工程中,系统会将view的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,再根据这个MeasureSpec测量出View的宽高,所以View很大程度上决定了view的尺寸规格。所以这个很抽象的MeasureSpec我们可以把它理解为“策略规格书”。

二.探索MeasureSpec

先看下View的内部类MeasureSpec的内容:

private static final int MODE_SHIFT = 30;
private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY     = 1 << MODE_SHIFT;
public static final int AT_MOST     = 2 << MODE_SHIFT;

public static int makeMeasureSpec(int size, int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

public static int getMode(int measureSpec) {
            return (measureSpec & MODE_MASK);
        }

 public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }

MeasureSpec代表一个32位的int值,通过SpecMode和SpecSize组合成一个int来节约内存,其中高2位代表SpecMode,指测量模式,低30位代表SpecSize,指该测量模式下的规格大小。通过getMode和getSize可以得到其中的值。

通过让0,1,2向左移位30定义了三个常量作为SpecMode的三种类型:

EXACTLY

检查出view的精确大小,此时view的宽高就等于SpecSize的宽高。

AT_MOST

父容器指定了一个可用大小的SpecSize,view宽高不能大于这个值。
用于wrapcontent时,内容包裹大小,但view大小不会超过父容器大小。

UNSPECIFIED

对view不做任何限制,不常出现,多用于scrollview这种要显示内容远大于可显示区域的情况。

二.MeasureSpec与LayoutParams

举个例子,这是自定义view的时候重写onMeasure中的一小段:

int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int height;
if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else {
            height = getPaddingTop() + mBound.height() + getPaddingBottom();
            if(heightMode==MeasureSpec.AT_MOST){
                height = Math.min(height,heightSize);
            }
        }

可以看出,这里通过条件判断SpecMode对view的高进行了约束,一般情况下,我们可以给view设置LayoutParams,比如宽高或者wrapcontent,细心的同学会发现重写onMeasure的时候我们会发现传了两个参数,宽高的MeasureSpec,这两个参数是伴随父容器传下来的,它会将LayoutParams在父容器的约束下转换成对应的MeasureSpec,然后根据这个MS来确定View的宽高。

对于一般的View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定。

那对于顶级View(DecorView)来说没有上层父容器了,所以其MeasureSpec由窗口的尺寸和自身的LayoutParams来决定。

三.MeasureSpec的创建过程

顶级view的MeasureSpec的创建过程与普通view有所不同,MeasureSpec由窗口的尺寸和自身的LayoutParams来决定,很好理解,父容器=屏幕。这里代码就不贴了。
总来就说就是跟屏幕尺寸有关:

  • 如果设置matchparent:大小就是屏幕大小。
  • wrapcontent:内容包裹,大小不能超过屏幕大小。
  • 固定大小(android:layout_height=”100dp”):指定多大就是多大。

那么对于普通View来说,measure过程由viewGroup传递过来,
viewGroup中的measureChildWithMargin方法:

    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);//调用子元素的measure方法
    }

注意child.measure传的参数就是我们之前所说的重写onMeasure方法得到的那两个参数,通过getChildMeasureSpec方法(注意传的参数)来得到子元素的MeasureSpec,所以解释了为什么子元素MeasureSpec的创建与父容器的的MeasureSpec和子元素和子元素本身的LayoutParams有关,还与View的margin和padding有关:

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);

        //size为子容器可用大小,为父容器减去padding的大小
        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);
    }
......//省略部分
    }

上述measureChildWithMargin方法传递了参数为父容器的MeasureSpec和子容器的padding=mPaddingTop+mPaddingBottom+lp.topMargin+lp.bottomMargin+ heightUsed(还有一组宽度),通过父容器的MeasureSpec结合view本身的LayoutParams来确定子元素的MeasureSpec。
那么总结来说:

  • view采用固定模式:
    • 不管父容器的SpecMode是什么,view的MeasureSpec都是EXACTLY,大小就是开发者设置的大小。
  • View是matchparent:
    • 父容器为EXACTLY,那么view也是EXACTLY,大小是父容器的剩余空间。
    • 父容器是AT_MOST,view也是AT_MOST,大小不能超过父容器的剩余大小。
  • view是wrapcontent:
    • 不管父容器为什么,view的模式总是AT_MOST,大小不超过父容器的剩余大小。
  • view为UNSPECIFIED:
    • 理论没有限制,可以无限大。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值