1. 程式人生 > >android canvas layer (圖層)詳解與進階

android canvas layer (圖層)詳解與進階

1 概述

前面的canvas變換文章中,已經粗略的講解過saveLayer的知識,只是圖層的概念沒有詳細的講解。這裡將詳細講解layer。在使用相關方法和flag的時候,先關閉硬體加速。如果需要開啟,參照谷歌官方的硬體加速表格。硬體加速版本

2 saveLayer

saveLayer可以為canvas建立一個新的透明圖層,在新的圖層上繪製,並不會直接繪製到螢幕上,而會在restore之後,繪製到上一個圖層或者螢幕上(如果沒有上一個圖層)。為什麼會需要一個新的圖層,例如在處理xfermode的時候,原canvas上的圖(包括背景)會影響src和dst的合成,這個時候,使用一個新的透明圖層是一個很好的選擇。又例如需要當前繪製的圖形都帶有一定的透明度,那麼建立一個帶有透明度的圖層,也是一個方便的選擇。

public int saveLayer(RectF bounds, Paint paint, int saveFlags)  
public int saveLayer(float left, float top, float right, float bottom,Paint paint, int saveFlags)  

上下兩個方法都差不多,只是第一個方法接收的是RectF,第二個是座標。都是指定Layer的大小和範圍的。
saveFlags:代表了需要儲存哪方面的內容,這裡一共有6種取值,分別是

MATRIX_SAVE_FLAG,
CLIP_SAVE_FLAG,
HAS_ALPHA_LAYER_SAVE_FLAG,
FULL_COLOR_LAYER_SAVE_FLAG,
CLIP_TO_LAYER_SAVE_,
ALL_SAVE_FLAG。//儲存全部

saveLayer的作用我們在之前的canvas變換中其實已經講解過一部分,這裡我們再看一下:

saveLayer saveLayer

上圖中,綠色是canvas背景,dst是一個黃色圓形,src為一個藍色正方形,xfermode為xor,可以看到,左邊圖形合成並不正常,原因就在於沒有新開一個圖層,而是直接在canvas上合成,受了背景顏色的影響,背景和dst這個黃色圓形一併被當作了dst,所以xor就成了這個影象,中間的正方形被除去了。
而右邊的圖中,新開了一個透明圖層,因此,dst和src和合成沒有受到其他干擾。合併之後正常顯示。

程式碼如下:

canvas.drawColor(Color.GREEN);

//        canvas.saveLayer(0, 0, 1000, 1000, paint, Canvas.ALL_SAVE_FLAG);
canvas.translate(x, y);
canvas.drawBitmap(mDstB, 0, 0, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));
canvas.drawBitmap(mSrcB, 0, 0, paint);
paint.setXfermode(null);
canvas.restore();

其中左圖沒有開啟註釋程式碼,右圖打開了註釋程式碼。

從上面可以看出來,canvas呼叫saveLayer之後,開啟了一個新的透明圖層。繪製完成後再合併到上一個圖層上。

3 saveLayerAlpha

該方法可以開啟一個帶有透明度的圖層,上面繪製的影象都會帶有透明度,這樣在需要繪製有透明度的圖形時比較方便。

public int saveLayerAlpha(RectF bounds, int alpha, int saveFlags)  
public int saveLayerAlpha(float left, float top, float right, float bottom,int alpha, int saveFlags)  

比上面的saveLayer方法多了一個alpha引數,也比較好理解。
alpha引數的取值是0到255
看一個示例圖片:

這裡寫圖片描述

可以看到,紅色的圓圈有透明度,這裡開啟了一個帶有一定透明度的圖層。

paint.setColor(Color.BLUE);
canvas.drawCircle(100, 100, 100, paint);

paint.setColor(Color.RED);
canvas.saveLayerAlpha(100, 100, 300, 300, 120, Canvas.ALL_SAVE_FLAG);
canvas.drawCircle(200, 200, 100, paint);
canvas.restore();

4 saveFlags

上面我們只是粗暴的使用了ALL_SAVE_FLAG來儲存的所有的資訊,但是實際使用中,所有資訊都儲存必然增加了開銷,所以,我們應該根據需要的動作,儘量的精確的儲存少量的資訊。這裡就需要了解各個flag的意義。

首先需要知道的是,使用flag的方法除了saveFlayer還有save方法,他們都可以使用flag來指定需要儲存的資訊。那麼來看看6中flag所對應的意義:

Flag 意義 適用方法
MATRIX_SAVE_FLAG 只儲存圖層的matrix矩陣 save,saveLayer
CLIP_SAVE_FLAG 只儲存大小資訊 save,saveLayer
HAS_ALPHA_LAYER_SAVE_FLAG 表明該圖層有透明度,和下面的標識衝突,都設定時以下面的標誌為準 saveLayer
FULL_COLOR_LAYER_SAVE_FLAG 完全保留該圖層顏色(和上一圖層合併時,清空上一圖層的重疊區域,保留該圖層的顏色) saveLayer
CLIP_TO_LAYER_SAVE_ 建立圖層時,會把canvas(所有圖層)裁剪到引數指定的範圍,如果省略這個flag將導致圖層開銷巨大(實際上圖層沒有裁剪,與原圖層一樣大)
ALL_SAVE_FLAG 儲存所有資訊 save,saveLayer
(1) MATRIX_SAVE_FLAG

