LinearLayout源码解读

LinearLayout基础

LinearLayout所具有的属性:

  • orientation:视图的布局方向,默认值:-1;
  • gravity:绘制起始点,默认值:-1;
  • baselineAligned:基准线对齐,其效果可以通过修改xml的属性值直接看到效果,默认值:true;
  • weightSum:子视图权重和,默认值:-1.0f;
  • baselineAlignedChildIndex:以第Index个子视图的基准线为对齐,该LinearLayout下的view以
    某个继承TextView的View的基线对齐,默认值:-1;
  • measureWithLargestChild:以最大子视图宽高,为其子视图的宽高,其起作用前提是为true,且LinearLayout在该方向的宽或高为warp_content,且子视图具有权重。默认值:false;
  • divider:分割线;
  • showDividers:分割线显示样式(middle|end|beginning|non),默认值:SHOW_DIVIDER_NONE;
  • dividerPadding:分割线内边距,默认值:0;

    解释:

  • 基准线
    其主要作用是在绘制字母的时候有个基线对齐,这个类似我们学习英语字母的时候用的四线谱:
    基线示意图
    其中红线就是基线(baseline),和下面我们书写英语字母的四线谱是不是很像,基线就是第三条。
    英语书写四线谱
  • 源码之垂直方向测量(void measureVertical(int widthMeasureSpec, int heightMeasureSpec))
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
/**
* Measures the children when the orientation of this LinearLayout is set
* to {@link #VERTICAL}.
*
* @param widthMeasureSpec Horizontal space requirements as imposed by the parent.
* @param heightMeasureSpec Vertical space requirements as imposed by the parent.
*
* @see #getOrientation()
* @see #setOrientation(int)
* @see #onMeasure(int, int)
*/
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
// mTotalLength作为LinearLayout成员变量,其主要目的是在测量的时候通过累加得到所有子控件的高度和(Vertical)或者宽度和(Horizontal)
mTotalLength = 0;
// maxWidth用来记录所有子控件中控件宽度最大的值。
int maxWidth = 0;
// 子控件的测量状态,会在遍历子控件测量的时候通过combineMeasuredStates来合并上一个子控件测量状态与当前遍历到的子控件的测量状态,采取的是按位相或
int childState = 0;

/**
* 以下两个最大宽度跟上面的maxWidth最大的区别在于matchWidthLocally这个参数
* 当matchWidthLocally为真,那么以下两个变量只会跟当前子控件的左右margin和相比较取大值
* 否则,则跟maxWidth的计算方法一样
*/
// 子控件中layout_weight<=0的View的最大宽度
int alternativeMaxWidth = 0;
// 子控件中layout_weight>0的View的最大宽度
int weightedMaxWidth = 0;
// 是否子控件全是match_parent的标志位,用于判断是否需要重新测量
boolean allFillParent = true;
// 所有子控件的weight之和
float totalWeight = 0;

// 如您所见,得到所有子控件的数量,准确的说,它得到的是所有同级子控件的数量
// 在官方的注释中也有着对应的例子
// 比如TableRow,假如TableRow里面有N个控件,而LinearLayout(TableLayout也是继承LinearLayout哦)下有M个TableRow,那么这里返回的是M,而非M*N
// 但实际上,官方似乎也只是直接返回getChildCount(),起这个方法名的原因估计是为了让人更加的明白,毕竟如果是getChildCount()可能会让人误认为为什么没有返回所有(包括不同级)的子控件数量
final int count = getVirtualChildCount();

// 得到测量模式
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

// 当子控件为match_parent的时候,该值为ture,同时判定的还有上面所说的matchWidthLocally,这个变量决定了子控件的测量是父控件干预还是填充父控件(剩余的空白位置)。
boolean matchWidth = false;

boolean skippedMeasure = false;

final int baselineChildIndex = mBaselineAlignedChildIndex;
final boolean useLargestChild = mUseLargestChild;

int largestChildHeight = Integer.MIN_VALUE;

// See how tall everyone is. Also remember max width.

