Android 自定义控件 draw

Read The Fucking Source Code

引言

Android自定义控件涉及View的绘制分发流程

源码版本(Android Q — API 29)

前置依赖 【Android 绘制流程】

1. 预览

View绘制流程分发-draw

2. draw

2.1 View 和 ViewGroup 的区别

  • View:View不执行dispatchDraw(子View的绘制)。
  • ViewGroup:ViewGroup执行dispatchDraw(子View的递归绘制)。

2.2 View 和 ViewGroup 的汇总

2.2.1 View draw 过程

紫色代表可重写

View的draw分发

2.2.2 ViewGroup draw 过程

紫色代表可重写

ViewGroup的draw分发

2.2.3 draw 各个过程的含义

draw绘制子流程含义

2.3 draw 自顶向下分发

2.3.1 DecorView 分发

最顶层(DecorView)分发的 draw

2.3.1.1 我们来看 ViewRootImpl 中的 performDraw() 方法。

private void performDraw() {
        //代码省略……

        try {
            //绘制分发
            boolean canUseAsync = draw(fullRedrawNeeded);
            if (usingAsyncReport && !canUseAsync) {
                mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
                usingAsyncReport = false;
            }
        } finally {
            mIsDrawing = false;
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        
        //代码省略……
    }
复制代码

2.3.1.2 我们来看 ViewRootImpl 中的 draw() 方法。

private boolean draw(boolean fullRedrawNeeded) {
        Surface surface = mSurface;
        
        //代码省略……

                //绘制分发
                if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                        scalingRequired, dirty, surfaceInsets)) {
                    return false;
                }
            }
        }

        //代码省略……
    }
复制代码

2.3.1.3 我们来看 ViewRootImpl 中的 drawSoftware() 方法。

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty, Rect surfaceInsets) {

        //代码省略……

            //canvas获取
            canvas = mSurface.lockCanvas(dirty);

        //代码省略……
            //绘制分发,终于到了DecorView的绘制分发
            mView.draw(canvas);

        //代码省略……
    }
复制代码

2.3.2 ViewGroup 分发

2.3.2.1 我们来看 ViewGroup 中的 dispatchDraw() 方法。

@Override
    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) {
                    //遍历子View绘制
                    more |= drawChild(canvas, transientChild, drawingTime);
                }
                transientIndex++;
                if (transientIndex >= transientCount) {
                    transientIndex = -1;
                }
            }
      //代码省略……
    }
复制代码

2.3.2.2 我们来看 ViewGroup 中的 drawChild() 方法。

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        //递归遍历子View的绘制
        return child.draw(canvas, this, drawingTime);
    }
复制代码

2.3.3 View 分发

2.3.3.1 我们来看 View 中的 draw() 方法。

@CallSuper
    public void draw(Canvas canvas) {
        //代码省略……

        // 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;

        //代码省略……

        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);

        if (debugDraw()) {
            debugDrawFocus(canvas);
        }
    }
复制代码

2.3.3.2 View 中的 drawBackground() 方法:绘制背景。

2.3.3.3 我们来看 View 中的 onDraw() 方法。

/**
     * //需要具体控件自己实现,比如TextView
     * Implement this to do your drawing. 
     *
     * @param canvas the canvas on which the background will be drawn
     */
    protected void onDraw(Canvas canvas) {
    }
复制代码

2.3.3.4 我们来看 View 中的 dispatchDraw() 方法。

/**
     * //由draw调用以绘制子视图。这可能会被派生类重写,以便在绘制其子对象之前(但在绘制其自己的视图之后)获得控制权
     * 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) {

    }
复制代码

2.3.3.5 View 中的 drawAutofilledHighlight() 方法:绘制自动填充视图的高亮。

2.3.3.6 View 中的 ViewOverlay::dispatchDraw() 方法:当覆盖层存在时,则绘制覆盖层。

2.3.3.7 我们来看 View 中的 onDrawForeground() 方法。

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.3.3.8 View 中的 drawDefaultFocusHighlight 方法:绘制默认焦点的高亮。

2.3.3.9 View 中的 debugDrawFocus 方法:实就是开发者选项中的显示布局边界的效果。

2.4 问题思考

ViewGroup 为什么无法通过 onDraw 方法绘制自定义内容?如果我希望重写 onDraw 来在 ViewGroup 进行一些绘制操作,怎样才能看到效果?

  • ViewGroup 无法显示绘制内容是因为默认设置的标记位关闭执行了它的 drawBackground 和 onDraw 方法。ViewGroup 一般就是管理布局相关的事项,不处理自身的绘制有助于提高执行效率。
  • 解决思路:调用 View.setWillNotDraw(false),为 ViewGroup 设置背景图。

小编的扩展链接

《Android 视图模块 全家桶》

优秀博客推荐

自定义View Draw过程- 最易懂的自定义View原理系列(4)
自定义View

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享