解决问题:
- View和ViewGroup的draw过程
- 自定义View 继承 ViewGroup,复写onDraw()方法,onDraw()为什么没有被调用?
- 继承ViewGroup的自定义控件如何调用onDraw()绘制内容?
draw过程的大致步骤
draw过程主要是绘制View的外观。ViewGroup除了负责绘制自己之外,还需要负责绘制所有的子View。而不含子View的View对象,就负责绘制自己就可以了。过程的主要流程如下:

- 绘制 backgroud(drawBackground)
- 如果需要的话,保存canvas的layer,来准备fading(不是必要的步骤)
- 绘制view的content(onDraw方法)
- 绘制children(dispatchDraw方法)
- 如果需要的话,绘制fading edges,然后还原layer(不是必要的步骤)
- 绘制装饰器、比如scrollBar(onDrawForeground)
1.1、 View的draw过程
绘制工作主要从draw方法开始
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);
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 (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
- View中 onDraw是一个空实现,由各种类型的View自定义实现
- dispatchDraw()负责控制子View绘制的,在view是空实现,但是在ViewGroup中有具体的实现;这是可以理解的,毕竟只有ViewGroup才需要分发绘制这是可以理解的,毕竟只有ViewGroup才需要分发绘制
1.2、 ViewGroup的draw过程
绘制工作主要从dispatchDraw方法开始
// ViewGroup.java
protected void dispatchDraw(Canvas canvas) {
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
...
}
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
可见 dispatchView内部负责遍历子View,分别调用子VIew的draw()。而且我们自定义ViewGroup中一般也是重写此方法完成绘制
2、自定义View 继承 ViewGroup,复写onDraw()方法,onDraw()为什么没有被调用?
- ViewGroup的源码,ViewGroup里面没有onDraw(Canvas canvas)方法,主要的绘制方法是父类View的draw(Canvas canvas) 方法。
- 分析draw方法,如果一个View不需要绘制任何内容则开启WILL_NOT_DRAW标记位,系统会进行相应优化,不会调用onDraw方法。View默认没有开启,ViewGroup默认开启
public void draw(Canvas canvas) {
// Step 3, draw the content 调用onDraw方法
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
}
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
initViewGroup方法中是ViewGroup开启了WILL_NOT_DRAW
ViewGroup.java
private void initViewGroup() {
// ViewGroup doesn't draw by default
if (!debugDraw()) {
//这个调用了View的方法.
setFlags(WILL_NOT_DRAW, DRAW_MASK);
}
......
}
setFlags方法导致mPrivateFlags 重新赋值 为 true.所以 if (!dirtyOpaque) onDraw(canvas); 这样的onDraw() 方法就进不来了.导致ViewGroup不会调用onDraw() 方法.
3、继承ViewGroup的自定义控件如何调用onDraw()绘制内容?
如果明确知道需要通过onDraw绘制内容两种方法:
- 通过setWillNotDraw方法设置WILL_NOT_DRAW 为false
/**
* @param willNotDraw whether or not this View draw on its own
*/
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
- 设置背景
那我们看一下设置背景的方法
public void setBackground(Drawable background) {
setBackgroundDrawable(background);
}
调用了setBackgroundDrawable方法
/**
* @deprecated use {@link #setBackground(Drawable)} instead
*/
@Deprecated
public void setBackgroundDrawable(Drawable background) {
computeOpaqueFlags();
....省略一大段代码
computeOpaqueFlags();
....省略一点点代码
invalidate(true);
invalidateOutline();
}
我们可以发现在setBackgroundDrawable调用了computeOpaqueFlags方法重新计算mPrivateFlags的值.接着调用invalidate方法触发onDraw方法.
网友评论