//查看每一个高,并记住最大宽度
for (int i = 0; i < count; ++i) {
//首先获取子View
final View child = getVirtualChildAt(i);
//如果子View是null就继续测量下一个子View
if (child == null) {
// 目前而言,measureNullChild()方法返回的永远是0,估计是设计者留下来以后或许有补充的。
mTotalLength += measureNullChild(i);
continue;
}
//如果子View是GONE的也不算在总高度里面,这里也能看出GONE和INVISIBLE的区别
if (child.getVisibility() == View.GONE) {
// 同上,返回的都是0。
// 事实上这里的意思应该是当前遍历到的View为Gone的时候,就跳过这个View,下一句的continue关键字也正是这个意思。
// 忽略当前的View,这也就是为什么Gone的控件不占用布局资源的原因。(毕竟根本没有分配空间)
i += getChildrenSkipCount(child, i);
continue;
}

// 根据showDivider的值(before/middle/end)来决定遍历到当前子控件时,高度是否需要加上divider的高度
// 比如showDivider为before,那么只会在第0个子控件测量时加上divider高度,其余情况下都不加
if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerHeight;
}
//有时候我们在代码里面通过Inflater服务,动态加载一个布局,然后去设置他的LayoutParams,
//如果不引用父容器的LayoutParams就会报一个强转错误,原因就在这个父容器在add,measure的时候都会
//把子View的LayoutParams强转成自己的类型
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
//得到每个子控件的LayoutParams后,累加权重和,后面用于跟weightSum相比较
totalWeight += lp.weight;


// 我们都知道,测量模式有三种:
// * UNSPECIFIED:父控件对子控件无约束,基本没有用到
// * Exactly:父控件对子控件强约束,子控件永远在父控件边界内,越界则裁剪。如果要记忆的话,可以记忆为有对应的具体数值或者是Match_parent
// * AT_Most:子控件为wrap_content的时候,测量值为AT_MOST。

// 下面的if/else分支都是跟weight相关
//这里就值得注意下了如果当前的LinearLayout是EXACTLY模式,且子view的高度为0,且权重大于0
//这个子view只有在LinearLayout高度有剩余的时候,才会根据权重的占比去平分剩余空间
//上文说的二次测量也就指的这部分
if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
// Optimization: don't bother measuring children who are going to use
// leftover space. These views will get measured again down below if
// there is any leftover space.
// 这个if里面需要满足三个条件:
// * LinearLayout的高度为match_parent(或者有具体值)
// * 子控件的高度为0
// * 子控件的weight>0

// 如果LinearLayout的垂直方向测量模式是EXACTLY,即确定值,且子视图的高度为0,weight大于0,
//则先将总高度加上子视图的topMargin和bottomMargin,并设置skippedMeasure(暂时跳过测量标识)为true

// 这其实就是我们通常情况下用weight时的写法,此时需要记住view的topMargin和bottomMargin(对于方向为)
// 测量到这里的时候,会给个标志位,稍后再处理。此时会计算总高度
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true;
} else {
// 到这个分支,则需要对不同的情况进行测量
int oldHeight = Integer.MIN_VALUE;

if (lp.height == 0 && lp.weight > 0) {
// heightMode is !!either UNSPECIFIED or AT_MOST!!, and this
// child wanted to stretch to fill available space.
// Translate that to WRAP_CONTENT so that it does not end up
// with a height of 0
// 满足这两个条件,意味着父类即LinearLayout是wrap_content,或者mode为UNSPECIFIED
// 那么此时将当前子控件的高度置为wrap_content
// 为何需要这么做,主要是因为当父类为wrap_content时,其大小实际上由子控件控制
// 我们都知道,自定义控件的时候,通常我们会指定测量模式为wrap_content时的默认大小
// 这里强制给定为wrap_content为的就是防止子控件高度为0.

//这里其实官方的注释讲了也挺清楚的,到了这步,当前的LinearLayout的模式
//肯定是UNSPECIFIED或者MOST,因为EXACTLY模式会进入上一个判断
//然后把子View的高度赋值成-1(WRAP_CONTENT)
// 如果垂直方向测量模式为UNSPECIFIED或AT_MOST,同时子视图想要尽量获取可用的剩余空间,
//把子视图的高度改为WRAP_CONTENT,这样子视图的最终高度就不会是0

oldHeight = 0;
lp.height = LayoutParams.WRAP_CONTENT;
}

// Determine how big this child would like to be. If this or
// previous children have given a weight, then we allow it to
// use all available space (and we will shrink things later
// if needed).
/**【1】*/
// 下面这句虽然最终调用的是ViewGroup通用的同名方法,但传入的height值是跟平时不一样的
// 这里可以看到,传入的height是跟weight有关,关于这里,稍后的文字描述会着重阐述

// 这个函数最后会调用child.measure(childWidthMeasureSpec, childHeightMeasureSpec)
//测量出子视图要占用多大空间,并设置子视图的mMeasuredWidth和mMeasuredHeight
measureChildBeforeLayout(
child, i, widthMeasureSpec, 0, heightMeasureSpec,
totalWeight == 0 ? mTotalLength : 0);

// 重置子控件高度,然后进行精确赋值
if (oldHeight != Integer.MIN_VALUE) {
lp.height = oldHeight;
}

final int childHeight = child.getMeasuredHeight();
final int totalLength = mTotalLength;

// getNextLocationOffset返回的永远是0,因此这里实际上是比较child测量前后的总高度,取大值。
//加上子View的margin值
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));

