• View的绘制流程

View的绘制是从ViewRootImpl类的performTraversals方法开始经过measure、layout、draw三个过程将View绘制出来的,measure方法用来测量ViewGroup/View的宽高,layout用来确定View的最终宽高和在容器内的位置,draw绘制View到屏幕上。

performTraversals方法会依次调用perfomrMeasure,performLayout和performDraw,这三个方法依次调用底层View的绘制流程,也就是调用onMeasure、onLayout和onDraw,三个方法通过递归方式完成整个布局的绘制。

  • MeasureSpec
    MeasureSpec可以理解为“测量规则”或“测量标准”。MeasureSpec用一个32位的int值来表示,高2位代表SpecMode(测量模式),低30位代表SpecSize(规格大小)。代码定义这样的:
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

private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;

/**
* Measure specification mode: The parent has not imposed any constraint
* on the child. It can be whatever size it wants.
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;

/**
* Measure specification mode: The parent has determined an exact size
* for the child. The child is going to be given those bounds regardless
* of how big it wants to be.
*/
public static final int EXACTLY = 1 << MODE_SHIFT;

/**
* Measure specification mode: The child can be as large as it wants up
* to the specified size.
*/
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);
}
}

@MeasureSpecMode
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}

public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}

代码里可以看到MeasureSpec将SpecMode和SpecSize打包成一个int值,这样可以避免过多的内存分配,getMode和getSize方法利用MODE_MASK与操作来得到需要的mode和size值。
SpecMode有三种模式:

  1. UNSPECIFIED:[ʌnˈspesɪfaɪd]未说明的,没有明确的。父容器对于View没有任何限制,想要多大就给多大。
  2. EXACTLY:[ɪɡˈzæktli] 明确的。它对应LayoutParams中的match_parent或指定大小值的两种场景。父容器给出了一个确定的范围,如果view设置了具体值这个具体没有超出给定的范围那么就是这个具体值,如果超出了那view的大小就是父容器给定的限制值或者直接就指定限制值也就是match_parent的场景
  3. AT_MOST:view想要多大就是多大,至到达到指定的值。对应LayoutParams中的wrap_content。

这三个值的设置是站在父容器的角度衡量view的,UNSPECIFIED是父容器没有对view做限制,EXACTLY是父容器给出了一个明确限制值(最大边界)来约束view,AT_MOST则是按照view的想要的来展示,但也有指定值来限定。

对于一个普通的View,它的MeasureSpec是由父容器的MeasureSpec和自身设置的LayoutParam参数来决定的,比如View设置了固定的宽或者高那么它在宽或者高方向上的SpecMode就是EXCATLY,无论它的父容器设置什么测量模式

View的绘制流程和activity的生命周期是不同步的,所以在onresume中直接获得view宽高是错误的,可以痛殴VIewTreeObser监听绘制过程或者通过View.post方法投递一个消息队列到尾部,等待looper调用该runnable时view已经初始化

postscript:getMeasureWidth和getWidth的区别