0%

View绘制的measure过程

在Activity中,所有的View都是DecorView的子View,然后DecorView又是被ViewRootImpl所控制,当Activity显示的时候,ViewRootImpl的performTranversals方法开始运行,这个方法很长,不过核心的三个流程就是依次调用performMeasure、performLayout、performDraw三个方法,从而完成DecorView的绘制。

其中,ViewRootImpl#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方法,参数是两个经过设置的MeasureSpec,接下来我们分析一下MeasureSpec是如何设置的。

MeasureSpec

测量规格(MeasureSpec) = 测量模式(mode) + 测量大小(size)
这个MeasureSpec不是实际测绘值,而是父View传递给子View的布局要求,MeasureSpec涵盖了对子View大小和模式的要求。其中,三种模式要求分别是:

UNSPECIFIED:对子View无任何要求,想要测绘多少由子View决定。
EXACTLY:父View已确定了自己确切的大小。子View将在这个边界内测绘自己的宽高。
AT_MOST:父View对子View没有要求,子View可以达到它想要的大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}

MeasureSpec是个32位的int值,其中高两位代表的是模式,低30位代表父View的尺寸。

measure过程

计算完MeasureSpec,DecorView就该执行measure方法了。
measure方法是final的,所以不能重写,不过measure方法最主要的作用就是调用了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
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

/**
* 源码分析:measure()
* 定义:Measure过程的入口;属于View.java类 & final类型,即子类不能重写此方法
* 作用:基本测量逻辑的判断
**/

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {

// 参数说明:View的宽 / 高测量规格

...

int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
mMeasureCache.indexOfKey(key);

if (cacheIndex < 0 || sIgnoreMeasureCache) {

onMeasure(widthMeasureSpec, heightMeasureSpec);
// 计算视图大小 ->>分析1

} else {
...

}

/**
* 分析1:onMeasure()
* 作用:a. 根据View宽/高的测量规格计算View的宽/高值:getDefaultSize()
* b. 存储测量后的View宽 / 高:setMeasuredDimension()
**/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 参数说明:View的宽 / 高测量规格

setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
// setMeasuredDimension() :获得View宽/高的测量值 ->>分析2
// 传入的参数通过getDefaultSize()获得 ->>分析3
}

/**
* 分析2:setMeasuredDimension()
* 作用:存储测量后的View宽 / 高
* 注:该方法即为我们重写onMeasure()所要实现的最终目的
**/
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {

//参数说明:测量后子View的宽 / 高值

// 将测量后子View的宽 / 高值进行传递
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;

mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
// 由于setMeasuredDimension()的参数是从getDefaultSize()获得的
// 下面我们继续看getDefaultSize()的介绍

/**
* 分析3:getDefaultSize()
* 作用:根据View宽/高的测量规格计算View的宽/高值
**/
public static int getDefaultSize(int size, int measureSpec) {

// 参数说明:
// size:提供的默认大小
// measureSpec:宽/高的测量规格(含模式 & 测量大小)

// 设置默认大小
int result = size;

// 获取宽/高测量规格的模式 & 测量大小
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);

switch (specMode) {
// 模式为UNSPECIFIED时,使用提供的默认大小 = 参数Size
case MeasureSpec.UNSPECIFIED:
result = size;
break;

// 模式为AT_MOST,EXACTLY时,使用View测量后的宽/高值 = measureSpec中的Size
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}

// 返回View的宽/高值
return result;
}

ViewGroup的measure过程

  1. 遍历 测量所有子View的尺寸
  2. 合并将所有子View的尺寸进行,最终得到ViewGroup父视图的测量值。即自上而下、一层层地传递下去,直到完成整个View树的measure()过程。


整个流程

1.即 单一View measure过程的onMeasure()具有统一实现,而ViewGroup则没有
2.注:其实,在单一View measure过程中,getDefaultSize()只是简单的测量了宽高值,在实际使用时有时需更精细的测量。所以有时候也需重写onMeasure()

在自定义ViewGroup中,关键在于:根据需求复写onMeasure()从而实现你的子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
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
/**
* 根据自身的测量逻辑复写onMeasure(),分为3步
* 1. 遍历所有子View & 测量:measureChildren()
* 2. 合并所有子View的尺寸大小,最终得到ViewGroup父视图的测量值(自身实现)
* 3. 存储测量后View宽/高的值:调用setMeasuredDimension()
**/

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

// 定义存放测量后的View宽/高的变量
int widthMeasure ;
int heightMeasure ;

// 1. 遍历所有子View & 测量(measureChildren())
// ->> 分析1
measureChildren(widthMeasureSpec, heightMeasureSpec);

// 2. 合并所有子View的尺寸大小,最终得到ViewGroup父视图的测量值
void measureCarson{
... // 自身实现
}

// 3. 存储测量后View宽/高的值:调用setMeasuredDimension()
// 类似单一View的过程,此处不作过多描述
setMeasuredDimension(widthMeasure, heightMeasure);
}
// 从上可看出:
// 复写onMeasure()有三步,其中2步直接调用系统方法
// 需自身实现的功能实际仅为步骤2:合并所有子View的尺寸大小

/**
* 分析1:measureChildren()
* 作用:遍历子View & 调用measureChild()进行下一步测量
**/

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
// 参数说明:父视图的测量规格(MeasureSpec)

final int size = mChildrenCount;
final View[] children = mChildren;

// 遍历所有子view
for (int i = 0; i < size; ++i) {
final View child = children[i];
// 调用measureChild()进行下一步的测量 ->>分析1
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}

/**
* 分析2:measureChild()
* 作用:a. 计算单个子View的MeasureSpec
* b. 测量每个子View最后的宽 / 高:调用子View的measure()
**/
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {

// 1. 获取子视图的布局参数
final LayoutParams lp = child.getLayoutParams();

// 2. 根据父视图的MeasureSpec & 布局参数LayoutParams,计算单个子View的MeasureSpec
// getChildMeasureSpec() 请看上面第2节储备知识处
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,// 获取 ChildView 的 widthMeasureSpec
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,// 获取 ChildView 的 heightMeasureSpec
mPaddingTop + mPaddingBottom, lp.height);

// 3. 将计算好的子View的MeasureSpec值传入measure(),进行最后的测量
// 下面的流程即类似单一View的过程,此处不作过多描述
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
// 回到调用原处

至此,ViewGroup的measure过程分析完毕。

赞赏是最好的支持