// 重新设置最大子视图高度
if (useLargestChild) {
largestChildHeight = Math.max(childHeight, largestChildHeight);
}
}

/**
* If applicable, compute the additional offset to the child's baseline
* we'll need later when asked {@link #getBaseline}.
*/
// 计算子视图baseline的偏移量
if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {
mBaselineChildTop = mTotalLength;
}

// if we are trying to use a child index for our baseline, the above
// book keeping only works if there are no children above it with
// weight. fail fast to aid the developer.
// 如果要为baseline指定子视图索引,只有在此子视图之上的视图没有设置weight属性时才有效
if (i < baselineChildIndex && lp.weight > 0) {
throw new RuntimeException("A child of LinearLayout with index "
+ "less than mBaselineAlignedChildIndex has weight > 0, which "
+ "won't work. Either remove the weight, or don't set "
+ "mBaselineAlignedChildIndex.");
}


// 下面开始测量宽度
boolean matchWidthLocally = false;

// 还记得我们变量里又说到过matchWidthLocally这个东东吗
// 当父类(LinearLayout)不是match_parent或者精确值的时候,但子控件却是一个match_parent
// 那么matchWidthLocally和matchWidth置为true
// 意味着这个控件将会占据父类(水平方向)的所有空间
if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
// The width of the linear layout will scale, and at least one
// child said it wanted to match our width. Set a flag
// indicating that we need to remeasure at least that view when
// we know our width.
//如果LinearLayout宽度不是已确定的,如wrap_content,而子视图是MATCH_PARENT,
matchWidth = true;
matchWidthLocally = true;
}

// 计算子视图总宽度(包含左右外边距)
final int margin = lp.leftMargin + lp.rightMargin;
final int measuredWidth = child.getMeasuredWidth() + margin;
maxWidth = Math.max(maxWidth, measuredWidth);
// 合并子元素的测量状态
childState = combineMeasuredStates(childState, child.getMeasuredState());

// 子视图宽度是否都为MATCH_PARENT
allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;
if (lp.weight > 0) {
/*
* Widths of weighted Views are bogus if we end up
* remeasuring, so keep them separate.
*/
//如设置了weigh属性,则子视图的宽度需要在父视图确定后才能确定。这里并不是真实的宽度
weightedMaxWidth = Math.max(weightedMaxWidth,
matchWidthLocally ? margin : measuredWidth);
} else {
alternativeMaxWidth = Math.max(alternativeMaxWidth,
matchWidthLocally ? margin : measuredWidth);
}

i += getChildrenSkipCount(child, i);
}
//for 循环结束

// 下面的这一段代码主要是为useLargestChild属性服务的,不在本文主要分析范围,略过
if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
mTotalLength += mDividerHeight;
}

if (useLargestChild &&
(heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
mTotalLength = 0;

// 如果设置了useLargestChild属性,且LinearLayout的垂直方向测量模式是AT_MOST或UNSPECIFIED,
//重新测量总高度,useLargestChild属性会使所有带weight属性的子视图具有最大子视图的最小尺寸
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);

if (child == null) {
mTotalLength += measureNullChild(i);
continue;
}

if (child.getVisibility() == GONE) {
i += getChildrenSkipCount(child, i);
continue;
}

final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
child.getLayoutParams();
// Account for negative margins
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
}
}
//在这两段代码之间还有些杂七杂八的处理,如果读者有兴趣可以自己阅读分析下
//当测量完子View的大小后,总高度会再加上padding的高度
// Add in our padding
mTotalLength += mPaddingTop + mPaddingBottom;

int heightSize = mTotalLength;
//如果设置了minimumheight属性,会根据当前使用高度和最小高度进行比较
//然后取两者中大的值,getSuggestedMinimumHeight为背景的最小高和视图设置的最小高的大值
// Check against our minimum height
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());

