一、draw 作用
draw 的作用是将 View 绘制到屏幕上
二、draw 是什么
三、draw 过程
1. 单一 View 的 draw 过程
(1)draw()
draw 过程的入口是 View 类的draw()
方法,该方法的作用是将视图渲染到给定的 Canvas 上。
- 调用该方法前必须完成 layout 过程
- 自定义 View 时,应复写
onDraw(canvas)
方法进行绘制,不应复写该方法 - 自定义 View 时,若确实要复写该方法,则需先调用
super.draw(canvas)
完成系统的绘制,然后再进行自定义的绘制
/**
* Manually render this view (and all of its children) to the given Canvas.// 手动将视图渲染到给定的Canvas上
* The view must have already done a full layout before this function is called. // 调用该方法前必须要完成 layout 过程
* When implementing a view, implement * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method. // 自定义 View 时重写"onDraw()"方法,而不是本方法
* If you do need to override this method, call the superclass version.// 若自定义的视图确实要复写该方法,那么需先调用 super.draw(canvas)完成系统的绘制,然后再进行自定义的绘制
*
* @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;
/*
* 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;
}
}
draw 过程必须以合适的顺序进行:
① 绘制 View 的背景
② 绘制 View 的内容
③ 绘制子 View
④ 绘制装饰(渐变框、滑动条、前景等)
(2)drawBackground()
/**
* Draws the background onto the specified canvas.
*
* @param canvas Canvas on which to draw the background
*/
private void drawBackground(Canvas canvas) {
// 获取背景 drawable
final Drawable background = mBackground;
if (background == null) {
return;
}
// 根据 layout 过程中获取的 View 的位置参数,来设置背景的边界
setBackgroundBounds();
// Attempt to use a display list if requested.
if (canvas.isHardwareAccelerated() && mAttachInfo != null &&
mAttachInfo.mHardwareRenderer != null) {
mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);
final RenderNode renderNode = mBackgroundRenderNode;
if (renderNode != null && renderNode.isValid()) {
setBackgroundRenderNodeProperties(renderNode);
((DisplayListCanvas) canvas).drawRenderNode(renderNode);
return;
}
}
// 获取 scrollX 和 scrollY
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
// 调用 Drawable 的 draw 方法绘制背景
background.draw(canvas);
} else {
// 若 scrollX 和 scrollY 有值,则对 canvas 的坐标进行偏移
canvas.translate(scrollX, scrollY);
// 调用 Drawable 的 draw 方法绘制背景
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
(3)onDraw()
作用是绘制 View 本身的内容,由于每个 View 的内容各不相同,所以该方法的实现是一个空实现。在自定义 View 中,需子类去复写该方法,从而绘制自身的内容。自定义 View 中必须重写onDraw()
方法。
/**
* 作用:绘制View本身的内容
*
* @param canvas the canvas on which the background will be drawn
*/
protected void onDraw(Canvas canvas) {}
(4)dispatchDraw()
作用是绘制子 View,由于单一 View中无子 View,故 View 类中该方法为空实现。
/**
* Called by draw to draw the child views. This may be overridden
* by derived classes to gain control just before its children are drawn
* (but after its own view has been drawn).
* @param canvas the canvas on which to draw the view
*/
protected void dispatchDraw(Canvas canvas) {
}
(5)onDrawForeground()
作用是绘制装饰。
/**
* 作用:绘制装饰
*
* <p>Foreground content may consist of scroll bars, a {@link #setForeground foreground}
* drawable or other view-specific decorations. The foreground is drawn on top of the
* primary view content.</p>
*
* @param canvas canvas to draw into
*/
public void onDrawForeground(Canvas canvas) {
onDrawScrollIndicators(canvas);
onDrawScrollBars(canvas);
final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
if (foreground != null) {
if (mForegroundInfo.mBoundsChanged) {
mForegroundInfo.mBoundsChanged = false;
final Rect selfBounds = mForegroundInfo.mSelfBounds;
final Rect overlayBounds = mForegroundInfo.mOverlayBounds;
if (mForegroundInfo.mInsidePadding) {
selfBounds.set(0, 0, getWidth(), getHeight());
} else {
selfBounds.set(getPaddingLeft(), getPaddingTop(),
getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
}
final int ld = getLayoutDirection();
Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(),
foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld);
foreground.setBounds(overlayBounds);
}
foreground.draw(canvas);
}
}
2. ViewGroup 的 draw 过程
(1)draw()
ViewGroup 绘制的入口是从 View 类中继承的方法draw()
,该方法的具体介绍参见上文单一 View 的绘制。
(2)drawBackground()
继承 View 类中该方法,参见上文单一 View 的绘制。
(3)onDraw()
继承 View 类中该方法,参见上文单一 View 的绘制。
(4)dispatchDraw()
ViewGroup 类中重写了该方法。
/**
* 作用:遍历子View & 绘制子View
* 注:
* a. ViewGroup中:由于系统为我们实现了该方法,故不需重写该方法
* b. View中默认为空实现(因为没有子View可以去绘制)
*/
protected void dispatchDraw(Canvas canvas) {
......
// 1. 遍历子View
final int childrenCount = mChildrenCount;
......
for (int i = 0; i < childrenCount; i++) {
......
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
// 2. 绘制子View视图
more |= drawChild(canvas, transientChild, drawingTime);
}
....
}
}
/**
* 作用:绘制子View
*/
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
// 最终还是调用了子 View 的 draw ()进行子View的绘制
return child.draw(canvas, this, drawingTime);
}
四、重点说明
1. View 类的setWillNotDraw()
方法
该方法用于设置标志位WILL_NOT_DRAW
的值。当一个 View 不需要绘制内容时,并且该标志位为 true,系统进行相应的优化。
/**
* If this view doesn't do any drawing on its own, set this flag to // 如果这个View不需要绘制任何内容,那么设置这个标记位为true后,系统会进行相应的优化。
* allow further optimizations. By default, this flag is not set on // 默认情况下,View没有启用这个优化标志位,但是ViewGroup默认启用这个标志位。
* View, but could be set on some View subclasses such as ViewGroup. // 当我们的自定义控件继承于ViewGroup并且本身不具备绘制功能时,可开启开标志位便于系统进行后续的优化
*
* Typically, if you override {@link #onDraw(android.graphics.Canvas)}
* you should clear this flag. // 如果一个自定义View重写了onDraw()来绘制内容时,应显示关闭WILL_NOT_DRAW这个标记位。
*
* @param willNotDraw whether or not this View draw on its own
*/
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
对实际开发的意义:
自定义控件继承自 ViewGroup、且本身不进行任何绘制
标志位设为 true,系统会进行相应的优化。自定义控件继承自 ViewGroup、且明确知道一个 ViewGroup 需要通过
onDraw()
来绘制内容时
标志位设为 false。