View/ViewGroup 的测量(measure )流程
View/ViewGroup 的测量(measure )流程
上文可知,ViewRootImpl.performMeasure中最终是跳到了ViewGroup的Measure方法中来了,我们来看看ViewGroup.Measure()
方法。
ViewGroup的Measure
ViewGroup
没有measure方法。
稍安勿躁,viewGroup继承自view,ViewGroup没有,就看View的。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
...
if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
...
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
...
}
这里调用到了view的onMeasure()方法。
-
如果自己是ViewGroup,而ViewGroup又没有默认的onMeasure方法,所以一般继承自ViewGroup的类都会自己重写onMeasure方法。里面必定会遍历自己的子view,并执行child.measure()方法。
-
如果自己是view的话,测量自己,view.onMeasure()
为什么要把ViewGroup放在前面,因为布局最顶层的肯定是ViewGroup,最先执行ViewGroup的
measureChildren()
方法。在这个方法里就确认了其child(子view)的measureSpec。所以 等到执行到
view.onMeasure(int,int)
的时候,该view的measureSpec【出进去的那两个int】已经被确认了。
ViewGroup的measureChildren
measureChildren是viewGroup提供的测量子view的方法,继承自他的子类有的会用他,也有的会自己写,所以各不相同。这里拿ViewGroup.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);
}
}
}
只要状态不是GONE,全用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);
}
也很清晰,先用获取到子view的LayoutParams
属性,再用getChildMeasureSpec()方法
获取到childMeasureSpec
,最后调用child的measure方法。
关键代码是child的measureSpec是怎么确认的,就在 getChildMeasureSpec()
方法里。传入了三个参数:
- parent的measureSpec
- padding值
- view的LayoutParam中设置的width(或height)
(代码看到了switch case,里面还包了几个if,就知道大概什么情况了,具体解释后面有图)
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);
}
根据 parent的measureSpec 和 自己的layoutparam 的(33)9种情况组合,可以确定下自己的MeasureSpec*。具体如图:
代码的意思如此一来,从顶层的ViewGroup,到最后的View,每个View的MeasureSpec都将被确定下来。
然后children.measure又调用了measure()方法。
view的onMeasure()如下:
首先明白一点,(默认这里的view不是viewGroup)到这里为止的话,MeasureSpec已经确定下来了,下面是根据spec的值确认宽高到底长度是多少。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(
getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)
);
}
按照方法执行的顺序,由内到外看吧。
First,getSuggestedMinimumWidth
方法:
/**
* Returns the suggested minimum width that the view should use. This
* returns the maximum of the view's minimum width
* and the background's minimum width
* ({@link android.graphics.drawable.Drawable#getMinimumWidth()}).
* <p>
* When being used in {@link #onMeasure(int, int)}, the caller should still
* ensure the returned width is within the requirements of the parent.
*
* @return The suggested minimum width of the view.
*/
//返回建议的最小值。即【view的最小值 和 background的最小值】中的最大值。
//当在onMeasure中调用的时候,调用者仍应保证返回值在父级的范围之内。
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
解释我放在了注释中。一句话:返回的是系统建议的值。【getSuggestedMinimumHeight
同理,一个宽,一个高】
再看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;
}
通过specMode的值,判断到底是用系统建议的值,还是用measureSpec中的值,并将这个值返回。
最后,最外层setMeasuredDimension()
调用这个值 ,作用是用来设置View的宽高的。
/**
此方法必须由onMeasure(int, int)被调用,以存储所测量的宽度和测量高度。
如果不这样做会引发在测量时间的异常。
**/
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);
}
所以一般自己自定义view的时候,沿用这个方法就行了,这个方法传入进去的两个值就是最后的测量结果了。
通用自定义view
最后,附上一个通用自定义view的onMeasure部分的代码实现:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measure(widthMeasureSpec, true), measure(heightMeasureSpec, false));
}
protected int measure(int measureSpec, boolean WOH) {
int size = MeasureSpec.getSize(measureSpec);
int mode = MeasureSpec.getMode(measureSpec);
int measured;
if (mode == MeasureSpec.EXACTLY) {
measured = size;
} else {
int measureMinimum = WOH ? getMinimumMeasureWidth() : getMinimumMeasureHeight();
// 根据内容计算最小值
// measureMinimum = Math.max(measureMinimum, MIN_CONTENT_SIZE);
if (WOH) {
measureMinimum = Math.max(measureMinimum, measureMinimum + getPaddingLeft() + getPaddingRight());
} else {
measureMinimum = Math.max(measureMinimum, measureMinimum + getPaddingTop() + getPaddingBottom());
}
measured = measureMinimum;
if (mode == MeasureSpec.AT_MOST) {
measured = Math.min(measured, size);
}
}
return measured;
}
测量完成后,下一步,layout。