前言
经过前面文章的对View树的measure和layout过程,现在一个页面的所有"View"(一个个矩形)其实都显示在屏幕上了,只不过现在缺少最后一步也就是在这些矩形进行draw,也就是给View绘制上我们可见的、多样多彩的内容。
正文
而draw的流程比较简单,在ViewRootImpl中会直接调用DecorView的draw方法,真正复杂的是canvas的使用,不同的展示效果都是通过canvas画出来的,具体canvas的使用,我们后面文章再慢慢分析。
draw流程
对于View的绘制流程就比较简单了,大致可以分为下面几步:
-
绘制背景 background.draw(canvas)
-
绘制自己 onDraw
-
绘制children dispatchDraw
-
绘制装饰 onDrawScrollbars
其实我们想一下现在的手机屏幕,为什么绘制一个View要这么几个步骤,最关键的是因为绘制可以覆盖,即离用户越近的View,可以覆盖下面的View。
流程图如下:
这里有个非常经典的使用,就是绘制RecyclerView的ItemDecoration,本身RecyclerView就是一个ViewGoup,它通过绘制背景以及绘制子View来达到效果,但是ItemDecoration可以绘制在子View的上面或者下面,其实这个就是利用draw的绘制流程来完成的。
比如下面示意图:
在RecyclerView中我们经常通过不同的绘制顺序,来绘制在Item下和Item上的View。
对于onDraw方法,我们再也熟悉不过了,就是使用canvas在View的矩形区域上绘制内容。而dispatchDraw方法在View中是空方法:
protected void dispatchDraw(Canvas canvas) {
}
原因非常简单,因为View是没有子View的,而发现ViewGroup是对dispatchDraw做了完整实现,它会遍历子View,然后调用子View的draw方法,而且一般情况下,我们在自定义ViewGoup时也不会对该方法进行重写。
setWillNotDraw
在View中有一个特殊方法叫做setWillNotDraw:
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
当一个View不需要绘制任何内容时,设置该标记为true之后,系统会对进行相应的优化。
为什么会这样呢 因为当是ViewGoup时,绘制它自己和调用dispatchDraw去绘制子View是分开的,而绝大多数情况下,我们使用ViewGoup仅仅是改变布局,所以不需要绘制自己。
所以默认情况下,View没有启用这个优化标志位,但是ViewGroup会默认启动。当自定义控件继承至ViewGroup时,当明确知道一个ViewGroup需要通过onDraw来绘制内容时,就需要显示关闭该标志位。
总结
draw流程比较简单,记住绘制是可以遮盖的,有先后顺序的,也就不难理解为什么要分绘制背景、内容、子View、前景这几个步骤了。
本人能力有限,如果发现有误,欢迎评论指正。