view

ViewGroup onDraw不会调用

2021-05-06  本文已影响0人  风月寒

在自定义view中,我们继承于一个ViewGraoup,我们发现绘制的东西不能显示出来,这是什么情况,下面我们来一探究竟。

自定义view,则肯定牵扯到View绘制流程,在讲View绘制流之前,咱们先简单介绍一下Android视图层次结构以及DecorView,因为View的绘制流程的入口和DecorView有着密切的联系。

1620288331(1).png

我们都知道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下添加一个背景。

上一篇 下一篇

猜你喜欢

热点阅读