Android View绘制(二)measure过程
上一篇博客简单地介绍了View
绘制的生命周期, 从这篇博客开始将会对这个周期中一些有用的过程进行一个详细一些的介绍。这篇的主角就是在构造方法之后调用的measure
过程。
为了演示,继承了TextView
来实现一个自定义的View
。注意这里继承的应该是android.support.v7.widget.AppCompatTextView
这个类。同时为了xml
文件的正常解析,我们需要实现View
的三个构造方法。
1 | public VView(Context context) { |
再通过完整包名的方法在xml
布局文件中创建我们的View
就可以直接显示了。
1 | <com.viseator.viewtest.VView |
这里给了TextView
一个背景颜色便于后面的观察。
下面就开始分析measure
过程。
measure
是一个自顶向下的过程,即父View
会依次调用它的子View
的measure()
方法来对它的子View
进行测量。
View
的measure()
方法最终会调用onMeasure()
,真正的尺寸信息就是在onMeasure()
方法中最终确定的。所以我们需要做的就是在自定义View
中重写onMeasure()
方法。
那么子View
根据什么来确定自己应该具有的尺寸呢?当然不可能让子View
自由地决定自己的大小,父View
必然需要向子View
传递信息来帮助子View
来确定尺寸,而子View
则必须满足父View
的要求。查看measure()
的方法签名:
1 | public final void measure(int widthMeasureSpec, int heightMeasureSpec) |
这里的widthMeasureSpec
与heightMeasureSpec
就是存储这一信息的参数。它们的类型是int
,内部以高两位来存储测量的模式,低三十位为测量的大小,计算中使用了位运算来提高并优化效率。当然我们不必使用位运算来获得对应的数值,View.MeasureSpec
为我们提供了对应的方法。
测量模式有三种:
-
EXACTLY
:精确值模式,即子View
必须使用这一尺寸,并且保证它们的所有后代都在这个范围之内。当我们将控件的layout_width
、layout_height
属性指定为具体数值或match_parent
时,系统使用这一模式。 -
UNSPECIFIED
:无限制模式,不对子View
施加任何限制,完全由子View
决定自己的大小。可以用于查看子View
想要的尺寸,比如可以把子View
的长度使用EXACTLY
模式限制在100,不限制宽度来查看子View
在长度为100情况想要的宽度。 -
AT_MOST
:最大值模式,只限制子View
能具有的最大尺寸,子View
必须保证它和它的后代们都在这一范围之内。
了解这些,我们就可以通过重写onMeasure()
来确定一个View
的尺寸。
但在重写方法时要注意:必须调用setMeasuredDimension()
来将最终尺寸存储在View
中,否则会抛出一个IllegalStateException
。
xml
:
1 | <com.viseator.viewtest.VView |
VView
:
1 |
|
log:
这段简单的代码验证了之前的说法,分别对宽高设置了wrap_content
和固定值,可以发现模式分别为AT_MOST
与EXACTLY
(以数值表示)。
这里输出的宽高值是以像素为单位的,可以看到高度的期望值就是设置的大小,但wrap_content
期望的宽度值为1080(屏幕宽度),默认即为屏幕宽度,但最终计算得出的宽度值由于里面没有文字所以为0。
同样地,UNSPECIFIED
模式给出的默认尺寸也是屏幕的宽/高。
所以我们可以看到如果想要实现wrap_content
的效果,我们必须在onMeasure
中对AT_MOST
模式计算其内容宽/高并作为最终的宽/高,否则将以屏幕的宽/高进行填充。以LinearLayout
的源码为例:
1 | if (useLargestChild && |
这部分代码向我们展示了LinearLayout
处理子View
并计算所有的高度的情况。
知道了这个调用过程,我们就可以真正地进行onMeasure()
的重写了。
例如可以暴力指定View
尺寸:
1 |
|
可以为AT_MOST
与UNSPECIFIED
模式指定一个默认大小:
1 |
|
至于更复杂的计算逻辑由于本人能力有限就不写demo了,如果以后实际中遇到需要的时候再作补充。