MeasureSpec 类是如何设计的

2021-09-19  本文已影响0人  Vic_wkx

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,整个类封装得还是很不错的。

上一篇 下一篇

猜你喜欢

热点阅读