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

相關文章
相關標籤/搜索