开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情
已LinearLayout为例
LinearLayout重写了onMeasure,而没有重写Measure。我们都知道onMeasure是由Measure调用的,先来看看View.java中Measure都做了什么。
大致看一下流程就好
public final void measure(int widthMeasureSpec, int 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);
}
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec;
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
if (forceLayout || needsLayout) {
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
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()");
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
详细分析
- 首先通过isLayoutModeOptical(this)判断this是否是ViewGroup,如果是的话在判断该this是否为LAYOUT_MODE_OPTICAL_BOUNDS的布局模式(关于这个布局模式是什么作用,我也不知道)
- 通过isLayoutModeOptical(view)让当前view和父view都执行这个函数,如果两者比较结果不相等则会对measureSpec进行调整,即执行
MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);adjust里面会把当前measureSpec的size加上adjust第二个参数传递的值。 - 后面有一段注释: Optimize layout by avoiding an extra EXACTLY pass when the view is already measured as the correct size. In API 23 and below, this extra pass is required to make LinearLayout re-distribute weight.
意思是:优化布局,当视图已经被测量为正确的大小时,避免额外的EXACTLY传递。在API 23及以下,这个额外的通道是需要使LinearLayout重新分配权重。
大致意思是:LineaLayout不止调用一次measure,这个时候我们就需要确定是否要重新测量,也就是是否再执行一次onMeasure,缓存中有的话就不需要再执行一次onMeasure了,但是有个例外: forceLayout为true的话(也就是mPrivateFlags设置了PFLAG_FORCE_LAYOUT)每次都会执行onMeasure不管之前有没有。
这里用到了mMeasureCache缓存数组(类型是LongSparseLongArray,专门用于存储long),它保存了之前测量完毕mMeasureWidth和mMeasureHeight,是用一个long型值保存的,由于long是64位,则用高32位保存宽度,低32位保存高度。