如果 View 只是一个原始的 View,通过 measure 方法就可以完成测量过程;如果是一个 ViewGroup,会先对子元素进行 measure,然后测量 ViewGroup 自身。
1:View 的 measure 过程
(ViewGroup) measureChildren() -> (ViewGroup) measureChild -> (ViewGroup) child.measure() -> measure() -> onMeasure() -> getDefaultSize() -> setMeasuredDimension()
View 的 measure
方法是一个 final 类型的,不能被重写,在该方法中会调用 onMeasure
,View 的onMeasure
实现如下:
/**
* 如果是ViewGroup,需要对所有子 View 进行测量,需要自己去实现
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
setMeasuredDimension()
:设置 View 宽高的测量值。
接下来看getDefaultSize()
的实现:
从代码中也可以看出:自定义View的时候,如果直接继承 View,并且不重写 onMeasure(),那么 AT_MOST 和 EXACTLY的测量结果是相等的,都是父容器的剩余空间大小。
/**
* 返回的大小就是View测量后的大小,多次提到测量后的大小,是因为View最终的测量是在layout阶段确定的。
* 但是几乎所有情况下View的测量大小和最终大小是相等的。
* UNSPECIFIED:一般是用于内部的测量
*/
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;
}
UNSPECIFIED:用于系统内部的测量,而此时 result 的大小是getSuggestedMinimumWidth()
的返回值:
protected int getSuggestedMinimumWidth() {
/**
* 如果没有设置背景,那么View的宽度为mMinWidth,该值对应于android:minWidth属性对应的值
* 如果不指定mMinWidth的值,默认为0;
* 如果View设置了背景,则View的宽度为max(mMinWidth, mBackground.getMinimumWidth(),
* mBackground.getMinimumWidth() 指的是Drawable的原始宽度
*/
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
/**
* 返回 Drawable 的原始宽高,前提是这个Drawable有原始宽高,否则就返回0
* shapeDrawable没有原始宽/高,BitmapDrawable有原始宽/高
*/
public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
1:ViewGroup 的 measure 过程
ViewGroup 是一个抽象类,没有重写 onMeasure()
,却提供了一个measureChildren()
,用于对子 View 进行测量。
/**
* ViewGroup没有重写measure方法,但是提供了该方法
*/
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);
}
}
}
从下面的代码可以看出来:子元素的 MeasureSpec 由子元素的 LayoutParams 和父容器的 MeasureSpec 来决定。
ViewGroup
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
/**
* 创建子元素的MeasureSpec,接着将MeasureSpec直接传递给View的measure进行测量
*/
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) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} 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 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
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);
}
ViewRootImpl:
(ViewRoot) performTraversals() -> (ViewRoot) measureHierarchy() -> (ViewGroup) measure() -> (ViewGroup) onMeasure -> (View) measure -> (View) onMeasure
1. 首先会在measureHierarchy
中根据屏幕大下和自身的 layoutparams 创建 MeasureSpec;
2. 在onMeasure 中创建 ViewGroup 的 MeasureSpec;
3. 在 View 的 onMeasure 中,测量View,如果直接继承View,使得View的wrap_content、padding有效,必须重写onMeasure处理。
4. 测量View后,接着对ViewGroup进行测量,然后测量ViewRoot。
注意:大致是这个流程,测量比较复杂,有些View甚至会多次测量。