// Reconcile our calculated size with the heightMeasureSpe
// 把测量出来的高度与测量模式进行匹配,得到最终的高度,MeasureSpec实际上是一个32位的int,高两位是测量模式,
//剩下的就是大小,因此heightSize = heightSizeAndState & MEASURED_SIZE_MASK;作用就是用来得到大小的精确值(不含测量模式)
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
heightSize = heightSizeAndState & MEASURED_SIZE_MASK;

//到了这里,会再对带weight属性的子View进行一次测绘
//首先计算剩余高度

//算出剩余空间,假如之前是skipp的话,那么几乎可以肯定是有剩余空间(同时有weight)的
// Either expand children with weight to take up available space or
// shrink them if they extend beyond our current bounds. If we skipped
// measurement on any children, we need to measure them now.
int delta = heightSize - mTotalLength;
if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {
//如果设置了weightSum就会使用你设置的weightSum,否则采用当前所有子View的权重和。所以如果要手动设置weightSum的时候,千万别计算错误哦
float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;

mTotalLength = 0;
//这里的代码就和第一次测量很像了
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);

if (child.getVisibility() == View.GONE) {
continue;
}

LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();

float childExtra = lp.weight;
if (childExtra > 0) {
// 全篇最精华的一个地方。。。。拥有weight的时候计算方式,ps:执行到这里时,child依然还没进行自身的measure
//子控件的weight占比*剩余高度
// Child said it could absorb extra space -- give him his share
int share = (int) (childExtra * delta / weightSum);
// weightSum计余
weightSum -= childExtra;
//剩余高度减去分配出去的高度
delta -= share;

final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
mPaddingLeft + mPaddingRight +
lp.leftMargin + lp.rightMargin, lp.width);
//如果是当前LinearLayout的模式是EXACTLY
//那么这个子View是没有被测量过的,就需要测量一次
//如果不是EXACTLY的,在第一次循环里就被测量一些了
// TODO: Use a field like lp.isMeasured to figure out if this
// child has been previously measured
if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {
// child was measured once already above...
// base new measurement on stored values
//如果是非EXACTLY模式下的子View就再加上
//weight分配占比*剩余高度
// 上面已经测量过这个子视图,把上面测量的结果加上根据weight分配的大小
int childHeight = child.getMeasuredHeight() + share;
if (childHeight < 0) {
childHeight = 0;
}

//重新测量一次,因为高度发生了变化
child.measure(childWidthMeasureSpec,
MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
} else {
// child was skipped in the loop above.
// Measure for this first time here

//如果是EXACTLY模式下的
//这里只会把weight占比所拥有的高度分配给你的子View
// 上面测量的时候被跳过,那么在这里进行测量
child.measure(childWidthMeasureSpec,
MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,
MeasureSpec.EXACTLY));
}

// Child may now not fit in vertical dimension.
childState = combineMeasuredStates(childState, child.getMeasuredState()
& (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
}

final int margin = lp.leftMargin + lp.rightMargin;
final int measuredWidth = child.getMeasuredWidth() + margin;
maxWidth = Math.max(maxWidth, measuredWidth);

boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&
lp.width == LayoutParams.MATCH_PARENT;

alternativeMaxWidth = Math.max(alternativeMaxWidth,
matchWidthLocally ? margin : measuredWidth);

allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;

final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
}
// 这里得到最终高度
// Add in our padding
mTotalLength += mPaddingTop + mPaddingBottom;
// TODO: Should we recompute the heightSpec based on the new total length?
} else {
// 没有weight的情况下,只看useLargestChild参数,如果都无相关,那就走layout流程了,因此这里忽略
alternativeMaxWidth = Math.max(alternativeMaxWidth,weightedMaxWidth);


// We have no limit, so make all weighted views as tall as the largest child.
// Children will have already been measured once.
// 使所有具有weight属性 视图都和最大子视图一样高,子视图可能在上面已经被测量过一次
if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);

if (child == null || child.getVisibility() == View.GONE) {
continue;
}

final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();

float childExtra = lp.weight;
if (childExtra > 0) {
child.measure(
MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),
MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(largestChildHeight,
MeasureSpec.EXACTLY));
}
}
}
}

if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
maxWidth = alternativeMaxWidth;
}

maxWidth += mPaddingLeft + mPaddingRight;

// Check against our minimum width
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

// 设置测量完的宽高
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);

if (matchWidth) {
// 使宽度一致
forceUniformWidth(count, heightMeasureSpec);
}
}

在垂直绘制中主要执行逻辑在两大块代码,第一个for循环,第二个if判断中的for循环,接下来我们分块分析该函数源码:

