Android学习笔记---深入理解View#03

上一篇的结尾中,我们发现了View的绘制发生在ViewRootImplperformTraversals()中.而且在其中先后调用了performMeasure(),performLayout(),performDraw().
如此一来,我们又有了新的猎物了.就像美食一样,好吃的东西一定要仔细地品尝.在上主菜之前,我们先来点开胃菜.我们先来了解一下Android是怎样绘制View的.(官方的文档How Android Draws Views)

开胃菜(关于View绘制时需要知道的常识)

Activity接受焦点时,就会被要求绘制其布局.虽然Android Framework会处理这个过程,但Activity必须提供整个布局层级的根节点,因为需要知道从哪开始绘制.

Activity的整个布局被转换成了一棵树,绘制整个布局就相当于了遍历整颗树并把每个节点的View绘制出来.相应地,ViewGroup负责要求它的每一个child进行绘制,而View则负责绘制自己.由于树的遍历是有序的,所以父View绘制之前会先绘制其子View,而兄弟节点会按照在树中出现的顺序进行绘制.
绘制布局需要进行两个传递过程(pass process):分别是测量时的传递(measure pass)布局时的传递(layout pass).这里所说的传递指的是在view tree的各个节点之间的传递.

  1. measure passmeasure(int,int)中实现,而且它是一个从上到下的传递.在view tree中每个View节点都将它的尺寸规格向下传递给它的孩子,在整个传递过程结束时,每个节点都应该拥有了自己的测量值(尺寸大小).
  2. layout passlayout(int,int,int,int)中发生,它同样也是一个从上到下的传递.在传递过程中每个parent都需要根据在measure pass时得到的测量值在布局中放置它的所有children.

下面贴上一张普通的view tree的图.

measure()函数中,官方定下了一些规则,在函数执行完毕返回前必须要满足下面的条件:

  1. View(以及其后代节点)的getMeasuredWidth()(即mMeasuredWidth的值)getMeasuredHeight()(即mMeasuredHeight的值)的值必须已经设置.从函数名已经知道函数获取的是已经测量的宽高值,measure()函数结束就表明测量结束了,这一条规则理所当然.
  2. View测量后的宽高必须符合其父View所规定的大小.这一条规则可以保证当measure pass结束时,所有的parents能接受其所有children的测量值.这也很好理解,子View的大小总不能比其父布局还大吧,否则就没有意义了.

一个为parent的View可能会不止一次地对其children调用measure().因为如果parent使用未指定的尺寸测量它的每一个child得到各个child想要的大小,但如果所有children的(未加限制的)测量值的总和太大或太小,那就需要parent再次调用measure()重新测量,但这次的测量设置了相应的规则.(举个比喻,就像孩子们在分配糖果时,大家都对所分配的糖果不满意时,父母就会干涉并重新分配)

我要吃神户牛柳(深入measure过程)

吃过开胃菜后,再来品尝我们的主菜就会更加的美味.美味的食物通常都有独特的吃法,比如使用特定的餐具.我们的第一道菜(measure)就是神户牛柳,我们需要准备刀叉来用餐.那先准备一下我们的餐具吧.(与measure过程密切相关的两个类).

刀 (ViewGroup.LayoutParams)

先来说明一下ViewGroup.LayoutParams是干什么用的.View通过ViewGroup.LayoutParams来告诉它的parent它在布局中想被放在什么位置和想占多大.而基本的ViewGroup.LayoutParams只能表达View想占多宽和多高,可以通过下面的其中一种方式表达:

  • 一个确切的数值大小
  • MATCH_PARENT,表达View想要和它的parent一样大(去掉View的内边距)
  • WRAP_CONTENT,表达View只想要能将它的内容包裹的大小(加上View的内边距)

ViewGroup.LayoutParams只能表达View大小,但ViewGroup的子类的LayoutParams能表达View的位置.

叉 (MeasureSpec)

