目录结构
一、measure 过程
1. measure()
测量单一 view 大小的入口方法是 View 类的measure()
方法,在该方法中会调用 View 类的onMeasure()
方法,我们直接看onMeasure()
方法的实现即可。
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);
}
// Suppress sign extension for the low bytes
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffff L;
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
// 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.
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) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int)(value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
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 & 0xffffffff L); // suppress sign extension
}
2. onMeasure()
onMeasure()
方法中主要有两个方法setMeasuredDimension()
和getDefultSize()
。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
3. getDefaultSize()
通过 View 类中的getDefultSize()
方法测量 View 的尺寸大小。该方法逻辑如下所示。
// size:传入 view 的默认宽/高
// measureSpec:传入 view 的测量规格
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
4. setMeasuredDimension()
View 类中的setMeasuredDimension()
方法存储测量后的大小(宽/高)
二、重点说明
1.getDefaultSize()
中如何得到 view 的默认宽/高
其中,view 的默认宽度通过getSuggestedMinimumWidth()
获得。
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
view 的默认高度通过getSuggestedMinimumHeight()
获得。
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
上述两个方法原理相同,下面说明getSuggestedMinimumWidth()
的逻辑:
- 若 view 无背景图片:view的默认宽度 = mMinWidth。
若指定了android:minWidth
属性,则mMinWidth为指定值;
若未指定该属性的值,则默认为0。 - 若 view 有背景图片:view的默认宽度=
max(mMinWidth , 背景图片的原始宽度)
那么,如何获得 Drawable 的原始宽度?见获得 Drawable 的原始宽度
2.getDefaultSize()
中子 View 测量规格 MeasureSpec 的创建过程
MeasureSpec
类是View
类中的一个静态内部类。
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
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) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
}
测量规格 MeasureSpec 是一个 32 位的 int 值,前 2 位保存测量模式(specMode),后 30 位保存测量大小(specSize)。将这两个值打包成一个 int 是为了避免过多的对象内存分配。
View 的 MeasureSpec 的创建过程对于顶层 View(DecorView)和普通 View 有所不同。
(1) DecorView 的 MeasureSpec 创建
取决于窗口尺寸大小,和 View 自身的 LayoutParams
(2)普通 View 的 MeasureSpec 创建
取决于父容器的 MeasureSpec,和 View 自身的 LayoutParams
- 创建时机
对于普通 View 来说,View 的 measure 过程由 ViewGroup 传递而来,ViewGroup 的measureChildWithMargins()
方法如下。该方法中在对子元素进行 measure 之前,先调用方法getChildMeasureSpec()
得到子元素的测量规格。
// 测量ViewGroup的子View:child
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
// 得到子View的LayoutParams参数
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
// 调用方法getChildMeasureSpec()得到子View的测量规格 childWidthMeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin +
widthUsed, lp.width);
// 调用方法getChildMeasureSpec()得到子View的测量规格 childHeightMeasureSpec
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin +
heightUsed, lp.height);
// 进行单一View的测量过程
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
- 计算方法
因此,子元素 MeasureSpec 的创建过程是在 ViewGroup 的getChildMeasureSpec()
方法中完成的。
/**
* ViewGroup 类中的方法,用于根据父容器的MeasureSpec和子View自身的LayoutParams得到子View的MeasureSpec
*
* @param spec :父View的测量规格(MeasureSpec)
* @param padding :父view中已占用的空间大小
* @param childDimension :当前view的布局参数(宽/高),是我们所说的LayoutParams相关参数
* @return a MeasureSpec integer for the child:返回子View的测量规格(MeasureSpec)
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
// 父view的测量模式
int specMode = MeasureSpec.getMode(spec);
// 父view的测量大小
int specSize = MeasureSpec.getSize(spec);
//通过父view计算出的子view大小 = 父大小-边距(父要求的大小,但子view不一定用这个值)
int size = Math.max(0, specSize - padding);
// 子view想要的实际大小和模式(需要计算)
int resultSize = 0;
int resultMode = 0;
// 通过父view的MeasureSpec和子view自身的LayoutParams计算子view的测量规格
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
// 当子view的LayoutParams>0,即有确切的值时
// 子view的测量大小为自身所指定的值,测量模式为EXACTLY
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 当子view的LayoutParams==-1为MATCH_PARENT时
// 子view的测量大小为通过父view计算出的子view大小 ,测量模式为EXACTLY
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// 当子view的LayoutParams==-2为MATCH_PARENT时
// 子view的测量大小自己决定,但不能超过通过父view计算出的子view大小 ,测量模式为AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
// 当父view的模式为UNSPECIFIED时,父view不对view有任何限制,要多大给多大
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
通过下表对getChildMeasureSpec()
方法的逻辑进行梳理。表中的parentSize
为父容器中可使用的大小,childSize
为子 View 指定的具体数值。
由上表可知:
普通 View 的 MeasureSpec 由父容器的 MeasureSpec 和 View 自身的 LayoutParams 决定。针对不同的父容器和 View 自身不同的 LayoutParams,View 就可以有多种 MeasureSpec。