提示:本文的源码均取自Android 7.0(API 24)
前言
自定义View是Android进阶路线上必须攻克的难题,而在这之前就应该先对View的工作原理有一个系统的理解。本系列将分为4篇博客进行讲解,本文主要对View的绘制流程进行讲解。相关内容如下:
从View的角度看draw流程
在本系列的第一篇文章中讲到整个视图树(ViewTree)的根容器是DecorView,ViewRootImpl通过调用DecorView的draw方法开启布局流程。draw是定义在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.
*
* View的子类不应该重写这个方法,而应该重写onDraw方法绘制自己的内容
*
* @param canvas The Canvas to which the View is rendered.
*/
@CallSuper
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
* 完整地绘制流程将按顺序执行以下6步
* 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 绘制子View
* 5. If necessary, draw the fading edges and restore layers 恢复保存的图层
* 6. Draw decorations (scrollbars for instance) 绘制装饰(比如滑动条)
*/
// ① Step 1, 绘制背景(如果有必要的话)
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// 通常情况下会跳过第2步和第5步
// 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, 绘制内容
if (!dirtyOpaque) onDraw(canvas);
// ③ Step 4, 绘制子View
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, 绘制View的装饰 (foreground, scrollbars)
onDrawForeground(canvas);
// ⑤ Step 7, 绘制默认的焦点高亮
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
/* 以下会完整执行所有绘制步骤(一般不会执行到这里)
* Here we do the full fledged routine...
* (this is an uncommon case where speed matters less,
* this is why we repeat some of the tests that have been
* done above)
*/
........
}
这个方法的逻辑非常清晰,这里咱们再来总结一下在draw中要执行的步骤:
- 绘制背景
- 保存Canvas图层信息(如果有必要的话)
- 绘制View的内容
- 绘制子View
- 绘制保存的Canvas图层信息(如果有必要的话)
- 绘制View的装饰(比如滑动条)
其中第2步和第5步在通常情况下是不会执行的,所以我们也就不再深究它们了。首先在代码①的位置,调用了drawBackground
方法绘制View的背景,那让我们首先来看看在这个方法中做了些什么:
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
if (background == null