1. 程式人生 > >Android繪圖之Canvas狀態儲存和恢復(7)

Android繪圖之Canvas狀態儲存和恢復(7)

1 Canvas 狀態儲存和恢復

前面講canvas概念理解時
已經講解了save和savelayer,saveLayerAlpha函式,這裡進行canvas狀態儲存和恢復的詳細講解。

Canvas 呼叫了translate,scale,rotate,skew,concat or clipRect等變換後,後續的操作都是基於變換後的Canvas,都會受到影響,對於後續的操作很不方便。

Canvas提供了save,saveLayer,saveLayerAlpha,restore,restoreToCount來儲存和恢復狀態,有了這些函式,我們就可以隨時恢復canvas以前的狀態,不必擔心當前操作對畫布造成的影響。

2 儲存和恢復狀態函式

注意:
Canvas提供的save,saveLayer等儲存狀態函式還提供了很多flag,類似MATRIX_SAVE_FLAG,CLIP_SAVE_FLAG, ALL_SAVE_FLAG等,預設如果呼叫沒有flag的函式flag預設為 ALL_SAVE_FLAG就是所有的狀態都儲存,而且新的api把
帶有flag的函式都標記成了deprecated,推薦使用不帶flag的函式,進行全部特性的儲存。

2.1 save函式

Save函式儲存當前畫布的matrix,clip等資訊到一個私有棧中,之後呼叫translate,scale,rotate,skew,concat or clipRect等操作,當呼叫restore或者resoreToCount()函式後,save之後對canvas做的操作將被拋棄,會從canvas狀態棧中取出畫布的狀態資訊進行恢復。返回getSaveCount的值,沒有呼叫過一次save,getSaveCount值為1。

getSaveCount() ;
獲取棧中儲存的狀態資訊的個數,計算方式等於save()的次數減去restore的次數。

呼叫save函式之後會返回一個saveId,表示應該退棧的位置。

2.2 saveLayer

Save和saveLayer還有一些過載函式,save和savelayer的帶saveFlag引數的函式都標註deprecated,所以就不像其他文章一樣進行講解了,用的也不多。
在這裡插入圖片描述

saveLayerAlpha(@Nullable RectF bounds, int alpha) 
saveLayerAlpha(float left, float top, float
right, float bottom, int alpha) saveLayer(float left, float top, float right, float bottom, @Nullable Paint paint) saveLayer(@Nullable RectF bounds, @Nullable Paint paint)

savelayer和saveLayerAlpha函式呼叫時會生成一個新的bitmap用於繪製,後續的操作都不會對原來的Canvas造成影響。呼叫restore或者resoreToCount()函式之後,新生成的bitmap最終會繪製到Canvas對應的原始Bitmap上,也會從canvas狀態棧中獲取狀態資訊,對canvas進行恢復。返回getSaveCount的值,沒有呼叫過一次save,getSaveCount值為1。

saveLayerAlpha和saveLayer的區別只是saveLayerAlpha指定了新生成的bitmap的透明度,所以後面不會過多講解saveLayerAlpha

2.3 restore,restoreToCount

restore函式:清除當前畫布的matrix/clip狀態資訊,然後從棧頂取出儲存的狀態資訊應用到畫布,呼叫restore的次數不能超過save的次數。

Save(): 會把當前的畫布的狀態進行儲存,然後放入Canvas狀態棧中;
saveLayer()或者saveLayerAlpha: 會把當前畫布狀態儲存放入棧中,會新建一個bitmap,後續的操作都會作用在這個bitmap上,同時可以指定新建bitmap的大小和透明度等。
restore()或restoreToCount: 就會把棧中最頂層的畫布狀態取出來,並按照這個狀態恢復當前的畫布,並在這個畫布上做畫。

saveLayer函式如果rectF為null,有一段註釋:
May be null. The maximum size the offscreen render target needs to be (in local coordinates)
如果為null,生成的Bitmap的最大大小將和渲染目標需要大小一樣,然而我去拿saveLayer後的canvas的大小是沒有變化的,哪位同仁知道這個大小是多少。

推薦使用save,因為save不會新建立bitmap,saveLayer會建立新的bitmap,如果建立的bitmap過大會導致記憶體洩漏,這點在savelayer函式上有說明,如果一定要使用saveLayer一定要給出確定的大小,防止記憶體洩漏。