看过我的自定义View#02文章的同学可能会对MeasureSpec有所了解.MeasureSpecparent用来限制child的大小,在measure()的过程中,它作为参数,从view tree的根节点往下传递到它的子节点和其后代.它有下列3种模式:

  • UNSPECIFIED, 这种模式表明parent对它的child的大小没有限制,child可以告诉parent它自己所希望的尺寸.
  • EXACTLY, 这种模式表明parentchild设置了一个确切的值,child必须使用这个值,并且需要保证child的后代节点都要符合这个值的设置
  • AT_MOST, 这种模式表明parentchild设置了一个最大值,child可以是它想要的任何值,但child以及它的后代节点的尺寸大小都必须保证在这个最大值内.

既然MeasureSpec有相应的模式来限制View的尺寸,那用什么来表示限制尺寸的大小呢.MeasureSpec采用了一个32位的int值来代表模式和大小,高2位表示模式,低30位表示大小.

有了我们的餐具后,我们可以终于可以开动了.我们先从ViewRootImpl.performTraversals()中调用performMeasure()的地方开始,下面是该部分的代码:

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
.......
if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
// 标注 1
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

if (DEBUG_LAYOUT) Log.v(TAG, "Ooops, something changed! mWidth="
+ mWidth + " measuredWidth=" + host.getMeasuredWidth()
+ " mHeight=" + mHeight
+ " measuredHeight=" + host.getMeasuredHeight()
+ " coveredInsetsChanged=" + contentInsetsChanged);

// Ask host how big it wants to be
// 标注 2
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

// Implementation of weights from WindowManager.LayoutParams
// We just grow the dimensions as needed and re-measure if
// needs be
// 标注 3
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;

if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}
// 标注 4
if (measureAgain) {
if (DEBUG_LAYOUT) Log.v(TAG,
"And hey let's measure once more: width=" + width
+ " height=" + height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}

layoutRequested = true;
}
}
.......

上面的代码我做了4个标注,我们一个一个来看,先看标注1的代码.

1
2
3
// 标注 1
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

其中这里的mHeightmWidth分别是窗口(Activity)的宽高,lp为窗口(Window)的布局参数.childWidthMeasureSpecchildHeightMeasureSpec这两个变量其实从标注2的代码就很容易的看出它们是作为参数传进performMeasure(int,int)的.那我们就看看它们代表的是什么意思.

由于它们是从getRootMeasureSpec(int,int)获取的,从函数名可以看出这个函数是用来获取Root节点的MeasureSpec的(就是根节点在测量时给它的孩子节点所定下的尺寸大小的限制).但我们还是要看看这个函数的代码:

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
/**
* Figures out the measure spec for the root view in a window based on it's
* layout params.
*
* @param windowSize
* The available width or height of the window
*
* @param rootDimension
* The layout params for one dimension (width or height) of the
* window.
*
* @return The measure spec to use to measure the root view.
*/

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.
// 如果布局参数要求MATCH_PARENT,那么就设置为窗口的大小,模式为EXACTLY,因为窗口(Activity)的大小固定
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
//如果布局参数为WRAP_CONTENT,就设置为AT_MOST模式,最大值为窗口大小
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
// 如果布局的参数为一个确切的值,那我们就让root view为该值,模式为EXACTLY
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}

getRootMeasureSpec(int,int)的两个参数分别代表窗口的大小(windowSize)和窗口的布局参数的大小(rootDimension).官方的注释(我也做了相应的注释)已经写得很清楚了,这个函数通过window的布局参数来决定root viewMeasureSpec.
经过标注1的代码,我们获取到了root tree的根节点的MeasureSpec,这样就可以从树的根节点开始进行测量传递的过程了(在开胃菜中提到的measure pass).在对标注2这个最主要的代码部分进行分析前,我们先来分析后面的标注3标注4的代码.(好东西肯定要留到最后,反正我是这样想的)

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
// Implementation of weights from WindowManager.LayoutParams
// We just grow the dimensions as needed and re-measure if
// needs be
// 标注 3
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;

