之前的文章《View体系(八)深入剖析View的onMeasure方法》我们深入分析了ViewonMeasure方法,我们今天就来看一下ViewGroup的测量流程。

(注:文中源码基于 Android 12

View做测量时,会调用ViewonMeasure方法,但是我们翻看ViewGroup的源码,并没有发现onMeasure方法,难道ViewGroup不用测量?显然不是,我们换一个类来看,LinearLayout继承自ViewGroup,我们从LinearLayout的源码中找到了熟悉的onMeasure方法,我们看一下源码:

1
2
3
4
5
6
7
8
9
10
11
public class LinearLayout extends ViewGroup {
...
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {//1
measureVertical(widthMeasureSpec, heightMeasureSpec);//2
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
...

注释1处根据mOrientation判断是横向排列还是纵向排列来进入不同的测量流程,我们以纵向排列为例,看LinearLayout是如何测量其高度的,进入注释2处的measureVertical方法:

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
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
...
mTotalLength = 0;
final int count = getVirtualChildCount();//1
...
for (int i = 0; i < count; ++i) {
final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {//2
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true;
} else {
final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);//3

final int childHeight = child.getMeasuredHeight();//4
if (useExcessSpace) {
lp.height = 0;
consumedExcessSpace += childHeight;
}

final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));//5

由于measureVertical方法很长,这里只贴出部分关键代码。注释1处获取子View的数量,注释2处遍历子View并根据测量模式进入不同的if-else分支,这里以wrap_content为例(即AT_MOST模式),则进入else分支,注释3处对子View进行测量,注释4处获取测量到的子View的高度,注释5处对子View的测量结果进行累加作为最终LinearLayout的测量高度。我们进入注释3的measureChildBeforeLayout方法:

1
2
3
4
5
6
void measureChildBeforeLayout(View child, int childIndex,
int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
int totalHeight) {
measureChildWithMargins(child, widthMeasureSpec, totalWidth,
heightMeasureSpec, totalHeight);
}

再进入measureChildWithMargins方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);//1
}

注释1处调用了child.measure方法:

1
2
3
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
onMeasure(widthMeasureSpec, heightMeasureSpec);

measure又调用了ViewonMeasure方法,对于ViewonMeasure方法,之前的文章《View体系(八)深入剖析View的onMeasure方法》已经讲过,这里不再赘述。

至此LinearLayout的测量已经很清楚了,即对于纵向的LinearLayout并指定高度为wrap_content来说,是通过遍历测量子View的高度并累加作为自己的高度。至于为什么ViewGroup没有onMeasure方法,是因为它无法统一究竟要以什么方式来测量,所以它要让它的子类来各自实现测量方法,最终它的子类又重写onMeasure完成对自己的测量。

关注我