测量
经过 measure()
-> onMeasure()
-> setMeasuredDimension()
等函数的调用,最终 View 自身测量流程执行完毕。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 1.通过遍历,对每个child进行测量
for(int i = 0 ; i < getChildCount() ; i++){
View child = getChildAt(i);
// 2.计算新的布局要求,并对子控件进行测量
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
...
}
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
// 通过 padding 值,计算出子控件的布局要求
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
// 将新的布局要求传入 measure 方法,完成子控件的测量
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
// 开发者需要自己重写onMeasure函数,以自定义测量逻辑
onMeasure(widthMeasureSpec, heightMeasureSpec);
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
...
// 该方法的本质就是将测量结果存起来,以便后续的layout和draw流程中获取控件的宽高
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
}
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;
// match_parent、wrap_content则返回布局要求中的size值
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
// 在某些情况下(比如自身设置了minWidth或者background属性),View需要通过getSuggestedMinimumWidth()函数作为默认的宽度值:
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
// 获取父 View 的测量模式
int specMode = MeasureSpec.getMode(spec);
// 获取父 View 的测量大小
int specSize = MeasureSpec.getSize(spec);
// 父 View 计算出的子 View 的大小
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
case MeasureSpec.EXACTLY:
// 子View的高度或宽度>0说明其实一个确切的值,因为 match_parent 和 wrap_content 的值是<0的
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
//子View的高度或宽度为match_parent
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;//将size即父View的大小减去边距值所得到的值赋值给resultSize
resultMode = MeasureSpec.EXACTLY;//指定子View的测量模式为EXACTLY
//子View的高度或宽度为wrap_content
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;//将size赋值给result
resultMode = MeasureSpec.AT_MOST;//指定子View的测量模式为AT_MOST
}
break;
//如果父容器的测量模式是AT_MOST
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
// 因为父View的大小是受到限制值的限制,所以子View的大小也应该受到父容器的限制并且不能超过父View
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
//如果父容器的测量模式是UNSPECIFIED即父容器的大小未受限制
case MeasureSpec.UNSPECIFIED:
//如果自View的宽和高是一个精确的值
if (childDimension >= 0) {
//子View的大小为精确值
resultSize = childDimension;
//测量的模式为EXACTLY
resultMode = MeasureSpec.EXACTLY;
//子View的宽或高为match_parent
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//因为父View的大小是未定的,所以子View的大小也是未定的
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//根据resultSize和resultMode调用makeMeasureSpec方法得到测量要求,并将其作为返回值
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
// 根据 size 和 mode,创建一个测量要求
public static int makeMeasureSpec(int size, int mode) {
return size + mode;
}