if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}
// 标注 4
if (measureAgain) {
if (DEBUG_LAYOUT) Log.v(TAG,
"And hey let's measure once more: width=" + width
+ " height=" + height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}

layoutRequested = true;

标注3首先获取到了测量后root view的宽高值,然后分别判断lp.horizontalWeightlp.verticalWeight的值是否大于0(即我们的布局参数在横向或纵向的weight被设置时,可以理解为在xml布局文件里设置了layout_weight属性).若设置了其中一个方向上的weight,那么标注4的代码都会执行,我们需要再调用一次performMeasure()进行测量(measure pass),但这次采用的是新的参数,把布局参数的weight考虑进去.

可能有同学会问,为什么第一次调用performMeasure()前不把weight考虑进去,测量完一次后才考虑这不让前面的工作都白费了吗?我也有相同的疑问,目前我还没找到一个准确的答案,但google这样写一定有它的道理,我在这里分享一下我的想法吧(不一定是正确的,如果错了希望大家能指正).

我是这样想的:第一次调用performMeasure()的时候并不知道weight是否设置了,因为我们通常设置layout_weight属性都是在子View中设置的,在子View测量完毕前,父布局并不知道它的所有子View的weight属性,而父布局的测量发生在子View测量结束后,所以我们可能需要进行两次的测量传递过程(measure pass).

好的,清楚了我们标注3,标注4的代码后,我们可以迎接我们的主角performMeasure()了,下面就是performMeasure()的代码.

1
2
3
4
5
6
7
8
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}

原来代码中调用的是mView.measure(int,int),即调用了root viewmeasure(),既然如此,我们来看measure()的代码:

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
/**
* <p>
* This is called to find out how big a view should be. The parent
* supplies constraint information in the width and height parameters.
* </p>
*
* <p>
* The actual measurement work of a view is performed in
* {@link #onMeasure(int, int)}, called by this method. Therefore, only
* {@link #onMeasure(int, int)} can and must be overridden by subclasses.
* </p>
*
*
* @param widthMeasureSpec Horizontal space requirements as imposed by the
* parent
* @param heightMeasureSpec Vertical space requirements as imposed by the
* parent
*
* @see #onMeasure(int, int)
*/

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int oWidth = insets.left + insets.right;
int oHeight = insets.top + insets.bottom;
widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
}

// Suppress sign extension for the low bytes
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);

if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {

// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

resolveRtlPropertiesIfNeeded();

int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}

// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("View with id " + getId() + ": "
+ getClass().getName() + "#onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}

mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}

mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;

mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}

注释中也说了,真正的测量工作是发生在onMeasure(int,int)函数中的,并且说明了View的子类可以并必须重写onMeasure()来测量我们的View.这里的必须并不代表我们在自定义VIew的时候一定要重写onMeasure(),因为onMeasure()已经在View中实现了,在不重写的情况下会调用默认的实现.

既然注释中给我们指明了方向,那我们就来看看这个onMeasure().

神户牛的精华(onMeasure)

onMeasure()可以说是整个measure pass的核心部分,就像是神户牛的精华一样.那现在我们就来感受一下这神户牛的精华所带来的美味.
由于在view tree上不可能每个节点都是View节点(这里是叶子节点的意思),就像在"开胃菜"中给大家展现的图一样,在view tree中也会有ViewGroup节点,像FrameLayout,LinearLayout,RelativeLayout...,这些ViewGroup节点都相应的实现了自己的onMeasure().那么这就说明了ViewGroup.onMeasure()View.onMeasure()并不一样.既然测量传递的过程(measure pass)是从根节点开始的,那我们也从ViewGrouponMeasure()开始.这里我们使用的是FrameLayout的代码(其他的ViewGroup大家可以自己试着去分析).

提示:下面的代码可以先跳过,因为在后面会再提到.

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
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 第1部分
int count = getChildCount();

final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();

int maxHeight = 0;
int maxWidth = 0;
int childState = 0;

for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}

// 第2部分
// Account for padding too
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

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

// Check against our foreground's minimum height and width
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}

setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));

