安卓开发学习之View的draw(canvas)方法

背景

这几天,我阅读了一下三大常见布局(线性,相对和框架)的测量、布局流程,而这三者的绘制过程,都是在View.draw()方法中实现的,所以我就阅读了一下View的draw(canvas)方法。

由于我只是想了解常见布局的绘制流程,所以一些特殊情况和多余步骤我就没有去看,这不是读小说散文,要带有目的性。


View#draw

主要代码如下

public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
        final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState); // 这个view是否不透明
        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); // 调用onDraw()方法绘制view,这个方法要子类自己去实现

            // Step 4, draw the children
            dispatchDraw(canvas); // 主要调用ViewGroup.dispatchDraw()方法

            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;
        }
        ...

}

这里我主要看了drawBackground()和dispatchDraw()方法


View#drawBackground()

代码如下

private void drawBackground(Canvas canvas) {
        final Drawable background = mBackground;
        if (background == null) {
            return;
        }

        setBackgroundBounds();

        // 一般走不到这儿
        if (canvas.isHardwareAccelerated() && mAttachInfo != null
                && mAttachInfo.mThreadedRenderer != null) {
            mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);

            final RenderNode renderNode = mBackgroundRenderNode;
            if (renderNode != null && renderNode.isValid()) {
                setBackgroundRenderNodeProperties(renderNode);
                ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
                return;
            }
        }

        // 如果发生滚动,计算偏移量
        final int scrollX = mScrollX;
        final int scrollY = mScrollY;
        if ((scrollX | scrollY) == 0) {
            background.draw(canvas);
        } else {
            canvas.translate(scrollX, scrollY); // 直接调用了native方法,应该是对canvas进行坐标偏移
            background.draw(canvas); // 绘制背景,可见我们在滚动view的时候,背景不是一直不变的,而是一直在绘制
            canvas.translate(-scrollX, -scrollY); // 再把canvas复原
        }
}

又调用了setBackgroundBounds()方法,代码如下

void setBackgroundBounds() {
        if (mBackgroundSizeChanged && mBackground != null) { // 背景尺寸变化才会重新设置边界
            mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop); // 设置背景图边界是整个view
            mBackgroundSizeChanged = false;
            rebuildOutline(); // 设置outline轮廓
        }
}

其间,又调用了rebuildOutline()方法,代码如下

private void rebuildOutline() {
        // Unattached views ignore this signal, and outline is recomputed in onAttachedToWindow()
        if (mAttachInfo == null) return;

        if (mOutlineProvider == null) {
            // no provider, remove outline
            mRenderNode.setOutline(null);
        } else { // 有provider,清空outline,并设置轮廓不透明
            final Outline outline = mAttachInfo.mTmpOutline;
            outline.setEmpty();
            outline.setAlpha(1.0f);

            mOutlineProvider.getOutline(this, outline); // ViewOutlineProvider.getOutline()
            mRenderNode.setOutline(outline); // 这直接调用了个native方法,直接忽略
        }
}

这里的关键方法是ViewOutlineProvider.getOutline(),代码如下

public static final ViewOutlineProvider BACKGROUND = new ViewOutlineProvider() {
        @Override
        public void getOutline(View view, Outline outline) {
            Drawable background = view.getBackground();
            if (background != null) { // 从背景获取轮廓
                background.getOutline(outline);
            } else { // 没有背景就把轮廓设为全透明
                outline.setRect(0, 0, view.getWidth(), view.getHeight());
                outline.setAlpha(0.0f);
            }
        }
};


ViewGroup#dispatchDraw

回到View.draw()方法,继续看第二个关键方法dispatchDraw(),这个方法是在ViewGroup类中实现的,代码非常长,但最关键的是下面这么几行

    @Override
    protected void dispatchDraw(Canvas canvas) {
        boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
        final int childrenCount = mChildrenCount;
        final View[] children = mChildren;
        int flags = mGroupFlags;

        for (int i = 0; i < childrenCount; i++) {
            ...

            final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
            final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex); // 相当于children[i]
            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                more |= drawChild(canvas, child, drawingTime);
            }
        }
        ...
    }

主要调用了drawChild()方法,代码如下

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        return child.draw(canvas, this, drawingTime);
}

又调用了View的draw(canvas,parent,drawingTime)方法,这个方法不是上面说的draw(canvas)方法,参数不一样。它的源码也很长,处理很多像动画、硬件加速等东西,跟正常绘制有关的是下面几行

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
        ...
        if (!drawingWithDrawingCache) { // 不用缓存,直接画
            if (drawingWithRenderNode) { // 是否硬件加速
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
            } else {
                // Fast path for layouts with no backgrounds
                if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { // 如果当前子view不需要绘制
                    mPrivateFlags &= ~PFLAG_DIRTY_MASK; 
                    dispatchDraw(canvas); // 绘制子view的子view
                } else {
                    draw(canvas); // 绘制子view自身
                }
            }
        } else if (cache != null) { // 有缓存的话,绘制bitmap。看来view的缓存是做为bitmap保存的?
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            if (layerType == LAYER_TYPE_NONE || mLayerPaint == null) {
                // no layer paint, use temporary paint to draw bitmap
                Paint cachePaint = parent.mCachePaint;
                if (cachePaint == null) {
                    cachePaint = new Paint();
                    cachePaint.setDither(false);
                    parent.mCachePaint = cachePaint;
                }
                cachePaint.setAlpha((int) (alpha * 255));
                canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
            } else {
                // use layer paint to draw the bitmap, merging the two alphas, but also restore
                int layerPaintAlpha = mLayerPaint.getAlpha();
                if (alpha < 1) {
                    mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
                }
                canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
                if (alpha < 1) {
                    mLayerPaint.setAlpha(layerPaintAlpha);
                }
            }
        }
        ...
}


结语

正常的view绘制流程大体就是这样,从View.draw(canvas)到View.onDraw(),再到ViewGroup.dispatchDraw(),如果有子view的话,最后到View.draw(canvas,parent,drawingTime),view利用递归来完成view树的绘制

是否可以这么认为,View.draw(canvas)启动View自己的绘制流程,View.onDraw()则是绘制view本身,ViewGroup.dispatchDraw()则是把绘制过程传递给子view,draw(canvas,parent,drawingTime)开启子view的绘制流程

另外,如果布局是根view,它的draw(canvas)方法在哪儿调用呢?答案是在ViewRootImpl.drawSoftware()方法中被调用,而ViewRootImpl.drawSoftware()方法则被ViewRootImpl.performDraw()调用,而这个performDraw()方法,就是view树绘制的开始

猜你喜欢

转载自blog.csdn.net/qq_37475168/article/details/80325077