Android 自定义控件 draw

Read The Fucking Source Code

引言

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

源码版本(Android Q — API 29)

本文涉及Android绘制流程

Android 绘制流程

1. 顶层视角预览 draw

2. draw

2.1 View和ViewGroup的区别

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

2.2 View和ViewGroup的汇总

2.2.1 View的 draw 过程(紫色代表可重写)
2.2.2 ViewGroup的 draw 过程(紫色代表可重写)
2.2.3 draw 各个过程的含义

2.3 draw自顶向下

2.3.1 最顶层(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分发的draw

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分发的draw

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

你可能感兴趣的:(Android 自定义控件 draw)