//第3部分
count = mMatchParentChildren.size();
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

final int childWidthMeasureSpec;
if (lp.width == LayoutParams.MATCH_PARENT) {
final int width = Math.max(0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
width, MeasureSpec.EXACTLY);
} else {
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width);
}

final int childHeightMeasureSpec;
if (lp.height == LayoutParams.MATCH_PARENT) {
final int height = Math.max(0, getMeasuredHeight()
- getPaddingTopWithForeground() - getPaddingBottomWithForeground()
- lp.topMargin - lp.bottomMargin);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
height, MeasureSpec.EXACTLY);
} else {
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
lp.topMargin + lp.bottomMargin,
lp.height);
}

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

Part 1

虽然代码不算很长,但为了方便,我们还是将它分为3个部分来分析吧(上面代码注释中所划分的).先来第1部分的代码:

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
// 第1部分
int count = getChildCount();
// 编号1. 用于判断是否需要对布局参数为MATCH_PARENT的子View进行重新测量
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();

int maxHeight = 0;
int maxWidth = 0;
int childState = 0;

for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
// 编号2. 对每个子View进行测量
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// 编号3. 得到所有子View中最大的宽度(加上子View的外边距)
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
// 编号4. 得到所有子View中最大的高度(加上子View的外边距)
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
// 编号5. 得到子View的MeasureState
childState = combineMeasuredStates(childState, child.getMeasuredState());
// 编号6. 将布局参数为MATCH_PARENT的子View加入到`mMatchParentChildren`集合中
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}

