前置知识
- 有Android开发基础
- 了解 View 体系
- 了解 View 的
Measure
和Layout
方法
前言
在上一篇文章中,笔者带大家学习了 View
的 Layout
流程。这个流程很简单,当程序进到 layout()
方法,执行 setFrame()
和重写的 onLayout()
方法,使用 onLayout()
方法继续遍历其余子元素,就可以找出 View
树所有元素的位置。
本文将继续讲述 View 绘制三大方法的最后一个方法——Draw
方法。该方法不会很复杂,相信大家很快可以弄懂。
Draw 方法的作用和入口
Draw
翻译为绘画。其方法的作用是绘制界面,是 View
绘制流程的最后一步。
我们依旧和上文一样,先来看一下该方法的入口是什么,从入口到 draw
方法又是怎么样的一个流程?
第一步依旧是在 ViewRootImpl
的类中找到 performTraversals()
方法,该方法调用的第三个重要的绘制方法就是 performDraw()
。关于这一点,大家感兴趣的话,可以到 图+源码,读懂View的Measure方法 - 掘金 (juejin.cn) 一文查看。 performDraw()
是 draw
方法的入口处,文章这一部分将讲述从 performDraw()
到 View 中 draw()
方法的流程。
首先,我们进入 performDraw()
方法,这里可以看到在下面代码的注释1处,调用了一个该类下的 draw()
方法。但是很显然,这个 boolean
类型的 draw()
方法并非我们所需要的 View
下面的 draw()
方法。
private boolean performDraw() { ... try { boolean canUseAsync = draw(fullRedrawNeeded, usingAsyncReport && mSyncBuffer);//1 ... } finally { mIsDrawing = false; Trace.traceEnd(Trace.TRACE_TAG_VIEW); } ... return true; } 复制代码
继续在 ViewRootImpl
类下的查看这个 boolean
类型的 draw()
方法。我们忽略一些无关的代码,可以在下方的注释1处看到其调用了一个 drawSoftware()
方法。事实上,这个方法是和 View 中的 draw()
方法有关系的,我们继续往下边查看。
private boolean draw(boolean fullRedrawNeeded, boolean forceDraw) { ... if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) { if (isHardwareEnabled()) { ... } else { ... if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty, surfaceInsets)) {//1 return false; } } } ... return useAsyncReport; } 复制代码
再次点击进去 drawSoftware()
方法查看,我们终于在下面注释2处,看到了 mView.draw(canvas)
字段。可见,View 中的 draw()
方法是在此处被调用的。由此,我们终于看到了 draw
流程的入口。
/** * @return true if drawing was successful, false if an error occurred */ private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty, Rect surfaceInsets) { ... try { ... mView.draw(canvas);//2 drawAccessibilityFocusedDrawableIfNeeded(canvas); } finally { ... } return true; } 复制代码
上面的流程,我们可以用一张图来展示,希望对你的理解有帮助。
Draw流程
源码分析
点进 View 的 draw()
方法的源码,我们首先看方法的注释。方法的注释大意是说:
- 在调用此绘制方法之前,需要已经完成了所有视图的
layout
流程; - 而在实现一个自定义 View 的时候,要重写实现
onDraw()
方法,而不是draw()
这个方法; - 如果确实需要重写此方法(
draw()
),请调用超类版本(surper
)。
再次往下面查看,我们会发现方法内部还有一段注释,该注释写明了 Draw
流程的每个步骤。我们翻译为中文是下面这样子。
- 绘制背景
- 如果有必要,保存
canvas
层以准备逐渐淡出绘制的内容(可以不执行) - 绘制 View 的内容
- 绘制子 View 的内容
- 如果有必要,绘制 View 的渐变淡出边缘(类似阴影效果)和修复层级(可以不执行)
- 绘制装饰,例如滚动条
- 如果有必要,绘制默认的焦点高亮显示(可以不执行)
上面的7个步骤中,他们的执行顺序是固定的,且其中的 2 3 7 不是必要执行的步骤。下面的代码省略了一段包含全部步骤的详细执行代码,感兴趣的同学可以点击查看。下面我们就逐个查看必要执行的步骤执行了什么。
/** * 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; 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) * 7. If necessary, draw the default focus highlight */ // Step 1, draw the background, if needed int saveCount; 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 onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); drawAutofilledHighlight(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); // Step 7, draw the default focus highlight drawDefaultFocusHighlight(canvas); if (isShowingLayoutBounds()) { debugDrawFocus(canvas); } // we're done... return; } ...//源码此处编写了一个完整的程序例程,包括了前面省略的第二步和第5步。由于篇幅原因,此处省略,大家可以点击参考处的详细链接查看 } 复制代码