什么是MeasureSpec
在看代码之前,先要了解MeasureSpec这个概念
MeasureSpec是View的一个静态内部类,用于记录view的大小;通过父View的measurespec和子view的layoutparams可以得到子view的MeasureSpec
MeasureSpec由specSize和specMode组成,为了节省内存,压缩成一个32位的int值。
他的mode有三种:
1. UNSPECIFIED:父view对子view没有任何限制,可以为子view想要的任何值
2.EXACTLY:如果子view没有设置确切的大小,将由父view决定
3.AT_MOST:在不超过父view大小的情况下,子view自己决定多大
View的onMeasure流程
View的Measure过程是从ViewRoot的performTraversals()方法开始的,在viewrotImpl的performTraverSals()方法中可以看到
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
可以看到performMeasure(widthMesaureSpec.heightMeasureSpec)的参数是来自getRootMeasureSpec(mWidth,lp.width),再看getRootMeasureSpec()方法
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
这里的第一个windowSize为当前window可用的宽度或高度,第二rootDimension传入的是我们在xml或者layoutparams中定义的宽和高,有三种,具体值,match_parent,wrap_content;通过getRootMeasureSpec()方法生成measureSpec。
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);
}
}
performMeasure()方法里比较简单,直接调用mView.measure();measure()里又会调用到onMeasure(widhtMeasureSpec,heightMeasureSpec),参数也是来自前面performTraversals()中生成的
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
先来看getSuggestedMiniumWidth()和getSuggestedMiniumHeight()
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
public int getMinimumHeight() {
return mMinHeight;
}
这两个方法都是返回当前view可能的最小宽高度。
再看getDefaultSize()方法
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;
}
这里返回的result是int,不是MeasureSpec;
- 在UNSPECIFIED模式下,返回的是前面getSuggestedMinWidth()或者getSuggestedMinHeight()的值
- 在AT_MOST和EXACTLY模式时,返回的是measureSpec中的测量值。
最后,onmeasure()调用setMeasureDimension()
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
这里做的就是将算出的MeasureWidth和MeasureHeight进行赋值,只有这步之后,view.getHeight()或者view.getWidth()才有值。
至此,onMeasure()的流程就完成了。
ViewGroup的onMeasure流程:
在ViewGroup中,通常不止一个view,所以要对它的子view一个个进行测量大小,在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);
}
}
}
代码也比较清楚,就是对一个个子view调用measureChild()
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);
}
来看getChildMeasurespec方法
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//这里spec是父view的measureSpec,所以specSize和specMode是父类的
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);
}
getChildMeasureSpec总结下来就是,根据父view的MeasureSpec,来确定子view的测绘类型和大小,写成表格如下。
父view/子view | childDimension >= 0 | childDimension = WRAP_CONTENT | childDimension =MATCH_PARENT |
EXACTLY | EXACTLY | AT_MOST | EXACTLY |
AT_MOST | EXACTLY | AT_MOST | AT_MOST |
UNSPECIFIED | EXACTLY | UNSPECIFIED | UNSPECIFIED |
这里通过getChildMeasureSpec()获取子view的宽高measureSpec,然后调用child.measure(),又会走到view的measure()进行递归。