欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

UI绘制流程的三部曲----Measure

程序员文章站 2024-03-24 13:16:52
...

之前的三篇文章的最后说到了三个步骤那就是Measure,Layout,Draw。接下来的三篇文章主要从源码的角度来分析这三个过程的具体实现。

本章主要梳理Measure过程

一、Measure流程分析

上篇文章说到了performTraversals这个方法调用了performMeasure

UI绘制流程的三部曲----Measure

performMeasure这个方法接收了两个参数,而这两个参数是通过getRootMeasureSpec方法得到来的,查看getRootMeasureSpec这个方法源代码:

UI绘制流程的三部曲----Measure

这个方法的注释是:根据窗口的布局参数,计算出其中根布局的测量规格。什么是测量规格呢?我们稍后再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源码:

UI绘制流程的三部曲----Measure

mView就是已经知道测量规格的DecorView,它调用的measure方法,我们查看measure源码:

UI绘制流程的三部曲----Measure

可以看到这个方法里调用了onMeasure方法,而这个方法会在具体的布局或着View中调用,我们以FrameLayout为类,查看FrameLayout中的onMeasure方法:

UI绘制流程的三部曲----Measure

这里的代码明显是遍历当前布局子View的方法,并且保存其中子View中的最大宽高值。查看measureChildWithMargins方法:

UI绘制流程的三部曲----Measure

可以知道这个方法的作用其实就是让子View进行measure的系列操作,上面已经介绍了measure方法调用流程,这里就不赘述了,我们回到FrameLayout方法中的onMeasure方法,有下面一段代码:

UI绘制流程的三部曲----Measure

我们层层进入查看可以知道,这个方法其实就是保存了View中的mMeasureWidth及mMeasureHeight。我们再次往下看FrameLayout中的onMeasure方法:

UI绘制流程的三部曲----Measure

这个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都已测量完毕。

欢迎留言,欢迎纠错,共同进步!