变量的定义

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
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {      
// mTotalLength作为LinearLayout成员变量,其主要目的是在测量的时候通过累加得到所有子控件的高度和(Vertical)或者宽度和(Horizontal)
mTotalLength = 0;
// maxWidth用来记录所有子控件中控件宽度最大的值。
int maxWidth = 0;
// 子控件的测量状态,会在遍历子控件测量的时候通过combineMeasuredStates来合并上一个子控件测量状态与当前遍历到的子控件的测量状态,采取的是按位相或
int childState = 0;

/**
* 以下两个最大宽度跟上面的maxWidth最大的区别在于matchWidthLocally这个参数
* 当matchWidthLocally为真,那么以下两个变量只会跟当前子控件的左右margin和相比较取大值
* 否则,则跟maxWidth的计算方法一样
*/
// 子控件中layout_weight<=0的View的最大宽度
int alternativeMaxWidth = 0;
// 子控件中layout_weight>0的View的最大宽度
int weightedMaxWidth = 0;
// 是否子控件全是match_parent的标志位,用于判断是否需要重新测量
boolean allFillParent = true;
// 所有子控件的weight之和
float totalWeight = 0;

// 如您所见,得到所有子控件的数量,准确的说,它得到的是所有同级子控件的数量
// 在官方的注释中也有着对应的例子
// 比如TableRow,假如TableRow里面有N个控件,而LinearLayout(TableLayout也是继承LinearLayout哦)下有M个TableRow,那么这里返回的是M,而非M*N
// 但实际上,官方似乎也只是直接返回getChildCount(),起这个方法名的原因估计是为了让人更加的明白,毕竟如果是getChildCount()可能会让人误认为为什么没有返回所有(包括不同级)的子控件数量
final int count = getVirtualChildCount();

// 得到测量模式
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

// 当子控件为match_parent的时候,该值为ture,同时判定的还有上面所说的matchWidthLocally,这个变量决定了子控件的测量是父控件干预还是填充父控件(剩余的空白位置)。
boolean matchWidth = false;

boolean skippedMeasure = false;

final int baselineChildIndex = mBaselineAlignedChildIndex;
final boolean useLargestChild = mUseLargestChild;

int largestChildHeight = Integer.MIN_VALUE;

// ...... 底下两个for循环
}

在变量定义中,我们主要留意三个方面:

  • mTotalLength:这个就是最终得到的整个LinearLayout的高度(子控件高度累加及自身padding)
  • 三个跟width相关的变量
  • weight相关的变量

第一个for代码块和baselineChildIndex处理

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
// ...上面的一大堆局部变量
for (int i = 0; i < count; ++i) {

final View child = getVirtualChildAt(i);

if (child == null) {
// 目前而言,measureNullChild()方法返回的永远是0,估计是设计者留下来以后或许有补充的。
mTotalLength += measureNullChild(i);
continue;
}

if (child.getVisibility() == GONE) {
// 同上,返回的都是0。
// 事实上这里的意思应该是当前遍历到的View为Gone的时候,就跳过这个View,下一句的continue关键字也正是这个意思。
// 忽略当前的View,这也就是为什么Gone的控件不占用布局资源的原因。(毕竟根本没有分配空间)
i += getChildrenSkipCount(child, i);
continue;
}

// 根据showDivider的值(before/middle/end)来决定遍历到当前子控件时,高度是否需要加上divider的高度
// 比如showDivider为before,那么只会在第0个子控件测量时加上divider高度,其余情况下都不加
if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerWidth;
}

final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
child.getLayoutParams();
// 得到每个子控件的LayoutParams后,累加权重和,后面用于跟weightSum相比较
totalWeight += lp.weight;

// 我们都知道,测量模式有三种:
// * UNSPECIFIED:父控件对子控件无约束
// * Exactly:父控件对子控件强约束,子控件永远在父控件边界内,越界则裁剪。如果要记忆的话,可以记忆为有对应的具体数值或者是Match_parent
// * AT_Most:子控件为wrap_content的时候,测量值为AT_MOST。

