1. 简介
View
的绘制过程分为三部分:measure
、layout
、draw
。
measure
用来测量View的宽和高。
layout
用来计算View的位置。
draw
用来绘制View。
- 本章主要对
measure
过程进行详细的分析。 - 本文源码基于android 27。
2. measure的始点
measure
是从ViewRootImpl
的performTraversals()
方法开始的:
2.1 ViewRootImpl的performTraversals
private void performTraversals() {
//...
//获得view宽高的测量规格,mWidth和mHeight表示窗口的宽高,lp.widthhe和lp.height表示DecorView根布局宽和高
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//执行测量
//...
}
首先会获取view宽高的测量规格,测量规格在下一节会详细讲述,然后就是调用performMeasure()
了:
2.2 ViewRootImpl的performMeasure
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
//...
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
//...
}
performMeasure()
中就是调用View
的measure()
方法开始进行测量。
3.MeasureSpec
了解measure
的过程时,我们须先了解MeasureSpec
。MeasureSpec
,顾名思义,就是测量规格,其决定了一个View的宽和高。
3.1 MeasureSpec组成
MeasureSpec
代表一个32位的int值,前2位代表SpecMode
,后30位代表SpecSize
。其中:SpecMode
代表测量的模式,SpecSize
值在某种测量模式下的规格大小。来张图解说明:
3.2 SpecMode测量模式
测量模式分为三种UNSPECIFIED
、EXACTLY
、AT_MOST
,其具体说明如下表所示:
测量模式 | 说明 | 应用场景 |
---|---|---|
UNSPECIFIED (未指定) | 父容器没有对当前View有任何限制,当前View可以任意取尺寸 | 系统内部 |
EXACTLY(精确) | 父容器已经确定当前View的大小,无论View想要多大都会在这范围内 | match_parent 和 具体数值 |
AT_MOST(最多) | 当前View不能超过父容器规格大小,具体数值由view去决定 | wrap-content |
3.3 MeasureSpec源码分析
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;//模式移位数
private static final int MODE_MASK = 0x3 << MODE_SHIFT;//模式掩码
//UNSPECIFIED模式:父容器没有对当前View有任何限制,当前View可以任意取尺寸
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
//EXACTLY模式:父容器已经确定当前View的大小,无论View想要多大都会在这范围内
public static final int EXACTLY = 1 << MODE_SHIFT;
//AT_MOST模式:当前View不能超过父容器规格大小,具体数值由view去决定
public static final int AT_MOST = 2 << MODE_SHIFT;
//根据提供的size和mode得到一个测量规格
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
//sUseBrokenMakeMeasureSpec = targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR1;
//即targetSdkVersion<=17时,size与mode是直接相加的;>17则进行位运算
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);//位运算
}
}
//根据提供的size和mode得到一个测量规格
public static int makeSafeMeasureSpec(int size, int mode) {
if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
return 0;
}
return makeMeasureSpec(size, mode);
}
//获取测量模式
@MeasureSpecMode
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
//获取测量大小
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
}
可以看到,MeasureSpec
类还是挺简单的,MeasureSpec
类通过将mode
和size
打包成一个32位int值来减少了对象内存分配,并提供了打包和解包的方法。
3.4 确定MeasureSpec值
在measure
过程中,系统会将View
的LayoutParams
和父容器所施加的规则转换成对应的MeasureSpec
,然后在onMeasure()
方法中根据这个MeasureSpec
来确定View
的测量宽高。父容器所施加的规则对于DecorView
与普通View
是不同的,我们分开来看。
3.4.1 确定DecorView的MeasureSpec值
DecorView
,作为顶级的View
,我们平时setContentView()
所设置的布局可能只是DecorView
其中的一部分,如下图所示:
关于DecorView
,可以看看我的另一篇文章:从setContentView揭开DecorView。
3.4.1.1 ViewRootImpl的PerformTraveals
在ViewRootImpl
的PerformTraveals()
方法中会获得DecorView
的MeasureSpec
值: