Canvas&Paint 知识梳理(2) Canvas 的保存和恢复

1、和Canvas保存相关的标志

在了解Canvas的保存以前,咱们先看一下和保存相关的标志的定义,它们定义了保存的类型,这些标志定义在Canvas.java当中,一共有六个标志。java

/**
     * Restore the current matrix when restore() is called.
     */
    public static final int MATRIX_SAVE_FLAG = 0x01;

    /**
     * Restore the current clip when restore() is called.
     */
    public static final int CLIP_SAVE_FLAG = 0x02;

    /**
     * The layer requires a per-pixel alpha channel.
     */
    public static final int HAS_ALPHA_LAYER_SAVE_FLAG = 0x04;

    /**
     * The layer requires full 8-bit precision for each color channel.
     */
    public static final int FULL_COLOR_LAYER_SAVE_FLAG = 0x08;

    /**
     * Clip drawing to the bounds of the offscreen layer, omit at your own peril.
     * <p class="note"><strong>Note:</strong> it is strongly recommended to not
     * omit this flag for any call to <code>saveLayer()</code> and
     * <code>saveLayerAlpha()</code> variants. Not passing this flag generally
     * triggers extremely poor performance with hardware accelerated rendering.
     */
    public static final int CLIP_TO_LAYER_SAVE_FLAG = 0x10;

    /**
     * Restore everything when restore() is called (standard save flags).
     * <p class="note"><strong>Note:</strong> for performance reasons, it is
     * strongly recommended to pass this - the complete set of flags - to any
     * call to <code>saveLayer()</code> and <code>saveLayerAlpha()</code>
     * variants.
     */
    public static final int ALL_SAVE_FLAG = 0x1F;
复制代码

从上面的定义能够看出,flag是用一个32位的int型变量来定义的,它的低5位的每一位用来表示须要保存Canvas当前哪部分的信息,若是所有打开,那么就是所有保存,也就是最后定义的ALL_SAVE_FLAG,这5位分别对应:android

  • xxxx1:保存Matrix信息,例如平移、旋转、缩放、倾斜等。
  • xxx1x:保存Clip信息,也就是裁剪。
  • xx1xx:保存Alpha信息。
  • x1xxx:保存8位的颜色信息。
  • 1xxxxClip drawing to the bounds of the offscreen layer,不太明白是什么意思。

若是须要多选以上的几个信息进行保存,那么对多个标志位执行或操做便可。canvas

2、save()save(int saveFlags)

下面是这两个方法的定义:bash

/**
     * Saves the current matrix and clip onto a private stack.
     * <p>
     * Subsequent calls to translate,scale,rotate,skew,concat or clipRect,
     * clipPath will all operate as usual, but when the balancing call to
     * restore() is made, those calls will be forgotten, and the settings that
     * existed before the save() will be reinstated.
     *
     * @return The value to pass to restoreToCount() to balance this save()
     */
    public int save() {
        return native_save(mNativeCanvasWrapper, MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG);
    }

    /**
     * Based on saveFlags, can save the current matrix and clip onto a private
     * stack.
     * <p class="note"><strong>Note:</strong> if possible, use the
     * parameter-less save(). It is simpler and faster than individually
     * disabling the saving of matrix or clip with this method.
     *
     * @param saveFlags flag bits that specify which parts of the Canvas state
     *                  to save/restore
     * @return The value to pass to restoreToCount() to balance this save()
     */
    public int save(@Saveflags int saveFlags) {
        return native_save(mNativeCanvasWrapper, saveFlags);
    }
复制代码

注释已经很好地说明了save()save(int saveFlags)的做用:当调用完save方法以后,例如平移、缩放、旋转、倾斜、拼接或者裁剪这些操做,都是和原来的同样,而当调用完restore方法以后,在save()restore()之间的全部操做都会被遗忘,而且会恢复调用save()以前的全部设置。此外还能够得到如下信息:app

  • 这两个方法最终都调用native_save方法,而无参方法save()默认是保存MatrixClip这两个信息。
  • 若是容许,那么尽可能使用无参的save()方法,而不是使用有参的save(int saveFlags)方法传入别的Flag
  • 该方法的返回值,对应的是在堆栈中的index,以后能够在restoreToCount(int saveCount)中传入它来说在它之上的全部保存图层都出栈。
  • 全部的操做都是调用了native_save来对这个mNativeCanvasWrapper变量,咱们会发现,全部对于Canvas的操做,其实最终都是操做了mNativeCanvasWrapper这个对象。
  • XXX_SAVE_FLAG的命名来看,带有参数的save(int saveFlags)方法只容许保存MATRIX_/CLIP_/ALL_这三种状态,而HAS_ALPHA_LAYER/FULL_COLOR_LAYER_/CLIP_TO_LAYER_这三种状态,则是为后面的saveLayer/saveLayerAlpha提供的。

3、restore() restoreToCount(int count) getSaveCount()

这三个方法用来恢复图层信息,也就是将以前保存到栈中的元素出栈,咱们看一下这几个方法的定义:less

/**
     * This call balances a previous call to save(), and is used to remove all
     * modifications to the matrix/clip state since the last save call. It is
     * an error to call restore() more times than save() was called.
     */
    public void restore() {
        boolean throwOnUnderflow = !sCompatibilityRestore || !isHardwareAccelerated();
        native_restore(mNativeCanvasWrapper, throwOnUnderflow);
    }

    /**
     * Returns the number of matrix/clip states on the Canvas' private stack. * This will equal # save() calls - # restore() calls. */ public int getSaveCount() { return native_getSaveCount(mNativeCanvasWrapper); } /** * Efficient way to pop any calls to save() that happened after the save * count reached saveCount. It is an error for saveCount to be less than 1. * * Example: * int count = canvas.save(); * ... // more calls potentially to save() * canvas.restoreToCount(count); * // now the canvas is back in the same state it was before the initial * // call to save(). * * @param saveCount The save level to restore to. */ public void restoreToCount(int saveCount) { boolean throwOnUnderflow = !sCompatibilityRestore || !isHardwareAccelerated(); native_restoreToCount(mNativeCanvasWrapper, saveCount, throwOnUnderflow); } 复制代码

这个几个方法很好理解:ide

  • restore()方法用来恢复,最近一次调用save()以前Matrix/Clip信息,若是调用restore()的次数大于save()的一次,也就是栈中已经没有元素,那么会抛出异常。
  • getSaveCount():返回的是当前栈中元素的数量。
  • restoreToCount(int count)会将saveCount()之上对应的全部元素都出栈,若是count < 1,那么会抛出异常。
  • 它们最终都是调用了native的方法。

4、示例

下面,咱们用一个简单的例子,来更加直观的了解一下save()restore(),咱们重写了一个View当中的onDraw()方法:布局

4.1 恢复Matrix信息

private void saveMatrix(Canvas canvas) {
        //绘制蓝色矩形
        mPaint.setColor(getResources().getColor(android.R.color.holo_blue_light));
        canvas.drawRect(0, 0, 200, 200, mPaint);
        //保存
        canvas.save();
        //裁剪画布,并绘制红色矩形
        mPaint.setColor(getResources().getColor(android.R.color.holo_red_light));
        //平移.
        //canvas.translate(100, 0);
        //缩放.
        //canvas.scale(0.5f, 0.5f);
        //旋转
        //canvas.rotate(-45);
        //倾斜
        canvas.skew(0, 0.5f);
        canvas.drawRect(0, 0, 200, 200, mPaint);
        //恢复画布
        canvas.restore();
        //绘制绿色矩形
        mPaint.setColor(getResources().getColor(android.R.color.holo_green_light));
        canvas.drawRect(0, 0, 50, 200, mPaint);
    }
复制代码

咱们对画布分别进行了平移、缩放、旋转、倾斜,获得的结果为: 性能

