View工作原理--MeasureSpec

MeasureSpec参与了View的measure过程,它在很大程度上决定了View的尺寸规格。(因为父容器影响View的MeasureSpec的创建过程)在测量过程中,系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,根据这个MeasureSpec测量出View的宽/高(不一定等于最终宽高)

注意:MeasureSpec不是唯一由LayoutParams决定的,LayoutParams需要和父容器一起才能决定View的MeasureSpec

MeasureSpec

MeasureSpec代表一个32位int值(其本身是View的一个静态内部类),高2位代表测量模式SpecMode,低30位代表在测量模式下的规格大小SpecSize。把它俩打包成一个int值来避免过多的对象内存分配,MeasureSpec提供了打包和解包方法

SpecMode

三类

  • UNSPECIFIED
    父容器不对View有任何限制,要多大给多大(这种情况一般用于系统内部)表示一种测量的状态
  • EXACTLY
    父容器已经检测出View所需要的精确大小,这时View的最终大小就是SpecSize所指定的值。它对应于LayoutParams中的match_parent和具体的数值这两种模式
  • AT_MOST
    父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,它对应于LayoutParams中的wrap_content

MeasureSpec一旦确定后,onMeasure中就可以确定View的测量宽/高

DecorView的MeasureSpec的创建过程

MeasureSpec的决定因素:

  • 窗口的尺寸
  • 自身的LayoutParams

ViewRootImpl # performTraversals() # measureHierarchy()

// desiredWindowWidth为窗口的宽度,lp.width为DecorView的layourParams中设置的宽度
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
// 得到DecorView的MeasureSpec宽高后测量,其中直接调用View的measure方法
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
//  private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
//        if (mView == null) {
//            return;
//        }
//        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
//        try {
//            // 开始测量过程
//            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
//        } finally {
//            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
//        }
//  }

ViewRootImpl # getRootMeasureSpec()

    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {
        // match_parent和fill_parent
        case ViewGroup.LayoutParams.MATCH_PARENT:
            // Window can't resize. Force root view to be windowSize.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            // public static int makeMeasureSpec(int size, int mode) {
            //      if (sUseBrokenMakeMeasureSpec) {
            //          return size + mode;
            //      } else {
            //          return (size & ~MODE_MASK) | (mode & MODE_MASK);
            //      }
            //}
            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;
    }

遵守如下规则,根据LayoutParams中的宽高参数来划分:

  • LayoutParams.MATCH_PARENT:
    SpecMode:EXACTLY SpecSize:windowSize
  • LayoutParams.WRAP_CONTENT:
    SpecMode:AT_MOST SpecSize:windowSize(大小不定,不能超过窗口大小)
  • 固定大小:
    SpecMode:EXACTLY SpecSize:rootDimension

普通View的MeasureSpec的创建过程

MeasureSpec的决定因素:

  • 父容器的MeasureSpec
  • 自身的LayoutParams

普通View的measure过程由ViewGroup传递过来

ViewGroup # measureChildWithMargins()

除了测量额外计算margin值外与measureChild基本一致

    protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        // parent...MeasureSpec是ViewGroup的MeasureSpec
        // widthUsed是已经占用的宽度
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
        // 第二个参数为ViewGroup中不属于自己的大小
        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);
        // 开始View的测量
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

ViewGroup # getChildMeasureSpec()

    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        // 父布局的Mode和Size
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);
        // 父布局原大小减去已用大小如果小于0,就取0
        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        // Parent has imposed an exact size on us
        // EXACTLY
        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
        // AT_MOST
        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
        // UNSPECIFIED
        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
                // 如果设置了sUseZeroUnspecifiedMeasureSpec,大小就是0
                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;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

创建规则

childLayoutParams\parent SpecModeEXACTLYAT_MOSTUNSPECIFIED
具体数值childSize
EXACTLY
childSize
EXACTLY
childSize
EXACTLY
match_parentsize
EXACTLY
size
AT_MOST
0 or size
UNSPECIFIED
wrap_contentsize
AT_MOST
size
AT_MOST
0 or size
UNSPECIFIED
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值