Canvas操作以及图层

一、Canvas概念

画布,应用程序绘制图形以及控制图形的动画都是在其上面实现的,它提供了图形绘制的真实表面和绘制图形相关的接口,你的绘画操作真正通过它被渲染在窗口的Bitmap上,使得用户可见。

在View的OnDraw回调事件里,可以获得当前View的Canvas;对于SurfaceView对象你也可以通过 SurfaceHolder.lockCanvas()来获得Canvas;假如你需要创建的一个新的Canvas,你首先需要创建的一个用于真正绘制的Bitmap,通过Bitmap来创建Canvas,代码如下:

Bitmap b = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);

二、Canvas与View之间的关系

在应用程序窗口的绘制过程中,可以逐步理解到Canvas和View之间的关系。
首先由该窗口的ViewRootImpl通过SurfaceHolder.lockCanvas()来获得创建一个Canvas,并把该Canvas传给窗口的DecorView的draw方法,源代码如下:

/**
     * Manually render this view (and all of its children) to the given Canvas.
     * The view must have already done a full layout before this function is
     * called.  When implementing a view, implement
     * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
     * If you do need to override this method, call the superclass version.
     *
     * @param canvas The Canvas to which the View is rendered.
     */
    public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
        final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
        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);
        }

       .......

        // Step 3, draw the content
        if (!dirtyOpaque) onDraw(canvas);

        // Step 4, draw the children
        dispatchDraw(canvas);

       .......

        // Step 6, draw decorations (foreground, scrollbars)
        onDrawForeground(canvas);
    }

接下来通过dispatchDraw(canvas)分发给窗口的子View,源代码如下:

    @Override
    protected void dispatchDraw(Canvas canvas) {

        .........
        // We will draw our child's animation, let's reset the flag
        mPrivateFlags &= ~PFLAG_DRAW_ANIMATION;
        mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;

        boolean more = false;
        final long drawingTime = getDrawingTime();

        if (usingRenderNodeProperties) canvas.insertReorderBarrier();
        final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
        int transientIndex = transientCount != 0 ? 0 : -1;
        // Only use the preordered list if not HW accelerated, since the HW pipeline will do the
        // draw reordering internally
        final ArrayList preorderedList = usingRenderNodeProperties
                ? null : buildOrderedChildList();
        final boolean customOrder = preorderedList == null
                && isChildrenDrawingOrderEnabled();
        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;
                }
            }
            int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
            final View child = (preorderedList == null)
                    ? children[childIndex] : preorderedList.get(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);
            }
            transientIndex++;
            if (transientIndex >= transientCount) {
                break;
            }
        }
        if (preorderedList != null) preorderedList.clear();

        // Draw any disappearing views that have animations
        if (mDisappearingChildren != null) {
            final ArrayList disappearingChildren = mDisappearingChildren;
            final int disappearingCount = disappearingChildren.size() - 1;
            // Go backwards -- we may delete as animations finish
            for (int i = disappearingCount; i >= 0; i--) {
                final View child = disappearingChildren.get(i);
                more |= drawChild(canvas, child, drawingTime);
            }
        }

       ........

    }

通过ViewGroup.drawChild(Canvas canvas, View child, long drawingTime)绘制子View,源代码如下:

 /**
     * Draw one child of this View Group. This method is responsible for getting
     * the canvas in the right state. This includes clipping, translating so
     * that the child's scrolled origin is at 0, 0, and applying any animation
     * transformations.
     *
     * @param canvas The canvas on which to draw the child
     * @param child Who to draw
     * @param drawingTime The time at which draw is occurring
     * @return True if an invalidate() was issued
     */
    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        return child.draw(canvas, this, drawingTime);
    }