Translate.png

Scale.png

Rotate.png

Skew.png

能够看到,当咱们调用上面这些方法时,其实就是对Canvas先进行了一些移动,旋转,缩放的操做,而后再在它这个新的状态上进行绘制,以后调用restore()以后,又恢复回了调用save()以前的状态。ui

4.2 恢复Clip信息

private void saveClip(Canvas canvas) {
        //绘制蓝色矩形
        mPaint.setColor(getResources().getColor(android.R.color.holo_blue_light));
        canvas.drawRect(0, 0, 200, 200, mPaint);
        //保存.
        canvas.save();
        //裁剪画布,并绘制红色矩形
        mPaint.setColor(getResources().getColor(android.R.color.holo_red_light));
        canvas.clipRect(150, 0, 200, 200);
        canvas.drawRect(0, 0, 200, 200, mPaint);
        //恢复画布
        canvas.restore();
        //绘制绿色矩形
        mPaint.setColor(getResources().getColor(android.R.color.holo_green_light));
        canvas.drawRect(0, 0, 50, 200, mPaint);
    }
复制代码

最终的结果以下所示:

  • 初始的时候,canvas的大小和View的大小是同样的,咱们以(0,0)为原点坐标,绘制了一个大小为200px * 200px的蓝色矩形。
  • 咱们保存画布的当前信息,以后以(150, 0)为原点坐标,裁剪成了一个大小为50px * 200px的新画布,对于这个裁剪的过程能够这么理解:就是咱们之前上学时候用的那些带镂空的板子,上面有各类的形状,而这一裁剪,其实就是在原来的canvas上盖了这么一个镂空的板子,镂空部分就是咱们定义的裁剪区域,当咱们进行绘制时,就是在这个板子上面进行绘制,而最终canvas上展示的部分就是这些镂空部分和以后绘制部分的交集
  • 以后咱们尝试以(0, 0)为原点绘制一个大小为200px * 200px的红色矩形,可是此时因为(0, 0) - (150, 200)这部分被盖住了,因此不咱们画不上去,实际画上去的只有(50, 0) - (200, 200)这一部分。
  • 以后调用了restore()方法,就至关于把板子拿掉,那么这时候就可以像以前那样正常的绘制了。

5、saveLayer saveLayerAlpha

5.1 方法定义

除了save()方法以外,canvas还提供了saveLayer方法

以上的这八个方法能够分为两个大类: saveLayersaveLayerAlpha,它们最终都是调用了两个 native方法: 对于 saveLayer,若是不传入 saveFlag,那么默认是采用 ALL_SAVE_FLAG

native_saveLayer(mNativeCanvasWrapper, left, top, right, bottom, paint != null ? paint.getNativeInstance() : 0, saveFlags);
复制代码

对于saveLayerAlpha,若是不传入saveFlag,那么默认是采用ALL_SAVE_FLAG,若是不传入alpha,那么最终调用的alpha = 0

native_saveLayerAlpha(mNativeCanvasWrapper, left, top, right, bottom, alpha, saveFlags);
复制代码

5.2 和save()方法的区别

关于save()saveLayer()的区别,源码当中是这么解释的,也就是说它会新建一个不在屏幕以内的bitmap,以后的全部绘制都是在这个bitmap上操做的。

This behaves the same as save(), but in addition it allocates and redirects drawing to an offscreen bitmap.

而且这个方法是至关消耗资源的,由于它会致使内容的二次渲染,特别是当canvas的边界很大或者使用了CLIP_TO_LAYER这个标志时,更推荐使用LAYER_TYPE_HARDWARE,也就是硬件渲染来进行Xfermode或者ColorFilter的操做,它会更加高效。

