前面学习了 View 三大流程中的两个:measure 过程(确定 View 的测量宽高)、layout 过程(确定 View 的最终宽高和四个顶点的位置)。下面学习 View 三大流程中的最后一个 --- draw 过程(绘制)。
下面看一下 View 中的 draw() 方法实现:
/**
* 实现视图时,请执行 onDraw() 方法,而不是覆盖这个方法。如果确实需要重写此方法,请调用超类版本
*/
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;
/*
* Draw 遍历执行几个必须执行的绘图步骤
* 按适当次序:
* 1. 绘制背景( background.draw(canvas) )
* 2. 如有必要,保存画布的图层以防淡化
* 3. 绘制自己的内容( onDraw() )
* 4. 绘制 children( dispatchDraw() )
* 5. 如有必要,绘制淡化边缘并恢复图层
* 6. 绘制装饰( 例如 scrollbars -> onDrawScrollBars() )
*/
// Step 1, 绘制背景
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
...
if (!verticalEdges && !horizontalEdges) {
// Step 3, 绘制自己
if (!dirtyOpaque) onDraw(canvas);
// Step 4, 绘制 children
dispatchDraw(canvas);
...
// Step 6, 绘制装饰(前景,滚动条)
onDrawForeground(canvas);
// Step 7, 绘制默认焦点高亮显示
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
...
// Step 2, 保存画布的图层
...
if (solidColor == 0) {
...
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}
if (drawBottom) {
canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
}
if (drawLeft) {
canvas.saveLayer(left, top, left + length, bottom, null, flags);
}
if (drawRight) {
canvas.saveLayer(right - length, top, right, bottom, null, flags);
}
} else {
scrollabilityCache.setFadeColor(solidColor);
}
// Step 3, 绘制自己
if (!dirtyOpaque) onDraw(canvas);
// Step 4, 绘制 children
dispatchDraw(canvas);
// Step 5, 绘制淡入淡出效果并恢复图层
...
if (drawTop) {
...
canvas.drawRect(left, top, right, top + length, p);
}
if (drawBottom) {
...
canvas.drawRect(left, bottom - length, right, bottom, p);
}
if (drawLeft) {
...
canvas.drawRect(left, top, left + length, bottom, p);
}
if (drawRight) {
...
canvas.drawRect(right - length, top, right, bottom, p);
}
canvas.restoreToCount(saveCount);
drawAutofilledHighlight(canvas);
// Step 6, 绘制装饰(前景,滚动条)
onDrawForeground(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
}
从上面可以看到,View 绘制过程的传递是通过 dispatchDraw() 实现的:
protected void dispatchDraw(Canvas canvas) {
...
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
...
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
...
}
...
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
}
然后 dispatchDraw() 中又调用 drawChild():
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
接着 drawChild() 又继续调用 View 中的 draw() 方法,如此反复,一层层的进行传递,就完成了整个 View 树的绘制过程。
下面看一个 View 中的一个特殊方法 --- setWillNotDraw()。
/**
* 如果该 View 本身不需要绘制任何内容,那么设置这个标记位为 true 以后,系统会进行相应的优化。
* 默认情况下,View 没有启用这个优化标志位,但是它的子类 ViewGroup 默认会启用这个优化标志位。
*/
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
ViewGroup.java:
private void initViewGroup() {
// ViewGroup doesn't draw by default
if (!debugDraw()) {
setFlags(WILL_NOT_DRAW, DRAW_MASK);
}
...
}
所以,当我们自定义控件继承于 ViewGroup 并且本身不具备绘制功能时,就可以不管它,默认启用这个优化标志位;但如果我们需要通过 onDraw 来绘制内容时,我们要显式地关闭 WILL_NOT_DRAW 这个标志位,比如可以在其构造方法中调用:
setWillNotDraw(false);