MeasureSpec 的分析

文章目录

  • 场景
  • MeasureSpec
  • SpecMode
  • 与 LayoutParams 关系
  • 总结

场景

当我们需要了解 View 的原理,我们肯定会碰到这么一个类 MeasureSpec,那么它是干嘛的呢,它有什么作用,在理解 View 原理时,可以跳过它么,它充当什么角色呢?

MeasureSpec

  • 它是 View类中 的一个静态内部类
  • MeasureSpec 可以理解成测量规范
  • MeasureSpec 通过将 SpecMode 和 SpecSize
    打包成 int 值,来减少本地对象。这个类还提供了打包和解包方法
  • measureSpec 是一个32位 int 值,高2位代表 mode,低30位代表 size。mode 是测量模式,size 是测量模式下的规格大小。
    看下源码中的重要方法(解包,打包):
 public static class MeasureSpec {
        public static int getMode(int measureSpec) {
            //noinspection ResourceType
            return (measureSpec & MODE_MASK);
        }
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }
        public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                          @MeasureSpecMode int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }
}

SpecMode

  • 三种模式,分别是:UNSPECIFIED , EXACTLY , AT_MOST
    1、UNSPECIFIED
    View 要多大有多大,不受父容器限制。一般用于系统内部
    2、EXACTLY
    View 的大小等于精确的数值
    3、AT_MOST
    父容器指定了一个可用的大小SpecSize,View 的大小不能大于这个值。

与 LayoutParams 关系

  • View 的 measureSpec 值,由父容器和 View 的 LayoutParams 一起决定的
  • DecorView 的 measureSpec 值 由窗体大小和 DecorView 的 LayoutParams 决定
  • 普通 View 的 measureSpec 值 由父容器 MeasureSpec 和 View 的 LayoutParams 决定
一、看下 DecorView 怎么获取 measureSpec :

1、其中 desiredWindowWidth,desiredWindowHeight 是窗口宽和高的大小。lp.width ,lp.height 是 LayoutParams 的宽高参数,下面是
ViewRootImpl#measureHierarchy()源码:

   private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
            final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
...
            childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
 return windowSizeMayChange;
}

2、获取 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;
    }

根据以上代码可以看出,DecorView 根据它的 LayoutParams 中的宽高的参数来划分的,还有窗口大小,最终返回 measureSpec.

二、普通 view 的怎么获取 measureSpec:

1、 看下 ViewGroup#measureChildWithMargins()源码:

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

2、从上面中我们看到调用了getChildMeasureSpec 方法,所以,我们在看下
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:
            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;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

从中可以发现的,普通 View 的 measureSpec 由父容器和 View 的 LayoutParams 共同决定的。另外在1展示地代码中更有这么一句:child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
从这句代码中,我们也知道了, View 的测量是由 View 的 measureSpec 决定的。

总结

  • MeasureSpec 是测量规范
  • measureSpec 代表一个 32位 int值,高2位是 mode(测量模式),低30位是 size(测量模式下的大小)
  • DecorView 的 measureSpec 由窗口大小和 DecorView 的 LayoutParams 共同决定
  • 普遍 View 的 measureSpec 由父容器 MeasureSpec 和 View 的 LayoutParams 共同决定
  • View 的测量是由 View 的 measureSpec 决定的

如果对你有一点点帮助,那是值得高兴的事情。:)

我的csdn:http://blog.csdn.net/shenshizhong
我的简书:http://www.jianshu.com/u/345daf0211ad

    原文作者:shenshizhong
    原文地址: https://www.jianshu.com/p/825f8c547618
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