所有的save,saveLayer系列函式都有返回值,返回的是restoreToCount(),也就是呼叫了save次數。

沒有呼叫任何一次save時的canvas.getSaveCount()的值為1。save,saveLayer,savelayeralpha儲存畫布資訊共用一個棧,所以每次呼叫save函式getSaveCount函式都會加一,每次呼叫restore函式getSaveCount函式都會減一。呼叫restoreToCount(id)則會直接退棧到id標識的canvas狀態,此時在其頂部儲存的狀態資訊都已經被彈棧了。

多次呼叫save函式,可以多次進行restore恢復,restore之後進行繪製,會在當前狀態canvas畫布上進行繪製,受當前Canvas狀態的影響。

重要:
呼叫save或者saveLayer系列函式是有返回值的,這個返回值就可以作為restoreToCount的函式實參,可以返回到儲存之前的畫布狀態。

例如呼叫save或者saveLayer後返回saveId為2,那麼現在getSaveCount的值應該為3,此時直接呼叫restoreToCount(2),就可以返回呼叫save或者saveLayer之前的狀態,而且可以儲存這個獲取到的saveId值,在特定的位置利用restoreToCount(saveId),就可以回到生成這個saveId之前的狀態。

總結:
restore ,restoreToCount兩個函式都是用於恢復畫布,restore直接取儲存在棧中的棧頂的畫布狀態進行恢復,restoreToCount:是對restore的封裝,可以直接彈棧直到目標位置的畫布狀態,當saveCount小於1時會報錯。

3程式碼示例

canvas.drawRect(200,200,700,700,mPaint1);
mPaint1.setColor(Color.GRAY);
Matrix matrix = new Matrix();
matrix.setTranslate(100,100);
canvas.setMatrix(matrix);
canvas.drawRect(200,200,700,700,mPaint1);
canvas.drawRect(0,0,100,100,mPaint1);

最後一句想在原點繪製圖形,無法回到繪製第一個矩形時的原點。
在這裡插入圖片描述

呼叫了save,restore。

canvas.drawRect(200,200,700,700,mPaint1);
canvas.save();
mPaint1.setColor(Color.GRAY);
Matrix matrix = new Matrix();
matrix.setTranslate(100,100);
canvas.setMatrix(matrix);
canvas.drawRect(200,200,700,700,mPaint1);
canvas.restore();
canvas.drawRect(0,0,100,100,mPaint1);

在這裡插入圖片描述

saveLayer(@Nullable RectF bounds, @Nullable Paint paint)
saveLayer(float left, float top, float right, float bottom, @Nullable Paint paint)
Paint可以不傳。
生成一個(0,0,100,100)的bitmap

canvas.drawRect(200,200,700,700,mPaint1);
canvas.saveLayer(0,0,100,100,mPaint1);
mPaint1.setColor(Color.GRAY);
Matrix matrix = new Matrix();
matrix.setTranslate(100,100);
canvas.setMatrix(matrix);
canvas.drawRect(200,200,700,700,mPaint1);//繪製不上,因為不在生成的bitmap範圍
canvas.restore();
canvas.drawRect(0,0,100,100,mPaint1);

在這裡插入圖片描述

可以繪製上的版本。

canvas.drawRect(200,200,700,700,mPaint1);
canvas.saveLayer(0,0,700,700,mPaint1);
mPaint1.setColor(Color.GRAY);
Matrix matrix = new Matrix();
matrix.setTranslate(100,100);
canvas.setMatrix(matrix);
canvas.drawRect(200,200,700,700,mPaint1);//由於translate的原因,繪製不全
canvas.restore();
canvas.drawRect(0,0,100,100,mPaint1);

在這裡插入圖片描述

** getSaveCount:**

canvas.drawRect(200,200,700,700,mPaint1);
System.out.println("==========daxiao11111=========="+canvas.getSaveCount());
//canvas.saveLayer(0,0,700,700,mPaint1);
canvas.saveLayer(null,mPaint1);
System.out.println("==========daxiao222========="+canvas.getSaveCount());
mPaint1.setColor(Color.GRAY);
Matrix matrix = new Matrix();
matrix.setTranslate(100,100);
canvas.setMatrix(matrix);
canvas.drawRect(200,200,700,700,mPaint1);
canvas.restore();
System.out.println("==========daxiao333========="+canvas.getSaveCount());
canvas.drawRect(0,0,100,100,mPaint1);

