直接了当的来说,Android系统中View的绘制需要经历三个主要过程: onMeasure()
、onLayout()
、onDraw()
。
onMeasure()
过程负责确定View本身的所占空间和大小(长宽),onLayout()
过程确定View在其父View中的具体位置并根据具体需求调整View的最终绘制大小,onDraw()
则负责将View在Canvas中绘制出来。一般情况下,onMeasure()
过程中确定的View的大小等同于onLayout()
过程结束后View的大小。下面对于这三个过程进行一个详细的描述:
1.onMeasure()
任何一个View,在绘制自己之前都得明确一点,自己的长宽是多少。从源码中可以知道,View绘制的流程开始于ViewRoot的performTraversals()
方法。在这个方法内部又会调用View的measure()
方法,measure()
方法接收两个int型参数widthMeasureSpec和heightMeasureSpec。我们来先来了解一下什么是MeasureSpec。
MeasureSpec
官方API文档中对它的解释是:
"A MeasureSpec encapsulates the layout requirements passed from parent to child. Each MeasureSpec represents a requirement for either the width or the height. A MeasureSpec is comprised of a size and a mode. There are three possible modes:
UNSPECIFIED
The parent has not imposed any constraint on the child. It can be whatever size it wants.
EXACTLY
The parent has determined an exact size for the child. The child is going to be given those bounds regardless of how big it wants to be.
AT_MOST
The child can be as large as it wants up to the specified size.
MeasureSpecs are implemented as ints to reduce object allocation. This class is provided to pack and unpack the <size, mode> tuple into the int."
一大堆的英文解释,简单看看就可以了。这么说吧,MeasureSpec就是View用来确定自己的大小的工具,任何一个View都需要通过父View传递给它的MeasureSpec中定义的specMode和specSize结合自身的定义文件(XML或代码中动态指定的长宽)来确定自己的大小。好的,弄清楚什么是MeasureSpec了我们现在回到measure()
方法中去。
在上文中measure()
方法中会调用到另一个方法onMeasure()
,这个方法里才是Android系统中真正用来确定View的大小的地方。在onMeasure()
中,系统通过调用setMeasuredDimension()
来最终确定一个View的大小是多少。这就是一个View在Measure过程中所经历的步骤。当然,如果一个View是ViewGroup,它还需要根据上面的规则Measure每一个子View的大小。
2.onLayout()
Android的View是一个层叠的结构,我们通过复杂的计算确定了一个View的大小之后,接下来就要考虑的是该把这个View放在其父View什么位置。
ViewRoot的performTraversals()
方法在完成View的Measure之后会进入到View的layout()
方法中去。类似于View的Measure过程中,在这里View的layout()
方法中会去调用onLayout()
方法来做真正的布局过程。然而,当我们进入到onLayout()
方法中去后会发现这是一个空的方法。Why?道理很简单,View的layout这件事情本身就是相对于父View来说的;对于父View(ViewGroup)来讲,每一种类型的布局如LinearLayout、RelativeLayout等的特性都不相同,因此这个onLayout()
最合理的处理办法就是留给ViewGroup的子类自己去做实现。
由于系统自带的任意一种布局的onLayout()
的过程都比较复杂,在这里限于篇幅就不做详细的分析。只有一点需要注意,Measure过程结束后我们拿到了View的大小信息,而在View的onLayout()
方法里面系统默认的实现是将Measure后拿到的大小信息直接传递进来。实际上,我们完全可以在onLayout()
方法中传入我们自定义的数字,这就是我之前提到过的View的onMeasure()
结束后确定的大小一般情况下等于onLayout()
结束后View的最终大小,但是这个结论不是永远都成立,就是因为我们可以自己去强制的改变其大小。在这里,相信我们也能解答很多人都会问的View的getMeasuredWidth()/getMeasuredHeight()
和View的getWidth()/getHeight()
的区别这个问题了。
3.onDraw()
Android系统要绘制一个View,我们先通过Measure过程确定View的大小,再通过Layout过程确定View的最终位置,最后我们就是要真正的把View会画出来。怎么画呢?Android给我提供了Canvas和Paint这一对好工具,我们简单的描述onDraw()
这个过程:Canvas是个画布,Paint是支画笔,Android系统按照一定的规则使用Paint在Canvas按照View的规格把View给“画”出来,就是这么简单。
这个绘制过程,实际包含了以下几个步骤:
1).绘制View的背景(如果必要)
2).绘制View自身内容
3).绘制View的子视图
4).绘制Layer和fade edges
5).绘制ScrollBar
经过这几个步骤之后,Android系统就成功的将一个View绘制出来,变成了呈现在我们眼前的一个按钮、一个文本框或者一副图片等。
最后
通过以上几步简单的分析,相信大家对于View的绘制过程应该能有一定的理解了。对于源码的细节考究,就留给有心人自己去探索吧。