为了后面的分析方便,我在上面的代码注释中都给相应的语句进行了编号,我们先来分析简单的.
编号1measureMatchParentChildren是用于判断FrameLayout是否需要对布局参数为MATCH_PARENT的子View进行第二次测量.这里我们等分析过编号2的代码后再对这个变量进行解析(这里可以先留个疑问).
编号3,编号4的代码就是为了得到FrameLayout所有孩子中测量后的最大宽高(加上边距),因为FrameLayout需要按照它的孩子中尺寸最大的宽高进行测量.
编号5的代码就是为了得到子View的MeasuredState,这个对我们来说是个新的概念.既然这样,我们就来看看child.getMeasuredState()这个在View类下的函数是个怎样的函数.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* Return only the state bits of {@link #getMeasuredWidthAndState()}
* and {@link #getMeasuredHeightAndState()}, combined into one integer.
* The width component is in the regular bits {@link #MEASURED_STATE_MASK}
* and the height component is at the shifted bits
* {@link #MEASURED_HEIGHT_STATE_SHIFT}>>{@link #MEASURED_STATE_MASK}.
*/

// 将宽高的状态位结合成在一个32位的int值并返回
// 宽度的状态位在常规的位置
// 高度的状态位在偏移后的位置
public final int getMeasuredState() {
return (mMeasuredWidth&MEASURED_STATE_MASK)
| ((mMeasuredHeight>>MEASURED_HEIGHT_STATE_SHIFT)
& (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
}
// 用于使高度的状态位偏移的位数
public static final int MEASURED_HEIGHT_STATE_SHIFT = 16;

我把用到的变量也贴在了上面的代码中.首先我们来了解一下什么是”宽高的状态位”.我们知道mMeasuredHeightmMeasuredWidth都是32位的int值,但这个值并不是一个表示宽高的实际大小的值,而是一个由宽高的状态和实际大小所组合的值.这里的高8位就表示状态(STATE),而低24位表示的是实际的尺寸大小(SIZE),这个信息可以从它们相应的掩码看出.

1
2
3
4
// 用于得出宽高的状态位的掩码
public static final int MEASURED_STATE_MASK = 0xff000000;
// 用于得出宽高的尺寸位的掩码
public static final int MEASURED_SIZE_MASK = 0x00ffffff;

这就解析了为什么我们的getMeasuredHeight()函数返回的是mMeasuredHeight & MEASURED_SIZE_MASK.而getMeasuredHeightAndState()返回的是mMeasuredHeight.相应的关于宽度的函数也是一个道理.

1
2
3
4
5
6
public final int getMeasuredHeight() {
return mMeasuredHeight & MEASURED_SIZE_MASK;
}
public final int getMeasuredHeightAndState() {
return mMeasuredHeight;
}

现在我们再来看getMeasuredState()是怎样将宽高的状态位组合在一个int值中的.首先mMeasuredWidth & MEASURED_STATE_MASK得到了宽度的状态位,保存在高8位.然后通过(mMeasuredHeight >> MEASURED_HEIGHT_STATE_SHIFT)(MEASURED_STATE_MASK >> MEASURED_HEIGHT_STATE_SHIFT)将高度和状态掩码都右移了16位,现在高度的状态位在第8到第15位上,而MEASURED_STATE_MASK变成了0x0000ff00,接着将两个移位后的数进行按位相与(&)得到了高度的状态位,保存在8-15位上.最后将处理后宽度和高度按位相或(|)得到一个保存了宽度和高度的状态位的int值.如下图.

1
2
3
4
5
6
7
8
9
10
11
/**
* Merge two states as returned by {@link #getMeasuredState()}.
* @param curState The current state as returned from a view or the result
* of combining multiple views.
* @param newState The new view state to combine.
* @return Returns a new integer reflecting the combination of the two
* states.
*/

public static int combineMeasuredStates(int curState, int newState) {
return curState | newState;
}

回到编号5的代码,就是为了将所有子View的state都结合在一起,这个有什么作用现在也不好讲.先继续看吧.

编号2的代码是将FrameLayout中所有visibility属性不为GONE的子View都进行测量(即在布局中占据位置的View),使用的是measureChildWithMargins().下面我们来看这个函数的代码.

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
/**
* Ask one of the children of this view to measure itself, taking into
* account both the MeasureSpec requirements for this view and its padding
* and margins. The child must have MarginLayoutParams The heavy lifting is
* done in getChildMeasureSpec.
*
* @param child The child to measure (需要测量的子View)
* @param parentWidthMeasureSpec The width requirements for this view
* (parent对子View宽度的要求(MeasureSpec))
* @param widthUsed Extra space that has been used up by the parent
* horizontally (possibly by other children of the parent)
* (被parent或其他兄弟节点在布局的水平方向上使用了的尺寸大小)
* @param parentHeightMeasureSpec The height requirements for this view
* (parent对子View高度的要求(MeasureSpec))
* @param heightUsed Extra space that has been used up by the parent
* vertically (possibly by other children of the parent)
* (被parent或其他兄弟节点在布局的垂直方向上使用了的尺寸大小)
*/

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);
}

像代码中的注释所写的一样,这个函数就是用来告诉child需要进行测量.测量过程中需要遵循parentMeasureSpec,还需要考虑将paddingmargin的值.通过了getChildMeasureSpec()得到了子View的MeasureSpec后就可以调用子View的measure()进行测量了.所以我们要来看看getChildMeasureSpec(int,int,int)这个函数,先来看看函数原型的文档,因为我们要先搞清楚各个参数所代表的意义.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* Does the hard part of measureChildren: figuring out the MeasureSpec to
* pass to a particular child. This method figures out the right MeasureSpec
* for one dimension (height or width) of one child view.
*
* The goal is to combine information from our MeasureSpec with the
* LayoutParams of the child to get the best possible results. For example,
* if the this view knows its size (because its MeasureSpec has a mode of
* EXACTLY), and the child has indicated in its LayoutParams that it wants
* to be the same size as the parent, the parent should ask the child to
* layout given an exact size.
*
* @param spec The requirements for this view
* (对View的尺寸限制MeasureSpec)
* @param padding The padding of this view for the current dimension and
* margins, if applicable
* (可以理解为父布局的padding值+View的margin值,即父布局中未使用的尺寸大小)
* @param childDimension How big the child wants to be in the current
* dimension
* (View希望在布局中的大小,即子View布局参数的宽高)
* @return a MeasureSpec integer for the child
*
*/

public static int getChildMeasureSpec(int spec, int padding, int childDimension)

这个函数是为了获得当前View的MeasureSpec以便于进行测量和传递给子View的.函数中主要是根据父布局的MeasureSpec来创建View自己的MeasureView.下面是相应的代码.

代码有点长,希望能完整地看一遍.但如果不想看也不要紧,就粗略地扫一眼吧.因为后面有图片进行总结,正所谓一图胜千言啊!

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
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
// 分别获取父布局`MeasureSpec`中的模式和尺寸
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
// 获取父布局实际提供给View的尺寸大小(去除边距)
// 即父布局最大的可用的大小
// 与0相比取最大值,以免尺寸大小为负值
int size = Math.max(0, specSize - padding);

// 当前View最终的尺寸大小和模式
int resultSize = 0;
int resultMode = 0;

// 根据父布局的模式来决定View的模式和尺寸
switch (specMode) {
// Parent has imposed an exact size on us
// 表示父布局的大小为确切的值
case MeasureSpec.EXACTLY:
// 由于`MATCH_PARENT`=-1,`WRAP_CONTENT`=-2,
// 所以childDimension >= 0 表示View的宽高布局参数为具体的值
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 表示View的布局参数为`MATCH_PARENT`,即View希望大小是父布局的最大的可以大小
// 模式与父布局一样为EXACTLY
// 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.
// 表示View的布局参数为`WRAP_CONTENT`,那么说明View的大小不明确,需要由它的内容决定
// 所以测量值的尺寸为父布局的最大的可以大小,模式为AT_MOST
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
// 表明View的布局大小为确切的值
// 所以View的测量大小为布局参数的值,模式为EXACTLY
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.
// 表明View的布局参数为MATCH_PARENT
// 所以View的测量大小为父布局的可用大小,模式与父布局一样为AT_MOST
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.
// View的布局参数为WRAP_CONTENT,即View的测量尺寸大小不确定,由其内容决定
// 所以View的测量大小为父布局最大的可以大小,模式为AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;

// Parent asked to see how big we want to be
// 表示父布局对View的大小没有限制,通常用在ListView等可滚动的控件中
// 这种情况下父布局会满足View的所有要求

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;
}
// 根据最终的View的模式和尺寸生成View的MeasureSpec
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

虽然上面的代码不短,但代码逻辑并不复杂,而且我已经在代码上做了比较明白的注释了,如果还是不理解,那就请看图.

函数的功能就是为了给View生成一个MeasureSpec类型的int,而这个值是由模式和大小合成的,而且它们的值由父布局MeasureSpec的模式和View的布局大小共同决定.上图就是一个决定View的ModeSize的过程.
这里有一点需要注意的,就是当父布局的MeasureSpec的模式为UNSPECIFIED时,若View的布局大小不为一个具体的值那么resultSize的大小就为0.这里决定resultSize的值是下面的一条语句.

1
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;

而这里的View.sUseZeroUnspecifiedMeasureSpec可以在View中找到相应的信息.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* Always return a size of 0 for MeasureSpec values with a mode of UNSPECIFIED
*/

static boolean sUseZeroUnspecifiedMeasureSpec = false;
public View(Context context) {
.....
// In M and newer, our widgets can pass a "hint" value in the size
// for UNSPECIFIED MeasureSpecs. This lets child views of scrolling containers
// know what the expected parent size is going to be, so e.g. list items can size
// themselves at 1/3 the size of their container. It breaks older apps though,
// specifically apps that use some popular open source libraries.
sUseZeroUnspecifiedMeasureSpec = targetSdkVersion < M;
......
}

就是说若当前的Android版本小于M的话那sUseZeroUnspecifiedMeasureSpec的值就为true.所以在旧版本的Android中,resultSize的值都为0.
现在我们回到编号1的地方就可能对那句代码有所理解了.

1
2
3
4
// 编号1. 用于判断是否需要对布局参数为MATCH_PARENT的子View进行重新测量
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;

我们现在对应着上面的View的MeasureSpec生成图来分析代码.将流程反过来看,即从有编号的框开始看,我们可以看到在(1),(2),(4),(7)4种情况下resultMode的值为EXACTLY.而它们的条件有3种是childDimension >= 0即FrameLayout的布局参数layout_widthlayout_height为实际的具体值.另一种情况就是FramLayout的布局参数为MATCH_PARENT且它的父布局为EXACTLY. 综合的来讲可以这么理解吧就是当FrameLayout的布局宽高不同时设置为具体的值,或不同时为MATCH_PARENT那么measureMatchParentChildren这个值就为true.
再简单的讲就是如果FrameLayout的宽高只要有一个设置为WRAP_CONTENT,那么该值就为true.那么就需要对布局参数为MATCH_PARENT的子View进行重新测量.因为WRAP_CONTENT的情况下父布局的测量值受子View的影响.

能看到这里的同学真是不简单啊,没想到第1部分讲了这么久,(有的同学就可能会说:这分”神户牛柳”的量也太多了吧,吃得有点撑啊!)这里篇幅确实有点长,但如果仔细看下来的话还是能学到不少的东西.大家可以先休息一下,待会再来继续阅读.我也在下面做了分割线帮大家标记位置.


Part 2

我们来继续我们onMeasure()第2部分代码的分析吧.相信我,当你看完第1部分的分析后,后面就会很有感觉.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 第2部分
// 前面我们的maxWidth和maxHeight只是计算了子View的外边距
// 但没有计算FrameLayout的内边距,所以在这里加上
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

// Check against our minimum height and width
// 保证我们的`maxWidth`和`maxHeight`不会太小(至少要等于最小的建议值)
// 这里的最少建议值与背景有关
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

// Check against our foreground's minimum height and width
// 上面保证了背景的宽高值,下面保证前景对的宽高值
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}

// 标注
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));

第2部分的代码是先是确定了maxWidthmaxHeight的值,给大家看一眼getSuggestedMinimumHeight()的代码吧,因为后面这个函数还会用到,我相信大家很容易就能理解.

1
2
3
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

我们主要还是看标注的那句代码,setMeasuredDimension()就是将测量好的宽高值存储下来供后面的布局过程使用.来看看代码吧.

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
/**
* <p>This method must be called by {@link #onMeasure(int, int)} to store the
* measured width and measured height. Failing to do so will trigger an
* exception at measurement time.</p>
*
* @param measuredWidth The measured width of this view. May be a complex
* bit mask as defined by {@link #MEASURED_SIZE_MASK} and
* {@link #MEASURED_STATE_TOO_SMALL}.
* @param measuredHeight The measured height of this view. May be a complex
* bit mask as defined by {@link #MEASURED_SIZE_MASK} and
* {@link #MEASURED_STATE_TOO_SMALL}.
*/

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);
}
/**
* Sets the measured dimension without extra processing for things like optical bounds.
* Useful for reapplying consistent values that have already been cooked with adjustments
* for optical bounds, etc. such as those from the measurement cache.
*
* @param measuredWidth The measured width of this view. May be a complex
* bit mask as defined by {@link #MEASURED_SIZE_MASK} and
* {@link #MEASURED_STATE_TOO_SMALL}.
* @param measuredHeight The measured height of this view. May be a complex
* bit mask as defined by {@link #MEASURED_SIZE_MASK} and
* {@link #MEASURED_STATE_TOO_SMALL}.
*/

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;

mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

上面就是相关的代码,也比较简单,setMeasuredDimension()中最后调用了setMeasuredDimensionRaw()来设置mMeasuredWidthmMeasuredHeight的值.其中关于Optical Bounds有兴趣的同学可以到Internet上搜索一下,或看看下图.这里我们不作讨论,可以跳过.

了解了setMeasuredDimension()后,那我们再来看看调用处给它传进的两个参数resolveSizeAndState(maxWidth, widthMeasureSpec, childState)resolveSizeAndState(maxWidth, widthMeasureSpec, childState).既然调用了resolveSizeAndState(),那就看看它的代码吧.

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
/**
* Utility to reconcile a desired size and state, with constraints imposed
* by a MeasureSpec. Will take the desired size, unless a different size
* is imposed by the constraints. The returned value is a compound integer,
* with the resolved size in the {@link #MEASURED_SIZE_MASK} bits and
* optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set if the
* resulting size is smaller than the size the view wants to be.
*
* @param size How big the view wants to be.
* @param measureSpec Constraints imposed by the parent.
* @param childMeasuredState Size information bit mask for the view's
* children.
* @return Size information bit mask as defined by
* {@link #MEASURED_SIZE_MASK} and
* {@link #MEASURED_STATE_TOO_SMALL}.
*/