// 下面的if/else分支都是跟weight相关
if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
// 这个if里面需要满足三个条件:
// * LinearLayout的高度为match_parent(或者有具体值)
// * 子控件的高度为0
// * 子控件的weight>0
// 这其实就是我们通常情况下用weight时的写法
// 测量到这里的时候,会给个标志位,稍后再处理。此时会计算总高度
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true;
} else {
// 到这个分支,则需要对不同的情况进行测量
int oldHeight = Integer.MIN_VALUE;

if (lp.height == 0 && lp.weight > 0) {
// 满足这两个条件,意味着父类即LinearLayout是wrap_content,或者mode为UNSPECIFIED
// 那么此时将当前子控件的高度置为wrap_content
// 为何需要这么做,主要是因为当父类为wrap_content时,其大小实际上由子控件控制
// 我们都知道,自定义控件的时候,通常我们会指定测量模式为wrap_content时的默认大小
// 这里强制给定为wrap_content为的就是防止子控件高度为0.
oldHeight = 0;
lp.height = LayoutParams.WRAP_CONTENT;
}

/**【1】*/
// 下面这句虽然最终调用的是ViewGroup通用的同名方法,但传入的height值是跟平时不一样的
// 这里可以看到,传入的height是跟weight有关,关于这里,稍后的文字描述会着重阐述
measureChildBeforeLayout(
child, i, widthMeasureSpec, 0, heightMeasureSpec,
totalWeight == 0 ? mTotalLength : 0);

// 重置子控件高度,然后进行精确赋值
if (oldHeight != Integer.MIN_VALUE) {
lp.height = oldHeight;
}

final int childHeight = child.getMeasuredHeight();
final int totalLength = mTotalLength;
// getNextLocationOffset返回的永远是0,因此这里实际上是比较child测量前后的总高度,取大值。
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));

if (useLargestChild) {
largestChildHeight = Math.max(childHeight, largestChildHeight);
}
}

if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {
mBaselineChildTop = mTotalLength;
}

if (i < baselineChildIndex && lp.weight > 0) {
throw new RuntimeException("A child of LinearLayout with index "
+ "less than mBaselineAlignedChildIndex has weight > 0, which "
+ "won't work. Either remove the weight, or don't set "
+ "mBaselineAlignedChildIndex.");
}

boolean matchWidthLocally = false;

// 还记得我们变量里又说到过matchWidthLocally这个东东吗
// 当父类(LinearLayout)不是match_parent或者精确值的时候,但子控件却是一个match_parent
// 那么matchWidthLocally和matchWidth置为true
// 意味着这个控件将会占据父类(水平方向)的所有空间
if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {
matchWidth = true;
matchWidthLocally = true;
}

final int margin = lp.leftMargin + lp.rightMargin;
final int measuredWidth = child.getMeasuredWidth() + margin;
maxWidth = Math.max(maxWidth, measuredWidth);
childState = combineMeasuredStates(childState, child.getMeasuredState());

allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;

if (lp.weight > 0) {
weightedMaxWidth = Math.max(weightedMaxWidth,
matchWidthLocally ? margin : measuredWidth);
} else {
alternativeMaxWidth = Math.max(alternativeMaxWidth,
matchWidthLocally ? margin : measuredWidth);
}

