在 Android
中自定义 View
时,onMeasure
方法中使用 MeasureSpec
类表示 View
的宽高。它不仅表示了宽高的具体大小(size
),还表示了宽高的模式(mode
),模式就是指是 View
设置的是 wrap_content
、match_parent
或固定大小
。
但 MeasureSpec
并没有使用两个变量来分别表示 size
和 mode
,它只用了一个变量就实现了这一点。本文就来看一下它的具体实现。
源码如下:
/**
* 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 <size, mode> 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
,值为 。MeasureSpec
是用一个 位的 int
值表示的,前两位表示测量方式,后三十位表示具体数值。这个 就是用来计算偏移量的。
然后定义了一个常量 MODE_MASK
,值为 0x3 << MODE_SHIFT
,转换成二进制就是 (一共 个),用来与 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;
分别是二进制的 、、 左移 位。
makeMeasureSpec
函数用来创建一个 MeasureSpec
,这个函数接收两个参数,第一个参数是 size
,它的范围是 到 int
使用 位能表示的最大正数:
@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size
第二个参数就是 MeasureSpecMode
,只能从注解定义的三个常量值中选择。
sUseBrokenMakeMeasureSpec
参数是用来做兼容的,定义如下:
sUseBrokenMakeMeasureSpec = targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR1;
JELLY_BEAN_MR1
是常量 ,代表 Android 4.2
。
通常情况下,makeMeasureSpec
函数会调用这里:
return (size & ~MODE_MASK) | (mode & MODE_MASK);
首先,size
和 MODE_MASK
的非
做且
运算,将 size
的前两位设置为 。
然后把 mode
和 MODE_MASK
做且
运算,将 mode
的后三十位设置为 。
其实只要传入的 size
和 mode
在规定的范围之内,是不需要这两步的。这里是为了更安全。
然后把这两个安全的值做或运算,相当于把 mode
的前两位和 size
的后三十位拼接起来。
sUseZeroUnspecifiedMeasureSpec
也是用来做兼容的,定义如下:
sUseZeroUnspecifiedMeasureSpec = targetSdkVersion < Build.VERSION_CODES.M;
M
是常量 ,代表 Android 6.0
。
通常 makeSafeMeasureSpec
会直接调用 mekeMeasureSpec
函数。
getMode
函数就是用传入的 measureSpec
与 MODE_MASK
()做且
运算,这样就能取到 measureSpec
的前两位,获取到宽高的模式。
getSize
就是用传入的 measureSpec
与 MODE_MASK
的非
()做且
运算,这样就能取到 measureSpec
的后三十位,获取到宽高的大小。
adjust
函数用于调整 MeasureSpec
,目的是把 size
加上一个 delta
,如果 mode
是 UNSPECIFIED
的,说明 size
大小没有限制,则无需调整。如果 size
加上 delta
后变成了负数,打印了一条 error
日志,然后将 size
设置成 :
MeasureSpec.adjust: new size would be negative!
否则就调用 makeMeasureSpec
函数设置新的 MeasureSpec
。
toString
函数用于打印 MeasureSpec
参数信息,将前两位和后三十位拆分开,打印出具体信息。
这就是 MeasureSpec
类的具体实现,主要是采用位运算实现了一个 int
表示两个信息。并且设计了 makeMeasureSpec
函数使得创建 MeasureSpec
更方便,还设计了 adjust
函数用于调整已有的 MeasureSpec
,整个类封装得还是很不错的。
网友评论