View#measure(int widthMeasureSpec, int heightMeasureSpec),Android布局机制的第一阶段,这阶段是为了了解一个View应该有多大。父节点在参数中提供约束信息。参数:
- widthMeasureSpec:父节点对子View宽度的约束
- heightMeasureSpec:父节点对子View高度的约束
View的实际度量工作是按此方法调用的onMeasure(int,int)进行的。因此,只有onMeasure(int,int)才能而且必须被子类覆盖。源码:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
//视觉边界模式时对widthMeasureSpec和heightMeasureSpec进行调整
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int oWidth = insets.left + insets.right;
int oHeight = insets.top + insets.bottom;
widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
}
// 将父布局的约束条件widthMeasureSpec和heightMeasureSpec组成一个long,作为key用于缓存
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
//如果需要强制布局 或 新旧widthMeasureSpec、heightMeasureSpec不同
if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
//清除 设置了测量尺寸标记
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
//解析所有与RTL相关的属性
resolveRtlPropertiesIfNeeded();
//如果是强制布局cacheIndex == -1,mMeasureCache.indexOfKey(key)没有缓存返回负数
int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {//不使用缓存
onMeasure(widthMeasureSpec, heightMeasureSpec);//实际度量工作在onMeasure(int,int)里
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;//清除 layout前需要测量标记
} else {//使用缓存
//获取缓存的 父布局的约束条件widthMeasureSpec和heightMeasureSpec组成的key的值
long value = mMeasureCache.valueAt(cacheIndex);
//设置测量尺寸,里面添加了 设置了测量尺寸标记
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;//添加 layout前需要测量标记
}
//没有调用setMeasuredDimension(int,int)会抛出异常
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("View with id " + getId() + ": "
+ getClass().getName() + "#onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
//添加 需要layout标记
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
//记录新的widthMeasureSpec和heightMeasureSpec
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
//将 父布局的约束条件组成的key 和 新的widthMeasureSpec和heightMeasureSpec缓存起来
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL);
}
方法添加了final修饰,子类有需要时应该重写onMeasure(int ,int)以完成自己的测量方式。
不了解视觉边界模式的可以看看这篇文章,Android 4.3中的视觉边界布局(Optical bounds layout)。
关于onMeasure(int ,int)欢迎看看我写的Android自定义View-onMeasure介绍 - oJin的博客 - CSDN博客
关于方法里面用到的标记:
修饰符 | 类型 | 常量名 | 值 | 描述 |
---|---|---|---|---|
static final | int | PFLAG_FORCE_LAYOUT | 0x00001000 | 需要强制布局 |
static final | int | PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT | 0x8 | layout前需要measure |
static final | int | PFLAG_LAYOUT_REQUIRED | 0x00002000 | 需要layout |
static final | int | PFLAG_MEASURED_DIMENSION_SET | 0x00000800 | 设置了测量尺寸 |
PFLAG_FORCE_LAYOUT:在调用了requestLayout()或forceLayout()时会添加上该标记。
PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT:在measure(int,int)方法中使用缓存来设置测量尺寸时添加该标记。
PFLAG_LAYOUT_REQUIRED:在measure(int,int)方法中进行测量后添加该标记。
PFLAG_MEASURED_DIMENSION_SET:在measure(int,int)方法中进行测量前清除标记,如果测量后没有添加该标记会抛出异常。在setMeasuredDimensionRaw(int, int)中添加标记,setMeasuredDimension(int, int)中会调用setMeasuredDimensionRaw(int, int)方法。