i += getChildrenSkipCount(child, i);
}
//... 底下第二个for循环
}
```
在第一个for循环中,主要是if(){}else{}分支,判断是heightMode == MeasureSpec.EXACTLY &&
lp.height == 0 && lp.weight > 0,这个主要是linearLayout测量模式为EXACTLY且子视图明确
了是使用linearLayout的剩余空间,此时将其上下间距计入总高度并以之前的做对比去大值,并设置
skippedMeasure标志为true。而在else中则为复杂点,else中首先对lp.height == 0 &&
lp.weight > 0的子视图的height做预处理使其为LayoutParams.WRAP_CONTENT(因为父类即
LinearLayout此时是wrap_content,或者mode为UNSPECIFIED),接着对子视图进行测量(
这个受总权重影响),并将其高度和上下间距计入到总高度中。之后对baselineChildIndex做处理,
计入总的基线高度并判定基线配置是否合理,不合理抛出异常,最后根据子视图设置最大宽度、
allFillParent、weightedMaxWidth或alternativeMaxWidth变量。

#### 第2个重要代码块(if(){}else{}分支))执行前的处理 ####

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
// … 局部变量定义和第一个for循环
// 下面的这一段代码主要是为useLargestChild属性服务的,不在本文主要分析范围,略过
if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
mTotalLength += mDividerHeight;
}

  if (useLargestChild &&
          (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
      mTotalLength = 0;

      // 如果设置了useLargestChild属性,且LinearLayout的垂直方向测量模式是AT_MOST或UNSPECIFIED,
//重新测量总高度,useLargestChild属性会使所有带weight属性的子视图具有最大子视图的最小尺寸
      for (int i = 0; i < count; ++i) {
          final View child = getVirtualChildAt(i);

          if (child == null) {
              mTotalLength += measureNullChild(i);
              continue;
          }

          if (child.getVisibility() == GONE) {
              i += getChildrenSkipCount(child, i);
              continue;
          }

          final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
                  child.getLayoutParams();
          // Account for negative margins
          final int totalLength = mTotalLength;
          mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
                  lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
      }
  }
  //... 第2个重要代码块
}
1
2
3
4
5
这里主要对useLargestChild属性处理,执行前提是设置了useLargestChild属性,且LinearLayout的垂直
方向测量模式是AT_MOST或UNSPECIFIED,重新测量总高度,useLargestChild属性会使所有带weight属性的子视
图具有最大子视图的最小尺寸

第2个重要代码块(if(){}else{}分支))执行逻辑

//当测量完子View的大小后,总高度会再加上padding的高度
// Add in our padding
mTotalLength += mPaddingTop + mPaddingBottom;

int heightSize = mTotalLength;
//如果设置了minimumheight属性,会根据当前使用高度和最小高度进行比较
//然后取两者中大的值,getSuggestedMinimumHeight为背景的最小高和视图设置的最小高的大值
// Check against our minimum height
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());

// Reconcile our calculated size with the heightMeasureSpe
// 把测量出来的高度与测量模式进行匹配,得到最终的高度,MeasureSpec实际上是一个32位的int,高两位是测量模式,剩下的就是大小,因此heightSize = heightSizeAndState & MEASURED_SIZE_MASK;作用就是用来得到大小的精确值(不含测量模式)
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
heightSize = heightSizeAndState & MEASURED_SIZE_MASK;

//到了这里,会再对带weight属性的子View进行一次测绘
//首先计算剩余高度

//算出剩余空间,假如之前是skipp的话,那么几乎可以肯定是有剩余空间(同时有weight)的
// Either expand children with weight to take up available space or
// shrink them if they extend beyond our current bounds. If we skipped
// measurement on any children, we need to measure them now.
int delta = heightSize - mTotalLength;
if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {
    //如果设置了weightSum就会使用你设置的weightSum,否则采用当前所有子View的权重和。所以如果要手动设置weightSum的时候,千万别计算错误哦
    float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;

    mTotalLength = 0;
    //这里的代码就和第一次测量很像了
    for (int i = 0; i < count; ++i) {
        final View child = getVirtualChildAt(i);

        if (child.getVisibility() == View.GONE) {
            continue;
        }

        LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();

        float childExtra = lp.weight;
        if (childExtra > 0) {
            // 全篇最精华的一个地方。。。。拥有weight的时候计算方式,ps:执行到这里时,child依然还没进行自身的measure
            //子控件的weight占比*剩余高度
            // Child said it could absorb extra space -- give him his share
            int share = (int) (childExtra * delta / weightSum);
            // weightSum计余
            weightSum -= childExtra;
            //剩余高度减去分配出去的高度
            delta -= share;

            final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                    mPaddingLeft + mPaddingRight +
                            lp.leftMargin + lp.rightMargin, lp.width);
            //如果是当前LinearLayout的模式是EXACTLY
            //那么这个子View是没有被测量过的,就需要测量一次
            //如果不是EXACTLY的,在第一次循环里就被测量一些了
            // TODO: Use a field like lp.isMeasured to figure out if this
            // child has been previously measured
            if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {
                // child was measured once already above...
                // base new measurement on stored values
                //如果是非EXACTLY模式下的子View就再加上
                //weight分配占比*剩余高度
                // 上面已经测量过这个子视图,把上面测量的结果加上根据weight分配的大小
                int childHeight = child.getMeasuredHeight() + share;
                if (childHeight < 0) {
                    childHeight = 0;
                }

                //重新测量一次,因为高度发生了变化
                child.measure(childWidthMeasureSpec,
                        MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
            } else {
                // child was skipped in the loop above.
                // Measure for this first time here  

                //如果是EXACTLY模式下的
                //这里只会把weight占比所拥有的高度分配给你的子View
                // 上面测量的时候被跳过,那么在这里进行测量    
                child.measure(childWidthMeasureSpec,
                        MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,
                                MeasureSpec.EXACTLY));
            }

            // Child may now not fit in vertical dimension.
            childState = combineMeasuredStates(childState, child.getMeasuredState()
                    & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
        }

        final int margin =  lp.leftMargin + lp.rightMargin;
        final int measuredWidth = child.getMeasuredWidth() + margin;
        maxWidth = Math.max(maxWidth, measuredWidth);

        boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&
                lp.width == LayoutParams.MATCH_PARENT;

        alternativeMaxWidth = Math.max(alternativeMaxWidth,
                matchWidthLocally ? margin : measuredWidth);

        allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;

        final int totalLength = mTotalLength;
        mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +
                lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
    }
    // 这里得到最终高度
    // Add in our padding
    mTotalLength += mPaddingTop + mPaddingBottom;
    // TODO: Should we recompute the heightSpec based on the new total length?
} else {
     // 没有weight的情况下,只看useLargestChild参数,如果都无相关,那就走layout流程了,因此这里忽略
    alternativeMaxWidth = Math.max(alternativeMaxWidth,weightedMaxWidth);


    // We have no limit, so make all weighted views as tall as the largest child.
    // Children will have already been measured once.
    // 使所有具有weight属性 视图都和最大子视图一样高,子视图可能在上面已经被测量过一次
    if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {
        for (int i = 0; i < count; i++) {
            final View child = getVirtualChildAt(i);

            if (child == null || child.getVisibility() == View.GONE) {
                continue;
            }

            final LinearLayout.LayoutParams lp =
                    (LinearLayout.LayoutParams) child.getLayoutParams();

            float childExtra = lp.weight;
            if (childExtra > 0) {
                child.measure(
                        MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),
                                MeasureSpec.EXACTLY),
                        MeasureSpec.makeMeasureSpec(largestChildHeight,
                                MeasureSpec.EXACTLY));
            }
        }
    }
}

```
在进入ifelse分支前,先计算视图的总高度,并与测量模式进行比较(resolveSizeAndState)得到最终高度,
在减去总高度,得到最终还剩多高(也就是可以分配给带权重的视图的高);
ifelse首先判断(skippedMeasure || delta != 0 && totalWeight > 0.0f),

  • 如果该条件为true,先将总高度置为0再进入for循环,此处根据子视图的权重,再次判定(lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)

    • true:子视图再次测量 则计算子视图可以得到多少高(有可能为负,也就是子视图要吐出一定高度出来),然后子视图测量高度和分配的高度相加,小于0,则重置为0,最后在测量一次。
    • false:直接测量子视图,这个是之前被跳过没有测量的子视图;
      最后再次测量视图的宽和总高度。

    • 如果该条件为false:看useLargestChild参数,如果都无相关,那就走layout流程了,
      我们可以看到这里直接判断是useLargestChild && heightMode != MeasureSpec.EXACTLY,如果条件成立的话,遍历子视图,再次判断子视图是否含有权重,如果有则直接将子视图高度都是largestChildHeight。如果条件不成立,则啥也不做。

