UI绘制流程的三部曲----Measure
之前的三篇文章的最后说到了三个步骤那就是Measure,Layout,Draw。接下来的三篇文章主要从源码的角度来分析这三个过程的具体实现。
-
Measure流程分析
-
Layout流程分析
-
Draw流程分析
本章主要梳理Measure过程
一、Measure流程分析
上篇文章说到了performTraversals这个方法调用了performMeasure
performMeasure这个方法接收了两个参数,而这两个参数是通过getRootMeasureSpec方法得到来的,查看getRootMeasureSpec这个方法源代码:
这个方法的注释是:根据窗口的布局参数,计算出其中根布局的测量规格。什么是测量规格呢?我们稍后再MeasureSpec类中寻找答案。我们来分析一下这个方法作用,传入的两个参数是窗口的两个参数,而得到的是他子View的测量规格,而窗口的子View其实就是DecorView。在这里我们得到了DecorView的测量规格,也就是根布局的测量规格。我们来查看MeasureSpec的源代码,我这里借用了另一篇博客整理好的代码:
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/**
* UNSPECIFIED 模式:
* 父View不对子View有任何限制,子View需要多大就多大
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* EXACTYLY 模式:
* 父View已经测量出子Viwe所需要的精确大小,这时候View的最终大小
* 就是SpecSize所指定的值。对应于match_parent和精确数值这两种模式
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* AT_MOST 模式:
* 子View的最终大小是父View指定的SpecSize值,并且子View的大小不能大于这个值,
* 即对应wrap_content这种模式
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
//将size和mode打包成一个32位的int型数值
//高2位表示SpecMode,测量模式,低30位表示SpecSize,某种测量模式下的规格大小
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
//将32位的MeasureSpec解包,返回SpecMode,测量模式
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
//将32位的MeasureSpec解包,返回SpecSize,某种测量模式下的规格大小
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
//...
}
测量模式
EXACTLY :父容器已经测量出所需要的精确大小,这也是childview的最终大小
------match_parent,精确值是爸爸的
ATMOST : child view最终的大小不能超过父容器的给的
------wrap_content 精确值不超过爸爸
UNSPECIFIED: 不确定,源码内部使用
-------一般在ScrollView,ListView
不了解二进制运算的小伙伴估计看到这里有点头疼,其实不难理解的,自己动手计算一下,很容易就入门了,我就不在这里赘述了。
简单说一下三个方法:
makeMeasureSpec------返回值是一个32位的二进制数,这个数字封装了size+mode,前2位是mode,后30位是size。为什么使用这种方式来封装呢,主要是为了内存考虑,是一种优化的方式。上面所说的规格就是这个方法的返回值,其实就是size+mode的封装体
getMode------解析出View的mode
getSize------解析出View的size
我们回到performTraversals方法中,查看performMeasure源码:
mView就是已经知道测量规格的DecorView,它调用的measure方法,我们查看measure源码:
可以看到这个方法里调用了onMeasure方法,而这个方法会在具体的布局或着View中调用,我们以FrameLayout为类,查看FrameLayout中的onMeasure方法:
这里的代码明显是遍历当前布局子View的方法,并且保存其中子View中的最大宽高值。查看measureChildWithMargins方法:
可以知道这个方法的作用其实就是让子View进行measure的系列操作,上面已经介绍了measure方法调用流程,这里就不赘述了,我们回到FrameLayout方法中的onMeasure方法,有下面一段代码:
我们层层进入查看可以知道,这个方法其实就是保存了View中的mMeasureWidth及mMeasureHeight。我们再次往下看FrameLayout中的onMeasure方法:
这个if中的方法有点长,我没有粘贴全,我这里简单的总结一下这个方法的作用:count是mMatchParentChildren及数量,这个集合的元素就是FrameLayout的子View,什么时候向这个集合中添加元素呢,查看源代码可以知道(这里比较简单,我就不贴了),就是在Fragment宽或高的mode如果是WRAP_CONTENT的时候并且子View是的宽或高为MATCH_PARENT时,就会想其中添加子View。根据这个集合的性质,我们可以分析到一个结论:如果Fragment宽或高的mode是WRAP_CONTENT、子View是的宽或高为MATCH_PARENT并且存储的数量大于1时,子View会进行重新的measure。为什么会有count大于1这个条件呢,我这里举个例子:如果FrameLayout有两个子View,在Fragment的宽和高都是WRAP_CONTENT,子View的宽和高都是MATCH_PARENT,所以第一个View一定的宽高一定是自己View本来的宽高,就不受FrameLayout的影响。假设距上100,距左100,这时候FrameLayout的真实宽高就是100+view的宽或高,而第二个View则不是自己View本来的宽高了,而变成了FrameLayout的宽高了,主要就是这个意思。
到这里,measure的流程就分析完了,布局中所有的View都已测量完毕。
欢迎留言,欢迎纠错,共同进步!