public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
final int specMode = MeasureSpec.getMode(measureSpec);
final int specSize = MeasureSpec.getSize(measureSpec);
final int result;
switch (specMode) {
case MeasureSpec.AT_MOST:
if (specSize < size) {
// 防止View超出了限制的大小所做的处理
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
result = size;
}
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
case MeasureSpec.UNSPECIFIED:
default:
result = size;
}
// 将尺寸大小值和状态组合到一起
return result | (childMeasuredState & MEASURED_STATE_MASK);
}

第1部分我们已经读过了不少的类似的代码了,在这里我就不啰嗦了.主要还是讲讲resolveSizeAndState(maxHeight, heightMeasureSpec,childState << MEASURED_HEIGHT_STATE_SHIFT)这句代码吧,为什么这里需要进行左移?如果前面有认真看的话就很容易理解,因为我们的childState是存有宽高的状态的组合值,我们的高度的状态值存在第8-15位,所以这里需要将它左移16位(将状态位放置在常规的位置).

Part 3

来到第3部分了,这部分比较简单,我们先来看看代码.

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
//第3部分
count = mMatchParentChildren.size();
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

final int childWidthMeasureSpec;
if (lp.width == LayoutParams.MATCH_PARENT) {
// 宽度为总宽度减去父布局的Padding和View的Margin
final int width = Math.max(0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
width, MeasureSpec.EXACTLY);
} else {
// 第1部分已经讨论过
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width);
}

