Android学习笔记-MeasureSpec的理解

MeasureSpec字面意思为测量的规格,他决定了view的测量过程

接下来探讨几个问题来学习MeauseSpec
1. MeasureSpec的构成
2. 如何创建MeasureSpec
3. 默认View的OnMeasure过程中对MeasureSpec的处理

一.MeasureSpec的构成

MeasureSpec代表一个32位的int值,前俩位代表SpecMode,后30位代表SpecSize.其中:SpecMode代表测量的模式,SpecSize值在某种测量模式下的规格大小。

共有三种测量模式:
1. EXACTLY: 父容器已经检测出子View所需要的精确大小,这个时候view的大小即为SpecSize的大小,他对应于布局参数中的MATCH_PARENT,或者精确大小值

2.AT_MOST: 父容器指定了一个大小,即SpecSize,子view的大小不能超过这个SpecSize的大小

3.UNSPECIFIED: 表示子View想多大都可以

二.如何创建MeasureSpec

MeasureSpec内部提供了创建MeasureSpec的方法:

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

MeasureSpec代表一个32位的int值,前俩位代表SpecMode,后30位代表SpecSize.通过巧妙的位运算,即可通过MeasureSpec来得到SpecSize,SpecMode.

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

系统内部是通过MeasureSpec来对view进行测量。在view测量的时候,系统会将LayoutParams在父容器的约束下创建MeasureSpec.

view的顶级View为DecorView.他的MeasureSpec有窗口的尺寸与布局参数来决定。对于普通的View,他的MesureSpec由父容器的MeasureSpec与自身的布局参数来一起决定的。

DecorView的MeasureSpec创建过程如下:

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

其中windowSize为窗口的大小,即屏幕大小,rootDimension在DecorView中为MATCH_PARENT.故DecorView的MeasureSpec中的SpecSize为窗口大小,SpecMode的EXACTLY.

对于普通的子View来说,通过传入fu容器的MeasureSpec,来计算字View的MeasureSpec。

通过measureChildWithMargins(View child,int parentWidthMeasureSpec, int widthUsed,int parentHeightMeasureSpec, int heightUsed);
在其内部在调用getChildMeasureSpec()方法来得到MeasureSpec.

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

大体过程如下:
一:如果父容器的SpecMode为EXACTLY,那么在判断子view自己的布局参数.
1.如果为具体数值,或者MATCH_PARENT时候,那么子view的SpecMode为EXACTLY,SpecSize为父容器的SpecSize,为具体指时,SpecSize为具体值。
2.如果为WRAP_CONTENT时,那么子View的SpecMode为AT_MOST,SpecSize为父容器的SpecSize.表示子View最大不会超过父容器

二:如果父容器的SpecMode为AT_MOST时:
1.如果子View的测量规格为MATCH_PARENT,或者WRAP_CONTENT时,那么子View的SpecMode为AT_MOST,SpecSize为父容器的SpecSize.

2.如果子View的测量规格为精确值时,那么子View的测量规格为EXACTLY,SpecSize为具体的值。

上述的判断过程均为系统的判断过程,了解这些有益于得知开发过程中出现的情况的原因。

三. 默认View的OnMeasure过程中对MeasureSpec的处理

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

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:
            result = specSize;
            break;
        }
        return result;
    }

从上面的代码中可以看到默认onMeasure()方法中的判断逻辑为:
在SpecMode为AT_MOST,EXACTLY时,测量的大小都为SpecSize.

这里我举一个列子,比方说我们在activity的布局文件中自定义了一个View,把这个view的宽高都设置为WRAP_CONTNET时,这时我们会发现这个view充满了整个屏幕。发生这样的原因是正是上面代码逻辑判断的结果。首先这个字View的父容器为DecorView中的一个ViewGroup,他内部是有一个FrameLayout的布局。这个父容器的SpecSize为整个屏幕的大小。现在看子View,子view的布局参数为wrap_content,那么子View的MeasureSpec中的SpecSize为整个屏幕,SpecMode为AT_MOST,在getDefaultSize中,当测量模式AT_MOST,或者MATCH_PARENT时,都是直接把SpecSize的值给返回了,而这个SpecSize的值正好是整个屏幕,所以出现是view不管是设置为MATCH_PARENT,还是WRAP_CONTENT都是充满了整个屏幕

你可能感兴趣的:(Android学习笔记)