* this method is very expensive,
     *
     * incurring more than double rendering cost for contained content. Avoid
     * using this method, especially if the bounds provided are large, or if
     * the {@link #CLIP_TO_LAYER_SAVE_FLAG} is omitted from the
     * {@code saveFlags} parameter. It is recommended to use a
     * {@link android.view.View#LAYER_TYPE_HARDWARE hardware layer} on a View
     * to apply an xfermode, color filter, or alpha, as it will perform much
     * better than this method.
复制代码

当咱们在以后调用restore()方法以后,那么这个新建的bitmap会绘制回Canvas的当前目标,若是当前就位于canvas的最底层图层,那么就是目标屏幕,不然就是以前的图层。

* All drawing calls are directed to a newly allocated offscreen bitmap.
     * Only when the balancing call to restore() is made, is that offscreen
     * buffer drawn back to the current target of the Canvas (either the
     * screen, it's target Bitmap, or the previous layer). 复制代码

再回头看下方法的参数,这两大类方法分别传入了PaintAlpha这两个变量,对于saveLayer来讲,Paint的下面这三个属性会在新生成的bitmap被从新绘制到当前画布时,也就是调用了restore()方法以后,被采用:

* Attributes of the Paint - {@link Paint#getAlpha() alpha},
     * {@link Paint#getXfermode() Xfermode}, and
     * {@link Paint#getColorFilter() ColorFilter} are applied when the
     * offscreen bitmap is drawn back when restore() is called.
复制代码

而对于saveLayerAlpha来讲,它的Alpha则会在被从新绘制回来时被采用:

* The {@code alpha} parameter is applied when the offscreen bitmap is
     * drawn back when restore() is called.
复制代码

对于这两个方法,都推荐传入ALL_SAVE_FLAG来提升性能,它们的返回值和save()方法的含义是相同的,都是用来提供给restoreToCount(int count)使用。 总结一下:就是调用saveLayer以后,建立了一个透明的图层,以后在调用restore()方法以前,咱们都是在这个图层上面进行操做,而save方法则是直接在原先的图层上面操做,那么对于某些操做,咱们不但愿原来图层的状态影响到它,那么咱们应该使用saveLayer

6、saveLayer示例

和前面相似,咱们建立一个继承于ViewSaveLayerView,并重写onDraw(Canvas canvas)方法:

6.1 验证建立新的图层理论

首先,由于咱们前面整个一整节获得的结论是saveLayer会建立一个新的图层,而验证是否产生新图层的方式就是采用Paint#setXfermode()方法,经过它和下面图层的结合关系,咱们就能知道是否生成了一个新的图层了。当使用saveLayer时:

@Override
    protected void onDraw(Canvas canvas) {
        useSaveLayer(canvas);
    }

    private void useSaveLayer(Canvas canvas) {
        //1.先画一个蓝色圆形.
        canvas.drawCircle(mRadius, mRadius, mRadius, mBlueP);
        //canvas.save();
        //2.这里产生一个新的图层
        canvas.saveLayer(0, 0, mRadius + mRadius, mRadius + mRadius, null);
        //3.现先在该图层上画一个绿色矩形
        canvas.drawRect(mRadius, mRadius, mRadius + mRadius, mRadius + mRadius, mGreenP);
        //4.设为取下面的部分
        mRedP.setXfermode(mDstOverXfermode);
        //5.再画一个红色圆形,若是和下面的图层有交集,那么取下面部分
        canvas.drawCircle(mRadius, mRadius, mRadius/2, mRedP);
    }
复制代码

当咱们使用saveLayer()方法时,获得的是:

而当咱们使用 save()方法,获得的则是:
只因此产生不一样的结果,是由于第4步的时候,咱们给红色画笔设置了 DST_OVER模式,也就是底下部分和即将画上去的部分有重合的时候,取底下部分。当咱们在第2步当中使用 saveLayer的时候,按咱们的假设,会产生一个新的图层,那么第3步的绿色矩形就是画在这个新的透明图层上的,所以第5步画红色圆形的时候, DST是按绿色矩形部分来算的,重叠部分只占了红色圆形的 1/4,所以最后画出来的结果跟第一张图同样。 而不使用 saveLayer时,因为没有产生新的图层,所以在第5步绘制的时候, DST实际上是由蓝色圆形和绿色矩形组成的,这时候和红色圆形的重叠部分占了整个红色圆形,因此最后画上去的时候就看不到了。 这就很好地验证了 saveLayer会建立一个新的图层。

6.2 saveLayerAlpha

下面,咱们再来看一下saveLayerAlpha,这个方法能够用来产生一个带有透明度的图层:

private void useSaveLayerAlpha(Canvas canvas) {
        //先划一个蓝色的圆形.
        canvas.drawCircle(mRadius, mRadius, mRadius, mBlueP);
        //canvas.save();
        //这里产生一个新的图层
        canvas.saveLayerAlpha(0, 0, mRadius + mRadius, mRadius + mRadius, 128);
        //现先在该图层上画一个矩形
        canvas.drawRect(mRadius, mRadius, mRadius + mRadius, mRadius + mRadius, mGreenP);
    }
复制代码

最终,咱们就获得了下面的带有透明度的绿色矩形覆盖在上面:

6.3 HAS_ALPHA_LAYER_XXXFULL_COLOR_LAYER_XXX

HAS_ALPHA_LAYER表示图层结合的时候,没有绘制的地方会是透明的,而对于FULL_COLOR_LAYER_XXX,则会强制覆盖掉。 首先,咱们先看一下整个布局为一个黑色背景的Activity,里面有一个背景为绿色的自定义View

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/black"
    tools:context="com.example.lizejun.repocanvaslearn.MainActivity">
    <com.example.lizejun.repocanvaslearn.SaveLayerView
        android:background="@android:color/holo_green_light"
        android:layout_width="200dp"
        android:layout_height="200dp" />
</RelativeLayout>
复制代码

下面,咱们重写onDraw(Canvas canvas)方法:

private void useSaveLayerHasAlphaOrFullColor(Canvas canvas) {
        //先划一个蓝色的圆形.
        canvas.drawRect(0, 0, mRadius * 2, mRadius * 2, mBlueP);
        //这里产生一个新的图层
        canvas.saveLayer(0, 0, mRadius, mRadius, null, Canvas.FULL_COLOR_LAYER_SAVE_FLAG);
        //canvas.saveLayer(0, 0, mRadius, mRadius, null, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);
        //绘制一个红色矩形.
        canvas.drawRect(0, 0, mRadius / 2, mRadius / 2, mRedP);
    }
复制代码

当采用FULL_COLOR_LAYER_SAVE_FLAG时,对应的结果为下图:

而采用 HAS_ALPHA_LAYER_SAVE_FLAG时,对应的结果为:
能够看到,当使用 FULL_COLOR_LAYER_SAVE_FLAG,不只下一层本来绘制的蓝色没有用,连 View自己的绿色背景也没有了,最后透上来的是 Activity的黑色背景。 关于这个方法,有几个须要注意的地方:

  • 须要在View中禁用硬件加速。
setLayerType(LAYER_TYPE_SOFTWARE, null);
复制代码
  • 当两个共用时,以FULL_COLOR_LAYER_SAVE_FLAG为准。
  • 当调用saveLayer而且只指定MATRIX_SAVE_FLAG或者CLIP_SAVE_FLAG时,默认的合成方式是FULL_COLOR_LAYER_SAVE_FLAG

6.3 CLIP_TO_LAYER_SAVE_FLAG

它在新建bitmap前,先把canvas给裁剪,一旦画板被裁剪,那么其中的各个画布就会被受到影响,而且它是没法恢复的。当其和CLIP_SAVE_FLAG共用时,是能够被恢复的。

6.4 ALL_SAVE_FLAG

对于save()来讲,它至关于MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG。 对于saveLayer()来讲,它至关于MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG|HAS_ALPHA_LAYER_SAVE_FLAG

7、参考文献

1.http://blog.csdn.net/cquwentao/article/details/51423371

相关文章
相关标签/搜索