PS:本文系转载文章,阅读原文可读性会更好,文章末尾有原文链接
ps:文章是基于 Android Api 31来分析源码的。
目录
1、View 的 measure 过程
1、1 View(它不是ViewGroup) 的 measure 过程
1、1 、1 原始 View 的 measure 过程
1、1、2 具体 View 的 measure 过程
1、2 ViewGroup 的 measure 过程
1、View 的 measure 过程
这里 View 的 measure 的过程就是 View 的测量过程嘛,也就是去测量 View 的宽高,它分两种情况去考虑,一种是具体的 View(比如说TextView) 或者说是原始的 View ,一种是 ViewGroup。
1、1 View(它不是ViewGroup) 的 measure 过程
这里没有子元素 View 的 measure 过程就分为两种情况了,一种是它是 ViewGroup 的 View,一种是她不是 ViewGroup 的 View(比如说 TextView);好,我们先分析它不是 ViewGroup 的 measure 过程。
1、1、1 原始 View 的 measure 过程
我们来看一下 View 的 measure 方法;
图片
看 View 的 measure 方法,它是 final 类型的对不对?那就是子类不可以重写该方法,要想重新计算 View 的宽高,那就得重写注释1中的 onMeasure 方法,我们往下看 View 的 onMeasure 方法;
图片
我们先看注释2中的 View 的 getDefaultSize(int size, int measureSpec) 方法;
图片
看注释4、5,当 specMode 为 View.MeasureSpec.AT_MOST 和 View.MeasureSpec.EXACTLY 的时候,得到的 specSize 就是 View 测量后的大小,这里仅仅只是得到 View 测量后的大小哦,并不是 View 最终的大小哦,View 的最终大小是在 layout 过程确定的,不过几乎所有情况下 View 的测量大小等于 View 的最终大小。
看注释3,View.MeasureSpec.UNSPECIFIED 的模式下用于系统内部测量,result 就为 size,而 size 是什么呢?我们回到注释2中的代码,看到没有 size 是 getSuggestedMinimumWidth 方法或者 getSuggestedM-inimumHeight 方法的返回值,这里我就分析 getSuggestedMinimum-Height 方法就好了,getSuggestedMinimumWidth 方法的原理跟 getSuggestedMinimumHeight 方法的实现原理是相同的;好,我们现在看一下 View 的 getSuggestedMinimumHeight 方法;
图片
看注释6的代码,mMinHeight 是 View 属性的 minHeight 值,默认值为0,而 mBackground.getMinimumHeight() 就是图片的最小高度,当这个 View 设置了图片背景的时候,那么 View 的 getSuggestedMinimu-mHeight方法就取 mMinHeight 和 图片的最小高度这2者的最大值,这就是 View 在 View.MeasureSpec.UNSPECIFIED 模式下测量的高;我这里举个例子,给出以下代码;
图片
看一下这个 TextView,它的 minHeight 的属性值为50px,它加了一个背景图片 ic_launcher,假设 ic_launcher 的最小高度是70px,那么该 TextView 在 View.MeasureSpec.UNSPECIFIED 模式下测量的高就是70px了。
我们看注释2中的 setMeasuredDimension方法;
图片
看注释7,View 的 setMeasuredDimension方法又调用了 View 的 setMeasuredDimensionRaw 方法;
图片
看到 View 的 setMeasuredDimensionRaw 方法了没,它只是将 View 测量后的宽高赋值给了 mMeasuredWidth、mMeasuredHeight 保存。
1、1、2 具体 View 的 measure 过程
这里具体的 View(比如说 TextView、ImageView 等),它们的 measure 有什么不同呢?具体的 View 的话,它是重写了它的 onMeasure 方法;我们就以 TextView 为例,说一下 TextView 的 measure ,因为 View 的 measure 方法是 final 类型的,所以我们只需要看 TextView 的 onMeasure 方法就好;
图片
看注释8、9,TextView 重写了 onMeasure 方法对 widthMode 为 View.MeasureSpec.AT_MOST 和 heightMode 为 View.MeasureS-pec.AT_MOST 做出了特殊处理;如果 TextView 不重写 onMeasure 并且宽和高的值都为 wrap_content 会怎么样呢?我们回到 View 的 getDefaultSize(int size, int measureSpec) 方法,看注释4、5 的代码,当 specMode 为 View.MeasureSpec.AT_MOST 和 View.MeasureSpe-c.EXACTLY 的时候,specSize 的值是一样的对不对?也就是说没有重写 onMeasure 方法的 View 的宽高值如果为 wrap_content,那么 View 的宽高值为 match_parent 时显示效果是一样的;不信?我举个例子,先看一下如下我一个 Activity 的 xml 文件的代码;
图片
假设我这个 View 标签看做是一个没有重写 onMeasure 方法的 "TextView" 标签,我执行一下app,运行结果如下所示;
图片
看到没,这个 View 设置宽高为 wrap_content 时,直接充满整个屏幕,而它的父容器是也充满整个屏幕的。
看回注释7的代码,它又调用到了 View 的 setMeasuredDimension 方法,而 View 的 setMeasuredDimension 方法又会调用到 View 的 setMeasuredDimensionRaw 方法,也最终在 setMeasuredDi-mensionRaw 方法设置好它的测量宽高。
1、2 ViewGroup 的 measure 过程
如果是 ViewGroup 去 measure,它除了给自己 measure 之外,还要给它的子元素进行 measure(如果有子元素的话),各个子元素再递归去执行 measure 这个过程;ViewGroup 是一个抽象类,它提供了一个分发给子元素进行测量的 measureChildWithMargins 方法,另外每个具体的 ViewGroup 容器测量过程不一样,所以 ViewGroup 并没有重写 onMeasure 方法,而是在 ViewGroup 的子类重写了 onMeasure 方法;我们以 FrameLayout 为例来分析一下,由于每一个 View 的子类不能重写 measure 方法,但 measure 方法会调用子类的 onMeasure 方法,所以我们从 FrameLayout 的 onMeasure 方法看就好;
图片
看注释11处的代码,如果这个 ViewGroup(实际是FrameLayout)没有子元素,那么 count 就为0,那么就不会走 for 循环里面的语句对不对,那么只是测量它自己而已,也就是执行注释13处的代码,会调用 View 的 setMeasuredDimension 方法,View 的 setMeasuredDimen-sion 方法 又会调用到 setMeasuredDimensionRaw 方法来保存 ViewGroup(实际是FrameLayout)的测量宽高。
假设 ViewGroup(实际是FrameLayout)有子元素,那么注释11处的 count 就大于0对不对?那么就会执行 measureChildWithMargins 方法,而 measureChildWithMargins 方法则会调用子元素的 measure 过程,我们看一下 ViewGroup 的 measureChildWithMargins方法;
图片
看到注释14没,又到子元素递归去执行子元素的 measure 过程,直到某一层的子元素没有子元素则递归结束。