只儲存圖層的matrix矩陣。
canvas中的哪些方法是利用matrix完成的,這裡需要明確,其實我們知道,canvas的繪製,最終是發生在bitmap上的,從canvas的建構函式中也可以看出。在Bitmap的建構函式中可以看出:

Bitmap createBitmap(Bitmap source, int x, int y, int width, int height,Matrix m, boolean filter)

bitmap的操作也是通過matrix來進行的。
那麼我們可以知道canvas的canvas.translate(平移)、canvas.rotate(旋轉)、canvas.scale(縮放)、canvas.skew(扭曲)其實都是通過matrix來達到的,這一點可以在程式碼中使用MATRIX_SAVE_FLAG來進行驗證。

save方法

這裡舉例平移:

這裡寫圖片描述

程式碼如下:

paint.setColor(Color.BLUE);
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.translate(200, 200);
canvas.drawRect(100, 100, 300, 300, paint);
canvas.restore();

paint.setColor(Color.RED);
canvas.drawRect(100, 100, 300, 300, paint);

可以看到平移效果得到了儲存,並且可以恢復。

saveLayer方法

上面看了save方法,這裡看看saveLayer是否有相同效果:

這裡寫圖片描述

圖中看出效果相同,程式碼如下

paint.setColor(Color.BLUE);
int count=canvas.saveLayer(0,0,1000,1000,paint,Canvas.MATRIX_SAVE_FLAG|Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);
canvas.translate(200, 200);
canvas.drawRect(100, 100, 300, 300, paint);
canvas.restoreToCount(count);

paint.setColor(Color.RED);
canvas.drawRect(100, 100, 300, 300, paint);

程式碼基本相同,只是save更換為了saveLayer。

如果這裡不使用MATRIX_SAVE_FLAG標誌位,那麼是否會出現不同的效果呢,使用CLIP_SAVE_FLAG標誌來試試:

這裡寫圖片描述

程式碼如下:

paint.setColor(Color.BLUE);
int count=canvas.saveLayer(0,0,1000,1000,paint,Canvas.CLIP_SAVE_FLAG|Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);
canvas.translate(200, 200);
canvas.drawRect(100, 100, 300, 300, paint);
canvas.restoreToCount(count);

paint.setColor(Color.RED);
canvas.drawRect(100, 100, 300, 300, paint);

程式碼和上面基本相同,只是標誌位改變了,這裡可以看到兩個圖重疊了,也就是說CLIP_SAVE_FLAG標誌位並沒有儲存相關的位移資訊,導致restore的時候沒能恢復。

(2) CLIP_SAVE_FLAG

看了上面的MATRIX_SAVE_FLAG,這裡的意義基本知道,主要就是儲存裁剪相關的資訊。
由於和上面的示例基本類似,這裡就不再做講解了。

(3) FULL_COLOR_LAYER_SAVE_FLAG 和 HAS_ALPHA_LAYER_SAVE_FLAG

這兩個方法是saveLayer專用的方法,HAS_ALPHA_LAYER_SAVE_FLAG為layer新增一個透明通道,這樣一來沒有繪製的地方就是透明的,覆蓋到上一個layer的時候,就會顯示出上一層的影象。而FULL_COLOR_LAYER_SAVE_FLAG 則會完全展示當前layer的影象,清除掉上一層的重合影象。

來看看FULL_COLOR_LAYER_SAVE_FLAG 的示例:

這裡寫圖片描述

程式碼如下:

canvas.drawColor(Color.RED);

canvas.saveLayer(200,200,700,700,mPaint,Canvas.FULL_COLOR_LAYER_SAVE_FLAG);
mPaint.setColor(Color.GREEN);
canvas.drawRect(300,300,600,600,mPaint);
canvas.restore();

可以看到,綠色的方塊周圍有白色的一圈,整個白色加上綠色區域是這個layer的區域,由於這裡使用了FULL_COLOR_LAYER_SAVE_FLAG標誌,所以這塊區域的紅色被layer層完全覆蓋(即使是透明),由於綠色周圍的顏色是透明的,所以在清除了紅色並覆蓋後,就顯示出了activity的背景顏色,所以顯示了白色。

如果activity背景是黑色,這一塊自然變為黑色:

這裡寫圖片描述

那麼其他程式碼不變,只是將標誌位替換成HAS_ALPHA_LAYER_SAVE_FLAG會發生什麼:

這裡寫圖片描述

可以看到,綠色周圍的白色不見了,可見,這就是區別。使用這個標誌位不會清空上一圖層的內容。

(4) CLIP_TO_LAYER_SAVE_

這個標誌比較重要,官方的建議是,最好不要忽略這個標識,這個標識如果不設定將會帶來很大的效能問題。

這個標識的作用是將canvas裁剪到指定的大小,並且無法回覆。看下面一個例子:

這裡寫圖片描述

再看下程式碼:

canvas.drawColor(Color.RED);
canvas.saveLayer(200,200,700,700,mPaint,Canvas.CLIP_TO_LAYER_SAVE_FLAG);
canvas.drawColor(Color.GREEN);
canvas.restore();
canvas.drawColor(Color.BLACK);

這裡看,先將底色繪製為紅色,然後開啟新圖層,再繪製為綠色,最後將canvas繪製為黑色,為什麼最後不是全屏黑色呢,這裡明明restore了,這是因為使用了CLIP_TO_LAYER_SAVE_FLAG標誌,這樣一來,canvas被裁剪了,並且無法回覆了。這樣也就減少了處理的區域,增加了效能。