View工作原理 | draw

500 阅读3分钟

前言

经过前面文章的对View树的measure和layout过程,现在一个页面的所有"View"(一个个矩形)其实都显示在屏幕上了,只不过现在缺少最后一步也就是在这些矩形进行draw,也就是给View绘制上我们可见的、多样多彩的内容。

正文

而draw的流程比较简单,在ViewRootImpl中会直接调用DecorView的draw方法,真正复杂的是canvas的使用,不同的展示效果都是通过canvas画出来的,具体canvas的使用,我们后面文章再慢慢分析。

draw流程

对于View的绘制流程就比较简单了,大致可以分为下面几步:

  1. 绘制背景 background.draw(canvas)

  2. 绘制自己 onDraw

  3. 绘制children dispatchDraw

  4. 绘制装饰 onDrawScrollbars

其实我们想一下现在的手机屏幕,为什么绘制一个View要这么几个步骤,最关键的是因为绘制可以覆盖,即离用户越近的View,可以覆盖下面的View。

流程图如下:

image.png

这里有个非常经典的使用,就是绘制RecyclerView的ItemDecoration,本身RecyclerView就是一个ViewGoup,它通过绘制背景以及绘制子View来达到效果,但是ItemDecoration可以绘制在子View的上面或者下面,其实这个就是利用draw的绘制流程来完成的。

比如下面示意图:

image.png

在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、前景这几个步骤了。

本人能力有限,如果发现有误,欢迎评论指正。