final int childHeightMeasureSpec;
if (lp.height == LayoutParams.MATCH_PARENT) {
final int height = Math.max(0, getMeasuredHeight()
- getPaddingTopWithForeground() - getPaddingBottomWithForeground()
- lp.topMargin - lp.bottomMargin);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
height, MeasureSpec.EXACTLY);
} else {
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
lp.topMargin + lp.bottomMargin,
lp.height);
}

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}

这里的代码就是当FrameLayout有多于1个的子View的布局宽高为MATCH_PARENT时(并且满足第1部分中的measureMatchParentChildrentrue),即当FrameLayout的宽高设置存在WRAP_CONTENT时,对子View进行重新的测量.

View的onMeasure()

到这里我们FrameLayoutonMeasure()已经分析完毕了,既然我们已经分析过了ViewGrouponMeasure(),那ViewonMeasure()我觉得也免不了,废话不说赶紧上菜.

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

代码还是很简单的,就一句.虽然嵌套很多,但只有一个函数我们没有见过,就是getDefaultSize(),那就看看它是何方神圣.

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
/**
* Utility to return a default size. Uses the supplied size if the
* MeasureSpec imposed no constraints. Will get larger if allowed
* by the MeasureSpec.
*
* @param size Default size for this view
* @param measureSpec Constraints imposed by the parent
* @return The size this view should be.
*/

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;
}

这个函数是决定View的MeasureSpecSize的一种默认的方法.代码非常的简单,我就不再多说了.因为今天看这种代码看得真的不少,我相信很多同学都快要看吐了.
不过到这里我很开心,因为我们这次的任务完成了,measure的过程我们已经分析完了.

最后的甜点(总结)

牛柳吃完了,不知道大家能不能消化,所以最后给大家上个甜点吧.最后还是用图说话,来总结一下measure的整个流程.
measure