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
不透明圖層儲存