canvas.saveLayer(100, 100, 200, 200, mPaint1);
canvas.drawRect(0,0,100,100,mPaint1);
canvas.restore();

result:
==daxiao11111=1
==daxiao222=2
==daxiao333=1

多次save,多次restore:

  canvas.save();//第一次儲存
        canvas.translate(200,200);
        canvas.save();// 儲存第二次
        canvas.translate(200,200);
        canvas.save();//第三次儲存
        canvas.translate(200,200);
        canvas.save();//第四次儲存
        canvas.translate(200,200);
        canvas.save();//第五次儲存
        canvas.translate(200,200);
        canvas.save();

        mPaint1.setColor(Color.RED);
        canvas.drawRect(0,0,100,100,mPaint1);

        canvas.restore();
        mPaint1.setColor(Color.YELLOW);
        canvas.drawRect(0,0,100,100,mPaint1);

        canvas.restore();
        mPaint1.setColor(Color.GREEN);
        canvas.drawRect(0,0,100,100,mPaint1);

        canvas.restore();
        mPaint1.setColor(Color.BLUE);
        canvas.drawRect(0,0,100,100,mPaint1);

        canvas.restore();
        mPaint1.setColor(Color.GRAY);
        canvas.drawRect(0,0,100,100,mPaint1);

        canvas.restore();
        mPaint1.setColor(Color.BLACK);
        canvas.drawRect(0,0,100,100,mPaint1);

在這裡插入圖片描述

可以看到,每次restore,恢復之前的canvas畫布狀態。

restoreToCount
上面的例子,如果restore一次,如何畫藍色矩形。

  canvas.save();//第一次儲存,count2
        canvas.translate(200,200);
        canvas.save();// 儲存第二次,count3
        canvas.translate(200,200);
        canvas.save();//第三次儲存
        canvas.translate(200,200);
        canvas.save();//第四次儲存
        canvas.translate(200,200);
        canvas.save();//第五次儲存
        canvas.translate(200,200);
        canvas.save();
        //直接4,就可以回到畫藍色矩形位置
        canvas.restoreToCount(4);
        mPaint1.setColor(Color.BLUE);
        canvas.drawRect(0,0,100,100,mPaint1);

在這裡插入圖片描述

總結:

  • 所有的save函式(save,saveLayer,saveLayerAlpha)公用一個棧,每次save函式會把Canvas狀態資訊儲存在棧頂
  • 進入onDraw沒有呼叫save函式前getSaveCount值為1,
  • restore 每次取棧頂Cavas狀態
  • restoreToCount(id) 直接出棧id之前的所有狀態,恢復呼叫save或者saveLayer返回id之前的對應棧中Canvas的狀態
  • saveLayer和saveLayerAlpha會生成一個新的Bitmap,可以指定大小和透明度,但可能引起記憶體洩漏要控制好生成bitmap的大小。
  • 多次save可以多次restore,呼叫restore的次數不能超過save的次數

4 狀態儲存saveFlag

雖然 帶有saveFlag的函式都標記了過時,但是還是可以使用的,下面簡介各個flag的作用:

ALL_SAVE_FLAG
儲存全部的狀態,預設的save行為,可以用於save,saveLayer函式。

CLIP_SAVE_FLAG
儲存裁剪的某個區域的狀態,只儲存尺寸狀態,不儲存matrix,所以呼叫restore恢復後,旋轉都操作依然影響canvas,clip操作不影響。可以用於save,saveLayer函式。

MATRIX_SAVE_FLAG
Matrix資訊(translate,rotate,scale,skew)的狀態儲存,不儲存尺寸資訊,呼叫restore後,clip依然影響畫布,matrix操作則不影響畫布。可以用於save,saveLayer函式。

後面的三個flag只作用於saveLayer函式
CLIP_TO_LAYER_SAVE_FLAG
儲存預先設定的範圍裡的狀態

FULL_COLOR_LAYER_SAVE_FLAG
儲存彩色塗層

HAS_ALPHA_LAYER_SAVE_FLAG
不透明圖層儲存