最后就是视图maxWidth计算,并setMeasuredDimension(这个是一定要的),自此垂直方向已经测量完毕。

总结
这里大篇幅讲解measureVertical()的流程,事实上对于LinearLayout来说,其最大的特性也正是两个
方向的排布以及weight的计算方式。回过头来看测量过程,我们可以看出设计者的测量计算思路,就是将有weight
和不含有weight的测量分开处理,再利用height跟0比较来更加的细分每一种情况。
最后我们在理下其测量不同情况和原理:

  • 父控件为match_parent(或者精确值),子控件拥有weight,并且高度给定为0,也即子控件明确表示使用剩余空间:
    • 子控件的高度比例将会跟我们分配的layout_weight一致,原因在于weight二次测量时走了else分支,传入的是计算出来的share值;
  • 父控件是match_parent(或者精确值),子控件拥有weight,但高度给定为match_parent(或者精确值),子控件使用自己的高度或者父控件的高度,但在父控件空间不足时,其大小可以调整:
    • 子控件高度比例将会跟我们分配的layout_weight相反,原因在于在此之前子控件测量过一次,同时子控件的测量高度为父控件的高度,在计算剩余空间的时候得出一个负值,加上自身的测量高度的时候反而更小;
  • 父控件是wrap_content,子控件拥有weight:
    • 子控件的高度将会强行置为其wrap_content给的值并以wrap_content模式进行测量
  • 父控件是wrap_content,子控件没有weight:
    • 子控件的高度跟其他的viewgroup一致

自此,LinearLayout在垂直方向的测量分析已经结束。

参考地址:
[1]. baselineAligned解析 http://www.bubuko.com/infodetail-612730.html
[2]. measureWithLargestChild使用解析 https://blog.csdn.net/a87b01c14/article/details/49420449
[3]. LinearLayout垂直测量分析 https://www.jianshu.com/p/aea27bac7c8e
[4]. view和LinearLayout源码分析 https://www.jianshu.com/p/f9b9f05222a8