measure过程要分为两种情况:如果是原始的View,需要通过measure 方法就完成其测量过程;如果是ViewGroup,除了完成自己的测量过程外,还会遍历去调用所有子元素的measure方法,各个子元素再递归去执行这个流程。
ViewRootImpl
View 的整个绘制流程的启动入口可以从 ViewRootImpl 的 performTraversals 方法开始看。performTraversals 方法调用了performMeasure()方法。
private void performTraversals() {
....
if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || dispatchApplyInsets ||
updatedConfiguration) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
.....
}
}
....
}
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
View measure过程
View 的measure 过程由其measure方法来完成,measure方法是一个final类型的方法,这意味着子类不能重写此方法,在View 的measure 方法中会去调用View的onMeasure方法。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
....
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) {
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;
}
}
....
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
}
从上面的代码可以看出,首先判断和上次测量的MeasureSpec是否相等,如果不相等就重新测量。measure() 方法调用了onMeasure()方法,并且把相应的宽/高测量规格传进去。说明测量的主要的逻辑在View#onMeasure()方法中。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),
widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
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;
}
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth,mBackground.getMinimumWidth());
}
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight :
max(mMinHeight, mBackground.getMinimumHeight());
}
从以上源码来看,如果View没有设置背景,那么View的宽度为mMinWidth,而mMinWidth对应于android:minWidth 这个属性所指定的值,因此View的宽度为android:minWidth属性所指定的值。android:minWidth属性如果不指定,那么mMinWidth则默认为0;如果View指定了背景,则返回android:minWidth和背景的最小宽度这两者中的最大值,getSuggestedMinimumWidth 和 getSuggestedMinimumHeight的返回值就是View在UNSPECIFIED情况下的测量宽/高。
public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
public int getMinimumHeight() {
final int intrinsicHeight = getIntrinsicHeight();
return intrinsicHeight > 0 ? intrinsicHeight : 0;
}
从以上的代码来看,返回的是Drawable的原始宽度/高度,前提是这个Drawable 有原始宽/高度,否则就返回0;举个例子,ShapeDrawable 无原始宽/高;而BitmapDrawable有原始宽/高(图片的尺寸)。
Question: 直接继承View 的自定义控件需要重写onMeasure 方法并设置wrap_content 时自身大小,否则在布局中使用 wrap_content就相当于使用 match_parent?
Why: 如果View在布局中使用wrap_content,那么它的specMode是AT_MOST模式,在这种模式下,它的宽/高等于specSize;这种情况下View的specSize是parentSize,而parentSize是父容器中目前可以使用大小,也就是父容器当前剩余的空间大小。View的宽/高就等于父容器当前剩余的空间大小。
Answer :
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
int widthSpecMode=MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize=MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode=MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize=MeasureSpec.getSize(heightMeasureSpec);
if(widthSpecMode==MeasureSpec.AT_MOST &&heightSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(mWidth,mHeight);
}else if(widthSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(mWidth,heightSpecSize);
}else if(heightSpecMode==MeasureSpec.AT_MOST){
setMeasuredDimension(widthSpecSize,mHeight);
}
}
在上面的代码中,只需要给View指定一个默认的内部宽/高(mWidth 和mHeight),并在wrap_content时设置此宽/高即可。对于非wrap_content时,用系统的测量值即可。至于这个默认的内部宽/高的大小如何指定,这个没有固定的依据,根据需要灵活指定即可
ViewGroup 的measure 过程
对于ViewGroup来说,除了完成自己的measure 过程外,还会遍历去调用所有子元素的measure方法,各个子元素再递归去执行这个过程。ViewGroup是一个抽象类,因此它没有重写View 的onMeasure方法,但提供了measureChildren 的方法。
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
从以上的代码来看,ViewGroup在measure时,会对每一个子元素会调用measureChild方法进行测量。measureChild方法就是取出子元素的LayoutParams,然后再通过getChildMeasureSpec来创建子元素的MeasureSpec,接着将MeasureSpec直接传递给View的measure方法进行测量。
下面具体分析实例:LinearLayout 源码measureHorizontal
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
void measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) {
....
final int count = getVirtualChildCount();
....
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child.getVisibility() == GONE) {
i += getChildrenSkipCount(child, i);
continue;
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
totalWeight += lp.weight;
final boolean useExcessSpace = lp.width == 0 && lp.weight > 0;
if (widthMode == MeasureSpec.EXACTLY && useExcessSpace) {
if (isExactly) {
mTotalLength += lp.leftMargin + lp.rightMargin;
} else {
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength +
lp.leftMargin + lp.rightMargin);
}
...
}else{
...
final int usedWidth = totalWeight == 0 ? mTotalLength : 0;
measureChildBeforeLayout(child, i, widthMeasureSpec, usedWidth,
heightMeasureSpec, 0);
if (isExactly) {
mTotalLength += childWidth + lp.leftMargin + lp.rightMargin
+ getNextLocationOffset(child);
} else {
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childWidth + lp.leftMargin
+ lp.rightMargin + getNextLocationOffset(child));
}
}
}
if (useLargestChild &&
(widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED)) {
mTotalLength = 0;
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
...
if (child.getVisibility() == GONE) {
i += getChildrenSkipCount(child, i);
continue;
}
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
child.getLayoutParams();
if (isExactly) {
mTotalLength += largestChildWidth + lp.leftMargin + lp.rightMargin +
getNextLocationOffset(child);
} else {
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + largestChildWidth +
lp.leftMargin + lp.rightMargin + getNextLocationOffset(child));
}
}
}
mTotalLength += mPaddingLeft + mPaddingRight;
int widthSize = mTotalLength;
int widthSizeAndState = resolveSizeAndState(widthSize, widthMeasureSpec, 0);
widthSize = widthSizeAndState & MEASURED_SIZE_MASK;
maxHeight += mPaddingTop + mPaddingBottom;
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
setMeasuredDimension(widthSizeAndState | (childState&MEASURED_STATE_MASK),
resolveSizeAndState(maxHeight, heightMeasureSpec,
(childState<<MEASURED_HEIGHT_STATE_SHIFT)));
}
测量流程:
总结
- 对于 View 来说,其 MeasureSpec 是由其父容器 ViewGroup 的 MeasureSpec 和 View 自身的 LayoutParams 来共同决定的。
- 对于DecorView,其MearsureSpec是窗口尺寸和其自己的LayoutParames共同决定。
|