View/ViewGroup 的测量(measure )流程

上文可知,ViewRootImpl.performMeasure中最终是跳到了ViewGroup的Measure方法中来了,我们来看看ViewGroup.Measure()方法。

ViewGroup的Measure

ViewGroup没有measure方法。

i皮.png

稍安勿躁,viewGroup继承自view,ViewGroup没有,就看View的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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来说。

1
2
3
4
5
6
7
8
9
10
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测量出来;

1
2
3
4
5
6
7
8
9
10
11
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()方法里。传入了三个参数:

  1. parent的measureSpec
  2. padding值
  3. view的LayoutParam中设置的width(或height)

(代码看到了switch case,里面还包了几个if,就知道大概什么情况了,具体解释后面有图)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
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 的(3*3)9种情况组合,可以确定下自己的MeasureSpec。具体如图:

代码的意思

如此一来,从顶层的ViewGroup,到最后的View,每个View的MeasureSpec都将被确定下来。

然后children.measure又调用了measure()方法。

view的onMeasure()如下:

首先明白一点,(默认这里的view不是viewGroup)到这里为止的话,MeasureSpec已经确定下来了,下面是根据spec的值确认宽高到底长度是多少。

1
2
3
4
5
6
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(
getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)
);
}

按照方法执行的顺序,由内到外看吧。

First,getSuggestedMinimumWidth方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    /**
* 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(),如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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的宽高的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
此方法必须由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部分的代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@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。