一、和Canvas
保存相關的標志
在了解Canvas
的保存之前半沽,我們先看一下和保存相關的標志的定義飒赃,它們定義了保存的類型,這些標志定義在Canvas.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
位分別對應:
-
xxxx1
:保存Matrix
信息,例如平移谦炬、旋轉悦屏、縮放、傾斜等键思。 -
xxx1x
:保存Clip
信息础爬,也就是裁剪。 -
xx1xx
:保存Alpha
信息吼鳞。 -
x1xxx
:保存8
位的顏色信息看蚜。 -
1xxxx
:Clip drawing to the bounds of the offscreen layer
,不太明白是什么意思赔桌。
如果需要多選以上的幾個信息進行保存供炎,那么對多個標志位執(zhí)行或操作即可渴逻。
二、save()
和save(int saveFlags)
下面是這兩個方法的定義:
/**
* 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()
之前的所有設置穴吹。此外還可以獲得以下信息:
- 這兩個方法最終都調用
native_save
方法,而無參方法save()
默認是保存Matrix
和Clip
這兩個信息嗜侮。 - 如果允許港令,那么盡量使用無參的
save()
方法,而不是使用有參的save(int saveFlags)
方法傳入別的Flag
锈颗。 - 該方法的返回值顷霹,對應的是在堆棧中的
index
,之后可以在restoreToCount(int saveCount)
中傳入它來講在它之上的所有保存圖層都出棧击吱。 - 所有的操作都是調用了
native_save
來對這個mNativeCanvasWrapper
變量淋淀,我們會發(fā)現,所有對于Canvas
的操作覆醇,其實最終都是操作了mNativeCanvasWrapper
這個對象朵纷。 - 從
XXX_SAVE_FLAG
的命名來看,帶有參數的save(int saveFlags)
方法只允許保存MATRIX_/CLIP_/ALL_
這三種狀態(tài)永脓,而HAS_ALPHA_LAYER/FULL_COLOR_LAYER_/CLIP_TO_LAYER_
這三種狀態(tài)袍辞,則是為后面的saveLayer/saveLayerAlpha
提供的。
三常摧、restore()
restoreToCount(int count)
getSaveCount()
這三個方法用來恢復圖層信息搅吁,也就是將之前保存到棧中的元素出棧,我們看一下這幾個方法的定義:
/**
* 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);
}
這個幾個方法很好理解:
-
restore()
方法用來恢復落午,最近一次調用save()
之前的Matrix/Clip
信息谎懦,如果調用restore()
的次數大于save()
的一次,也就是棧中已經沒有元素溃斋,那么會拋出異常界拦。 -
getSaveCount()
:返回的是當前棧中元素的數量。 -
restoreToCount(int count)
會將saveCount()
之上對應的所有元素都出棧盐类,如果count < 1
寞奸,那么會拋出異常呛谜。 - 它們最終都是調用了
native
的方法。
四枪萄、示例
下面隐岛,我們用一個簡單的例子,來更加直觀的了解一下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);
}
我們對畫布分別進行了平移聚凹、縮放、旋轉齐帚、傾斜妒牙,得到的結果為:
可以看到,當我們調用上面這些方法時对妄,其實就是對Canvas
先進行了一些移動湘今,旋轉,縮放的操作剪菱,然后再在它這個新的狀態(tài)上進行繪制摩瞎,之后調用restore()
之后,又恢復回了調用save()
之前的狀態(tài)孝常。
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
上蓋了這么一個鏤空的板子,鏤空部分就是我們定義的裁剪區(qū)域稿茉,當我們進行繪制時锹锰,就是在這個板子上面進行繪制,而最終在canvas
上展現的部分就是這些鏤空部分和之后繪制部分的交集漓库。 - 之后我們嘗試以
(0, 0)
為原點繪制一個大小為200px * 200px
的紅色矩形恃慧,但是此時由于(0, 0) - (150, 200)
這部分被蓋住了,所以不我們畫不上去渺蒿,實際畫上去的只有(50, 0) - (200, 200)
這一部分痢士。 - 之后調用了
restore()
方法,就相當于把板子拿掉茂装,那么這時候就能夠像之前那樣正常的繪制了怠蹂。
五善延、saveLayer
saveLayerAlpha
5.1 方法定義
除了save()
方法之外,canvas
還提供了saveLayer
方法
以上的這八個方法可以分為兩個大類:
saveLayer
和saveLayerAlpha
城侧,它們最終都是調用了兩個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()
方法的區(qū)別
關于save()
和saveLayer()
的區(qū)別,源碼當中是這么解釋的炮温,也就是說它會新建一個不在屏幕之內的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).
再回頭看下方法的參數漾脂,這兩大類方法分別傳入了Paint
和Alpha
這兩個變量假颇,對于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
之后激涤,創(chuàng)建了一個透明的圖層,之后在調用restore()
方法之前判呕,我們都是在這個圖層上面進行操作倦踢,而save
方法則是直接在原先的圖層上面操作送滞,那么對于某些操作,我們不希望原來圖層的狀態(tài)影響到它辱挥,那么我們應該使用saveLayer
累澡。
六、saveLayer
示例
和前面類似般贼,我們創(chuàng)建一個繼承于View
的SaveLayerView
愧哟,并重寫onDraw(Canvas canvas)
方法:
6.1 驗證創(chuàng)建新的圖層理論
首先,因為我們前面整個一整節(jié)得到的結論是saveLayer
會創(chuàng)建一個新的圖層哼蛆,而驗證是否產生新圖層的方式就是采用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
會創(chuàng)建一個新的圖層跌前。
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_XXX
和FULL_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
。