View的draw过程相比之于measrue过程,也是比较简单的。并且在我们自定义View时,也经常需要重写onDraw
方法,来绘制出我们要实现的效果。
如之前的文章所说,绘制的流程也是起始于ViewRootImpl#perfomTraversals
,ViewRootImpl#performDraw()
方法调用了ViewRootImpl#draw(boolean fullRedrawNeeded)
,其中这个boolean
类型的形参,作用是判断是否需要重新绘制全部视图。最后调用的DecorView.draw(canvas)
方法,自此开始了正式的绘制流程。
ViewGroup
当中并没有实现draw(Canvas canvas)
与onDraw(Canvas canvas)
方法,所以所有View都是调用View#draw
方法, 其源码如下:
/** * Manually render this view (and all of its children) to the given Canvas. * The view must have already done a full layout before this function is * called. When implementing a view, implement * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method. * If you do need to override this method, call the superclass version. * * @param canvas The Canvas to which the View is rendered. */ @CallSuper public void draw(Canvas canvas) { final int privateFlags = mPrivateFlags; //@author www.yaoxiaowen.com // dirtyOpaque 标志位,判断该view是否透明,如果透明,它就可以省略一些步骤 final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE && (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState); mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN; /* * Draw traversal performs several drawing steps which must be executed * in the appropriate order: * * 1. Draw the background * 2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content * 4. Draw children * 5. If necessary, draw the fading edges and restore layers * 6. Draw decorations (scrollbars for instance) */ // Step 1, draw the background, if needed int saveCount; if (!dirtyOpaque) { drawBackground(canvas); } // skip step 2 & 5 if possible (common case) final int viewFlags = mViewFlags; boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0; boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0; if (!verticalEdges && !horizontalEdges) { // Step 3, draw the content if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // Overlay is part of the content and draws beneath Foreground if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); } // Step 6, draw decorations (foreground, scrollbars) onDrawForeground(canvas); // we're done... return; } //... }
源码还是比较复杂的,不过注释倒是很清晰的。所以结合注释看源码,倒也算是一种正确的姿势。
注释当中清晰的说明了绘制流程的6个步骤。
- 绘制View背景。
drawBackground
- 保存当前图层信息。(可跳过)
- 绘制View内容。
onDraw(canvas)
- 绘制子View。(如果有的话)。
dispatchDraw(canvas)
- 绘制View的褪色边缘,类似于阴影效果。(可跳过)
- 绘制View的装饰。(比如滚动条)。
onDrawForeground(canvas)
Skip 1 : 绘制背景
这里调用的是 View#drawBackground
方法,源码如下:
private void drawBackground(Canvas canvas) { //mBackground是该View的背景参数,比如背景颜色 final Drawable background = mBackground; if (background == null) { return; } //根据View四个布局参数来确定背景的边界 setBackgroundBounds(); ... //获取当前View的mScrollX和mScrollY值 final int scrollX = mScrollX; final int scrollY = mScrollY; if ((scrollX | scrollY) == 0) { background.draw(canvas); } else { //如果scrollX和scrollY有值,则对canvas的坐标进行偏移,再绘制背景 canvas.translate(scrollX, scrollY); background.draw(canvas); canvas.translate(-scrollX, -scrollY); }}
Skip 3 : 绘制内容
View#onDraw(Canvas)
方法是空实现,这是因为不同View有不同的内容,所以具体各个子View都有不同的绘制内容。而我们在自定义View的时候,也一定要重写该方法。
Skip 4 : 绘制子View
如果当前View是ViewGroup容器类,那么它就要去循环遍历绘制它的子View了。而View#dispatchDraw
是空实现,只有ViewGroup#dispatchDraw
才有具体实现。
ViewGroup#dispatchDraw
方法的主要内容是 遍历所有子View,对每个子View调用ViewGroup#drawChild
方法,该方法源码如下:
//ViewGroup#drawChildprotected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime);}
这里调用的是 View#draw(Canvas canvas, ViewGroup parent, long drawingTime)
方法。这其实是View#draw(Canvas canvas)
的重载方法.
而三个参数的draw方法,则会先判断是否有缓存,如果有缓存,则利用缓存显示。如果没有,则 调用draw(canvas)
方法,开始正常绘制流程(就是那6个步骤)。
Skip 6 : 绘制装饰
所谓绘制装饰,就是指View除了背景,内容,子View的其余部分。(比如滚动条)。
调用的是View#onDrawForeground
方法,该过程和一般的绘制流程比较相似,都是先设定绘制区域,然后再利用 canvas进行绘制。 自此,view的draw流程就已经分析完了。
其实在这篇博客中,自己思考分析原创部分比较少,而参考别人的内容比较多。
而其实 对于我们自定义控件而言,如果是自定义ViewGroup,则不用重写onDraw
方法,它默认就会遍历所有子View进行绘制。
onDraw
方法,然后结合Paint
来使用canvas.drawRect
, canvas#drawText
等方法来绘制出我们所需要的内容,所以这里要对相关的API比较熟悉。 我觉的这就像之前写的那篇 一样,虽然知道相关流程,但是对相关API不熟悉,其实也无力去实现很多功能。
因此未来如果有时间的话,希望能结合具体的自定义控件,和相关的常用API来就相关过程,进行更详细的讨论吧。参考内容:
- 任玉刚 《Anroid开发艺术探索》
作者:
github:
欢迎对于本人的博客内容批评指点,如果问题,可评论或邮件(yaowen369@gmail.com)联系
欢迎转载,转载请注明出处.谢谢