美文网首页
MeasureSpec 类是如何设计的

MeasureSpec 类是如何设计的

作者: Vic_wkx | 来源:发表于2021-09-19 00:17 被阅读0次

    Android 中自定义 View 时,onMeasure 方法中使用 MeasureSpec 类表示 View 的宽高。它不仅表示了宽高的具体大小(size),还表示了宽高的模式(mode),模式就是指是 View 设置的是 wrap_contentmatch_parent固定大小

    MeasureSpec 并没有使用两个变量来分别表示 sizemode,它只用了一个变量就实现了这一点。本文就来看一下它的具体实现。

    源码如下:

    /**
     * A MeasureSpec encapsulates the layout requirements passed from parent to child.
     * Each MeasureSpec represents a requirement for either the width or the height.
     * A MeasureSpec is comprised of a size and a mode. There are three possible
     * modes:
     * <dl>
     * <dt>UNSPECIFIED</dt>
     * <dd>
     * The parent has not imposed any constraint on the child. It can be whatever size
     * it wants.
     * </dd>
     *
     * <dt>EXACTLY</dt>
     * <dd>
     * The parent has determined an exact size for the child. The child is going to be
     * given those bounds regardless of how big it wants to be.
     * </dd>
     *
     * <dt>AT_MOST</dt>
     * <dd>
     * The child can be as large as it wants up to the specified size.
     * </dd>
     * </dl>
     * <p>
     * MeasureSpecs are implemented as ints to reduce object allocation. This class
     * is provided to pack and unpack the &lt;size, mode&gt; tuple into the int.
     */
    public static class MeasureSpec {
        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK = 0x3 << MODE_SHIFT;
    
        /**
         * @hide
         */
        @IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
        @Retention(RetentionPolicy.SOURCE)
        public @interface MeasureSpecMode {
        }
    
        /**
         * Measure specification mode: The parent has not imposed any constraint
         * on the child. It can be whatever size it wants.
         */
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    
        /**
         * Measure specification mode: The parent has determined an exact size
         * for the child. The child is going to be given those bounds regardless
         * of how big it wants to be.
         */
        public static final int EXACTLY = 1 << MODE_SHIFT;
    
        /**
         * Measure specification mode: The child can be as large as it wants up
         * to the specified size.
         */
        public static final int AT_MOST = 2 << MODE_SHIFT;
    
        /**
         * Creates a measure specification based on the supplied size and mode.
         * <p>
         * The mode must always be one of the following:
         * <ul>
         *  <li>{@link android.view.View.MeasureSpec#UNSPECIFIED}</li>
         *  <li>{@link android.view.View.MeasureSpec#EXACTLY}</li>
         *  <li>{@link android.view.View.MeasureSpec#AT_MOST}</li>
         * </ul>
         *
         * <p><strong>Note:</strong> On API level 17 and lower, makeMeasureSpec's
         * implementation was such that the order of arguments did not matter
         * and overflow in either value could impact the resulting MeasureSpec.
         * {@link android.widget.RelativeLayout} was affected by this bug.
         * Apps targeting API levels greater than 17 will get the fixed, more strict
         * behavior.</p>
         *
         * @param size the size of the measure specification
         * @param mode the mode of the measure specification
         * @return the measure specification based on size and mode
         */
        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);
            }
        }
    
        /**
         * Like {@link #makeMeasureSpec(int, int)}, but any spec with a mode of UNSPECIFIED
         * will automatically get a size of 0. Older apps expect this.
         *
         * @hide internal use only for compatibility with system widgets and older apps
         */
        public static int makeSafeMeasureSpec(int size, int mode) {
            if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
                return 0;
            }
            return makeMeasureSpec(size, mode);
        }
    
        /**
         * Extracts the mode from the supplied measure specification.
         *
         * @param measureSpec the measure specification to extract the mode from
         * @return {@link android.view.View.MeasureSpec#UNSPECIFIED},
         * {@link android.view.View.MeasureSpec#AT_MOST} or
         * {@link android.view.View.MeasureSpec#EXACTLY}
         */
        @MeasureSpecMode
        public static int getMode(int measureSpec) {
            //noinspection ResourceType
            return (measureSpec & MODE_MASK);
        }
    
        /**
         * Extracts the size from the supplied measure specification.
         *
         * @param measureSpec the measure specification to extract the size from
         * @return the size in pixels defined in the supplied measure specification
         */
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }
    
        static int adjust(int measureSpec, int delta) {
            final int mode = getMode(measureSpec);
            int size = getSize(measureSpec);
            if (mode == UNSPECIFIED) {
                // No need to adjust size for UNSPECIFIED mode.
                return makeMeasureSpec(size, UNSPECIFIED);
            }
            size += delta;
            if (size < 0) {
                Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +
                        ") spec: " + toString(measureSpec) + " delta: " + delta);
                size = 0;
            }
            return makeMeasureSpec(size, mode);
        }
    
        /**
         * Returns a String representation of the specified measure
         * specification.
         *
         * @param measureSpec the measure specification to convert to a String
         * @return a String with the following format: "MeasureSpec: MODE SIZE"
         */
        public static String toString(int measureSpec) {
            int mode = getMode(measureSpec);
            int size = getSize(measureSpec);
    
            StringBuilder sb = new StringBuilder("MeasureSpec: ");
    
            if (mode == UNSPECIFIED)
                sb.append("UNSPECIFIED ");
            else if (mode == EXACTLY)
                sb.append("EXACTLY ");
            else if (mode == AT_MOST)
                sb.append("AT_MOST ");
            else
                sb.append(mode).append(" ");
    
            sb.append(size);
            return sb.toString();
        }
    }
    

    源码中有太多的注释了,我们去掉注释看一下:

    public static class MeasureSpec {
        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK = 0x3 << MODE_SHIFT;
        
        @IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
        @Retention(RetentionPolicy.SOURCE)
        public @interface MeasureSpecMode {
        }
        
        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(@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);
            }
        }
        
        public static int makeSafeMeasureSpec(int size, int mode) {
            if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
                return 0;
            }
            return makeMeasureSpec(size, mode);
        }
        
        @MeasureSpecMode
        public static int getMode(int measureSpec) {
            return (measureSpec & MODE_MASK);
        }
        
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }
    
        static int adjust(int measureSpec, int delta) {
            final int mode = getMode(measureSpec);
            int size = getSize(measureSpec);
            if (mode == UNSPECIFIED) {
                return makeMeasureSpec(size, UNSPECIFIED);
            }
            size += delta;
            if (size < 0) {
                Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +
                        ") spec: " + toString(measureSpec) + " delta: " + delta);
                size = 0;
            }
            return makeMeasureSpec(size, mode);
        }
        
        public static String toString(int measureSpec) {
            int mode = getMode(measureSpec);
            int size = getSize(measureSpec);
            StringBuilder sb = new StringBuilder("MeasureSpec: ");
            if (mode == UNSPECIFIED)
                sb.append("UNSPECIFIED ");
            else if (mode == EXACTLY)
                sb.append("EXACTLY ");
            else if (mode == AT_MOST)
                sb.append("AT_MOST ");
            else
                sb.append(mode).append(" ");
            sb.append(size);
            return sb.toString();
        }
    }
    

    首先定义了一个常量 MODE_SHIFT,值为 30MeasureSpec 是用一个 32 位的 int 值表示的,前两位表示测量方式,后三十位表示具体数值。这个 30 就是用来计算偏移量的。

    然后定义了一个常量 MODE_MASK,值为 0x3 << MODE_SHIFT,转换成二进制就是 1100 0000 0000 0000 0000 0000 0000 0000 (一共 300),用来与 MeasureSpec 做位运算。

    可以看到,MeasureSpec 中还定义了一个注解 MeasureSpecMode,值可以在三个常量中选择。

    这三个常量对应三种测量方式:

    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;
    

    分别是二进制的 000110 左移 30位。

    makeMeasureSpec 函数用来创建一个 MeasureSpec,这个函数接收两个参数,第一个参数是 size,它的范围是 0int 使用 30 位能表示的最大正数:

    @IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size
    

    第二个参数就是 MeasureSpecMode,只能从注解定义的三个常量值中选择。

    sUseBrokenMakeMeasureSpec 参数是用来做兼容的,定义如下:

    sUseBrokenMakeMeasureSpec = targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR1;
    

    JELLY_BEAN_MR1 是常量 17,代表 Android 4.2

    通常情况下,makeMeasureSpec 函数会调用这里:

    return (size & ~MODE_MASK) | (mode & MODE_MASK);
    

    首先,sizeMODE_MASK运算,将 size 的前两位设置为 0
    然后把 modeMODE_MASK运算,将 mode 的后三十位设置为 0
    其实只要传入的 sizemode 在规定的范围之内,是不需要这两步的。这里是为了更安全。

    然后把这两个安全的值做或运算,相当于把 mode 的前两位和 size 的后三十位拼接起来。

    sUseZeroUnspecifiedMeasureSpec 也是用来做兼容的,定义如下:

    sUseZeroUnspecifiedMeasureSpec = targetSdkVersion < Build.VERSION_CODES.M;
    

    M 是常量 23,代表 Android 6.0

    通常 makeSafeMeasureSpec 会直接调用 mekeMeasureSpec 函数。

    getMode 函数就是用传入的 measureSpecMODE_MASK (11000000000000000000000000000000)做运算,这样就能取到 measureSpec 的前两位,获取到宽高的模式。

    getSize 就是用传入的 measureSpecMODE_MASK00111111111111111111111111111111)做运算,这样就能取到 measureSpec 的后三十位,获取到宽高的大小。

    adjust 函数用于调整 MeasureSpec,目的是把 size 加上一个 delta,如果 modeUNSPECIFIED 的,说明 size 大小没有限制,则无需调整。如果 size 加上 delta 后变成了负数,打印了一条 error 日志,然后将 size 设置成 0

    MeasureSpec.adjust: new size would be negative!
    

    否则就调用 makeMeasureSpec 函数设置新的 MeasureSpec

    toString 函数用于打印 MeasureSpec 参数信息,将前两位和后三十位拆分开,打印出具体信息。

    这就是 MeasureSpec 类的具体实现,主要是采用位运算实现了一个 int 表示两个信息。并且设计了 makeMeasureSpec 函数使得创建 MeasureSpec 更方便,还设计了 adjust 函数用于调整已有的 MeasureSpec,整个类封装得还是很不错的。

    相关文章

      网友评论

          本文标题:MeasureSpec 类是如何设计的

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