[TOC]
# 1. 前言
在之前我們已經用過很多次關于Canvas的一些常用方法。需要注意的是:
* 每次調用 drawXXX 系列函數來繪圖時,都會產生一個全新的 Canvas 透明圖層。
* 如果在調用 drawXXX 系列函數前,調用平移、旋轉等函數對 Canvas 進行了操作, 那么這個操作是不可逆的。每次產生的畫布的最新位置都是這些操作后的位置。
* 在 Canvas 圖層與屏幕合成時,超出屏幕范圍的圖像是不會顯示出來的。
# 2. 畫布裁剪
裁剪畫布是指利用 clip 系列函數,通過與 Rect、Path、Region 取交、并、差等集合運算 來獲得最新的畫布形狀。除調用 save()、restore()函數以外,這個操作是不可逆的,**一旦 Canvas 被裁剪,就不能恢復。**
**注意**:在使用裁剪畫布系列函數時,需要禁用硬件加速功能。
比如:
```
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.RED);
canvas.clipRect(new Rect(100, 100, 200, 200));
canvas.drawColor(Color.GREEN);
}
```
先把背景色涂成紅色,顯示在屏幕上;然后裁剪畫布;最后將最新的畫布涂成綠色。可 見,綠色部分只有一小塊,而不再是整個屏幕了。注意到前面提到,**一旦 Canvas 被裁剪,就不能恢復。**也就是之后的繪制操作都將只會在裁剪得到的矩形畫布中進行。
# 3. 畫布的保存和恢復
前面介紹的所有對畫布的操作都是不可逆的,這會造成很多麻煩。比如,為了實現一些 效果而不得不對畫布進行操作,但操作完了,畫布狀態也改變了,這會嚴重影響到后面的畫 圖操作。故而在Android中提供了save()和 restore()函數來保存當前畫布狀態。即:
* save():每次調用 save()函數,都會先**保存當前畫布的狀態(但不會新建畫布),然后將其放入特定的棧**中,該方法返回一個int類型的ID,可以用來記錄在棧中的記錄,可以指定恢復畫布狀態。
* restore():每次調用 restore()函數,都會**把棧中頂層的畫布狀態取出來,并按照這個狀 態恢復當前的畫布,然后在這個畫布上作畫**。
* saveLayer(RectF bounds, Paint paint, int saveFlags):保存當前畫布狀態到特定棧,并新建一個空白畫布。該方法返回一個int類型的ID,可以用來記錄在棧中的記錄,可以指定恢復畫布狀態。
* restoreToCount(int saveCount),表示一直退棧,直到把指定索引的畫布信息退出來,之后的棧最上層的畫布信息將作為最新的畫布。
## 3.1 saveFlags
也就是標識。

## 3.1 saveLayer()函數時的繪圖流程
在調用 saveLayer()函數時,會生成一塊全新的畫布(Bitmap),這塊畫布的大小就是我們 指定的所要保存區域的大小。新生成的畫布是全透明的,在調用 saveLayer()函數后所有的繪 圖操作都是在這塊畫布上進行的。
### 3.1.1 Q1:為什么在使用圖像混合模式的時候,通常需要在saveLayer和restoreToCount兩個函數之間?
因為圖像混合模式是就近的進行計算的,而前面提到過,每次調用 canvas.drawXXX 系列函數,都會生成一個透明圖層來專門繪制這個圖形,也就是如果我們繪制了兩個圖像。在《Android自定義控件開發入門與實戰》一書中給了圖示,很直觀,比如:

在通過使用圖像混合模式的時候,就近的兩個圖像進行疊加,也就是上圖中的源圖像和目標圖像進行運算,得到最終的顯示狀態。對應的代碼:
~~~
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.GREEN);
// 保存當前圖層,新建一個圖層
int layerID = canvas.saveLayer(0, 0, width * 2, height * 2, mPaint,
Canvas.ALL_SAVE_FLAG);
// 目標圖像
canvas.drawBitmap(dstBmp, 0, 0, mPaint);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
// 源圖像
canvas.drawBitmap(srcBmp, width / 2, height / 2, mPaint);
// 清空圖像混合模式
mPaint.setXfermode(null);
// 恢復保存的圖層狀態
canvas.restoreToCount(layerID);
}
~~~
注釋:目標圖像在下,需要先繪制。
如果將saveLayer這個代碼注釋,也就是不再新建一個圖層。在去掉 saveLayer()函數后,就不會新建畫布了。也就是目標將繪制在原始畫布上,對應的代碼:
~~~
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.GREEN);
// 目標圖像
canvas.drawBitmap(dstBmp, 0, 0, mPaint);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
// 源圖像
canvas.drawBitmap(srcBmp, width / 2, height / 2, mPaint);
mPaint.setXfermode(null);
}
~~~
圖示:

那么效果就是這兩個圖層的圖像混合運算結果。
### 3.1.2 Q2:關于Bitmap的內存問題
Bitmap 是位圖,也就是由一個個像素點組成的。一張位圖所占用的內存計算為:
> 圖片長度(px)× 圖片寬度(px)× 一個像素點占用的字節數。
存儲一個像素點所使用的字節數是用枚舉類型 Bitmap.Config 中的各個參數來 表示的。比如,Bitmap.Config.ARGB\_8888。也就是8*4=32位,一個像素點對應4個字節。RGB\_565:表示 16 位 RGB 位圖。
在《Android自定義控件開發入門與實戰》一書中提到:

- 介紹
- UI
- MaterialButton
- MaterialButtonToggleGroup
- 字體相關設置
- Material Design
- Toolbar
- 下拉刷新
- 可折疊式標題欄
- 懸浮按鈕
- 滑動菜單DrawerLayout
- NavigationView
- 可交互提示
- CoordinatorLayout
- 卡片式布局
- 搜索框SearchView
- 自定義View
- 簡單封裝單選
- RecyclerView
- xml設置點擊樣式
- adb
- 連接真機
- 小技巧
- 通過字符串ID獲取資源
- 自定義View組件
- 使用系統控件重新組合
- 旋轉菜單
- 輪播圖
- 下拉輸入框
- 自定義VIew
- 圖片組合的開關按鈕
- 自定義ViewPager
- 聯系人快速索引案例
- 使用ListView定義側滑菜單
- 下拉粘黏效果
- 滑動沖突
- 滑動沖突之非同向沖突
- onMeasure
- 繪制字體
- 設置畫筆Paint
- 貝賽爾曲線
- Invalidate和PostInvalidate
- super.onTouchEvent(event)?
- setShadowLayer與陰影效果
- Shader
- ImageView的scaleType屬性
- 漸變
- LinearGradient
- 圖像混合模式
- PorterDuffXfermode
- 橡皮擦效果
- Matrix
- 離屏繪制
- Canvas和圖層
- Canvas簡介
- Canvas中常用操作總結
- Shape
- 圓角屬性
- Android常見動畫
- Android動畫簡介
- View動畫
- 自定義View動畫
- View動畫的特殊使用場景
- LayoutAnimation
- Activity的切換轉場效果
- 屬性動畫
- 幀動畫
- 屬性動畫監聽
- 插值器和估值器
- 工具
- dp和px的轉換
- 獲取屏幕寬高
- JNI
- javah命令
- C和Java相互調用
- WebView
- Android Studio快捷鍵
- Bitmap和Drawable圖像
- Bitmap簡要介紹
- 圖片縮放和裁剪效果
- 創建指定顏色的Bitmap圖像
- Gradle本地倉庫
- Gradle小技巧
- RxJava+Okhttp+Retrofit構建網絡模塊
- 服務器相關配置
- node環境配置
- 3D特效