在自定义view中,我们继承于一个ViewGraoup,我们发现绘制的东西不能显示出来,这是什么情况,下面我们来一探究竟。
自定义view,则肯定牵扯到View绘制流程,在讲View绘制流之前,咱们先简单介绍一下Android视图层次结构以及DecorView,因为View的绘制流程的入口和DecorView有着密切的联系。
我们都知道View的绘制流程会走ViewRootImpl类中如下逻辑:
private void performTraversals() {
......
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
......
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
......
performLayout(lp, mWidth, mHeight);
......
performDraw();
}
1)performMeasure():从根节点向下遍历View树,完成所有ViewGroup和View的测量工作,计算出所有ViewGroup和View显示出来需要的高度和宽度;
2)performLayout():从根节点向下遍历View树,完成所有ViewGroup和View的布局计算工作,根据测量出来的宽高及自身属性,计算出所有ViewGroup和View显示在屏幕上的区域;
3)performDraw():从根节点向下遍历View树,完成所有ViewGroup和View的绘制工作,根据布局过程计算出的显示区域,将所有View的当前需显示的内容画到屏幕上。
今天讲的是关于onDraw相关的,所以我们重点讲解performDraw()。
private void performDraw() {
......
boolean canUseAsync = draw(fullRedrawNeeded);
......
}
private boolean draw(boolean fullRedrawNeeded) {
......
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
return false;
}
....
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
......
mView.draw(canvas);
......
}
从最前面我们知道,最外层是DecorView,所以会调用DecorView的onDraw()。
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (mMenuBackground != null) {
mMenuBackground.draw(canvas);
}
}
在DecorView的draw()调用的是父类的Draw(),而DecorView是继承Framelayout的,Framelayout和ViewGroup都没有实现draw(),所以进入View的draw()。
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
// 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 (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
......
// Step 3, draw the content
onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
final Paint p = scrollabilityCache.paint;
final Matrix matrix = scrollabilityCache.matrix;
final Shader fade = scrollabilityCache.shader;
canvas.restoreToCount(saveCount);
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);
}
View的draw()主要做了以下事,
1、drawBackground(canvas);绘制背景
2、 onDraw(canvas);绘制内容
3、dispatchDraw(canvas);绘制子View
4、onDrawForeground(canvas);绘制装饰
所以会调用DecorView的onDraw();
public void onDraw(Canvas c) {
super.onDraw(c);
mBackgroundFallback.draw(this, mContentRoot, c, mWindow.mContentParent,
mStatusColorViewState.view, mNavigationColorViewState.view);
}
调用父类的onDraw,而父类FrameLayout没有重写onDraw(),所以调用ViewGroup的onDraw()。ViewGroup也没有重写onDraw(),最终走到View的onDraw().
protected void onDraw(Canvas canvas) {
}
是一个空实现,所以在onDraw里面什么都没做,然后调用DecorView的dispatchDraw().
当本view被画完之后,就开始要画它的子view了。dispatchDraw()方法也是一个空方法,实际上对于叶子view来说,该方法没有什么意义,因为它没有子view需要画了,而对于ViewGroup来说,就需要重写该方法来画它的子view。
在源码中发现,像平时常用的LinearLayout、FrameLayout、RelativeLayout等常用的布局控件,都没有再重写该方法,DecorView中也一样,而是只在ViewGroup中实现了dispatchDraw方法的重写。所以当DecorView执行完onDraw方法后,流程就会切到ViewGroup中的dispatchDraw方法了。
protected void dispatchDraw(Canvas canvas) {
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);
}
}
while (transientIndex >= 0) {
// there may be additional transient views after the normal views
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);//1
}
transientIndex++;
if (transientIndex >= transientCount) {
break;
}
}
}
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
重点看注释1.最后调用View的draw(),只是这个draw()有三个参数。
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
......
if (!drawingWithDrawingCache) {
if (drawingWithRenderNode) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
((RecordingCanvas) canvas).drawRenderNode(renderNode);
} else {
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas);
} else {
draw(canvas);
}
}
}
......
return more;
}
从上面可以知道,在ViewGroup调用dispatchDraw()还是调用draw主要是看mPrivateFlags这个变量的值。
全局搜索查到,在setFlags(),将mPrivateFlags进行赋值。
public boolean setFlags(){
if ((changed & DRAW_MASK) != 0) {
if ((mViewFlags & WILL_NOT_DRAW) != 0) {
if (mBackground != null
|| mDefaultFocusHighlight != null
|| (mForegroundInfo != null && mForegroundInfo.mDrawable != null)) {
mPrivateFlags &= ~PFLAG_SKIP_DRAW;
} else {
mPrivateFlags |= PFLAG_SKIP_DRAW;
}
} else {
mPrivateFlags &= ~PFLAG_SKIP_DRAW;
}
requestLayout();
invalidate(true);
}
}
从上面可以知道,当有背景属性,或者高亮的时候,或者前景图片不为null的时候,就会将这个PFLAG_SKIP_DRAW还原标志位为0.(解释一下|= 和&= ~的意义:|=就是将标志位置为1,&= ~将标志位置为0)。反之mPrivateFlags设置成PFLAG_SKIP_DRAW。当mPrivateFlags设置成PFLAG_SKIP_DRAW时,回到draw(),(mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW为true,则会调用dispatchDraw(),就不会走draw(),即不会走onDraw().
那setFlags()在什么时候调用,ViewGroup的初始化的时候。
private void initViewGroup() {
// ViewGroup doesn't draw by default
if (!debugDraw()) {
setFlags(WILL_NOT_DRAW, DRAW_MASK);
}
}
在ViewGroup初始化的时候,就直接将flag这是为WILL_NOT_DRAW,if ((mViewFlags & WILL_NOT_DRAW) != 0)则成立,当没有背景这些时,就会把 mPrivateFlags |= PFLAG_SKIP_DRAW,当mPrivateFlags设置成PFLAG_SKIP_DRAW时,回到draw(),(mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW为true,则会调用dispatchDraw(),就不会走draw(),即不会走onDraw().
此时为什么设置背景又可以走onDraw()?
public void setBackgroundDrawable(Drawable background) {
computeOpaqueFlags();
if (background == mBackground) {
return;
}
boolean requestLayout = false;
mBackgroundResource = 0;
if (background != null) {
mBackground = background;
......
if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
mPrivateFlags &= ~PFLAG_SKIP_DRAW;
requestLayout = true;
}
} else {
/* Remove the background */
mBackground = null;
if ((mViewFlags & WILL_NOT_DRAW) != 0
&& (mDefaultFocusHighlight == null)
&& (mForegroundInfo == null || mForegroundInfo.mDrawable == null)) {
mPrivateFlags |= PFLAG_SKIP_DRAW;
}
}
......
}
在设置背景图片,会调用setBackgroundDrawable(),当background != null的时候,mPrivateFlags &= ~PFLAG_SKIP_DRAW;相当于置为0.draw()中的(mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW则为false,则会走onDraw();
在setDefaultFocusHighlight()也是一样的逻辑。
所以对于在ViewGroup里面不调用onDraw()解决办法是怎样的?
1、可将onDraw中的处理移至到dispatchDraw()中。因为就算走draw()最后也会调用dispatchDraw()。所以复写view的时候,draw不一定走,但是dispatchDraw一定会走。
2、ViewGroup的初始背景通过setFlags设定为透明的,根据setFlags可以找到这么一个方法setWillNotDraw,
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
源码中也只是将setFlags封装了一层供外部调用,那么我们就可以在ViewGroup的构造函数中调用该方法,并且传入参数false,即
setWillNotDraw(false);
3、xml下添加一个背景。