然后又通过View.draw(Canvas canvas, ViewGroup parent, long drawingTime),来循环各个绘制自己的view,源代码代码如下:

 /**
     * This method is called by ViewGroup.drawChild() to have each child view draw itself.
     *
     * This is where the View specializes rendering behavior based on layer type,
     * and hardware acceleration.
     */
    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {

       .........

       if (!drawingWithRenderNode) {
            // apply clips directly, since RenderNode won't do it for this draw
            if ((parentFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0 && cache == null) {
                if (offsetForScroll) {
                    canvas.clipRect(sx, sy, sx + getWidth(), sy + getHeight());
                } else {
                    if (!scalingRequired || cache == null) {
                        canvas.clipRect(0, 0, getWidth(), getHeight());
                    } else {
                        canvas.clipRect(0, 0, cache.getWidth(), cache.getHeight());
                    }
                }
            }

            if (mClipBounds != null) {
                // clip bounds ignore scroll
                canvas.clipRect(mClipBounds);
            }
        }
        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) {
                    mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                    dispatchDraw(canvas);
                } else {
                    draw(canvas);
                }
            }
        } else if (cache != null) {
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            if (layerType == LAYER_TYPE_NONE) {
                // 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();
                mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
                canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
                mLayerPaint.setAlpha(layerPaintAlpha);
            }
        }

       .......
    }

最终又回到真正绘制View的Draw(Canvas canvas)方法,从而从最外层DecorView一层层绘制,最终完成整个窗口的绘制。

分析以上过程可知,一个窗口都只有Canvas对象,通过方法draw(Canvas canvas, ViewGroup parent, long drawingTime)我们可以看到,每个子View借助对canvas的裁剪来控制View的显示区域,某View显示区域对应着画布大小,该View的坐标原点对应着Canvas的坐标原点。

三、Canvas操作

先了解一下Canvas.save()和Canvas.restore()这两个方法。
Canvas.save()方法:保存画布,将当前画布的设置保存到一个私有堆栈,用以对画布进行某些操作后也能够恢复到保存之前的画布状态。
Canvas.restore()方法:恢复画布,弹出当前私有堆栈中栈顶的画布状态,来设置当前的画布。
对Canvas进行translate,scale,rotate,skew,concat操作,都只是对其坐标系的操作, 对Canvas进行clipRect, clipPath操作,只会裁剪显示的区域大小,不会影响到Canvas的大小。

四、Canvas层

(1)当前Screen的图层
我们每次调用Canvas的draw…方法都会产生一个透明的层,相邻的两个图层的之间效果可由Paint的Xfermode设置,Paint.setXfermode(new PorterDuffXfermode(int mode)),mode类型主要分为以下16种:

Canvas操作以及图层_第1张图片

1.PorterDuff.Mode.CLEAR 所绘制不会提交到画布上。
2.PorterDuff.Mode.SRC 显示上层绘制图片
3.PorterDuff.Mode.DST 显示下层绘制图片
4.PorterDuff.Mode.SRC_OVER 正常绘制显示,上下层绘制叠盖。
5.PorterDuff.Mode.DST_OVER 上下层都显示。下层居上显示。
6.PorterDuff.Mode.SRC_IN 取两层绘制交集。显示上层。
7.PorterDuff.Mode.DST_IN 取两层绘制交集。显示下层。
8.PorterDuff.Mode.SRC_OUT 取上层绘制非交集部分。
9.PorterDuff.Mode.DST_OUT 取下层绘制非交集部分。
10.PorterDuff.Mode.SRC_ATOP 取下层非交集部分与上层交集部分
11.PorterDuff.Mode.DST_ATOP 取上层非交集部分与下层交集部分
12.PorterDuff.Mode.XOR 异或:去除两图层交集部分
13.PorterDuff.Mode.DARKEN 取两图层全部区域,交集部分颜色加深
14.PorterDuff.Mode.LIGHTEN 取两图层全部,点亮交集部分颜色
15.PorterDuff.Mode.MULTIPLY 取两图层交集部分叠加后颜色
16.PorterDuff.Mode.SCREEN 取两图层全部区域,交集部分变为透明色

通过以上不同PorterDuff.Mode的Paint,来实现多个图层之间绘画的重叠效果,可以理解为相邻两个图层合并为一个新的图层,然后又与其上的相邻图层合并,进而在画布上层层合并,呈现出多次绘画的效果。
(2)幕后Screen的图层
saveLayer (RectF bounds, Paint paint, int saveFlags):可以理解为在后台创建了一个临时图层,相比于当前Screen的图层而言,该图层不会直接渲染在屏幕上,只有当restore()被调用时,才会合并到当前Screen的图层上,否则不会合并,看不到任何绘制效果。创建这种图层的成本比较高,尤其是bounds区域比较大时,应尽量避免采用这种方式创建图层。

你可能感兴趣的:(Android开发基础,Canvas,图层,坐标系,View,Paint)