[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
也就是标识。
![](https://img.kancloud.cn/88/13/8813ea897aa1453eb2aacee41b35b714_1025x486.png)
## 3.1 saveLayer()函数时的绘图流程
在调用 saveLayer()函数时,会生成一块全新的画布(Bitmap),这块画布的大小就是我们 指定的所要保存区域的大小。新生成的画布是全透明的,在调用 saveLayer()函数后所有的绘 图操作都是在这块画布上进行的。
### 3.1.1 Q1:为什么在使用图像混合模式的时候,通常需要在saveLayer和restoreToCount两个函数之间?
因为图像混合模式是就近的进行计算的,而前面提到过,每次调用 canvas.drawXXX 系列函数,都会生成一个透明图层来专门绘制这个图形,也就是如果我们绘制了两个图像。在《Android自定义控件开发入门与实战》一书中给了图示,很直观,比如:
![](https://img.kancloud.cn/d4/14/d41443eaa8d3cc1b5a5e538c17f1afdf_653x367.png)
在通过使用图像混合模式的时候,就近的两个图像进行叠加,也就是上图中的源图像和目标图像进行运算,得到最终的显示状态。对应的代码:
~~~
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);
}
~~~
图示:
![](https://img.kancloud.cn/7f/07/7f078e58902951bd7473cc9647ff1dc3_643x319.png)
那么效果就是这两个图层的图像混合运算结果。
### 3.1.2 Q2:关于Bitmap的内存问题
Bitmap 是位图,也就是由一个个像素点组成的。一张位图所占用的内存计算为:
> 图片长度(px)× 图片宽度(px)× 一个像素点占用的字节数。
存储一个像素点所使用的字节数是用枚举类型 Bitmap.Config 中的各个参数来 表示的。比如,Bitmap.Config.ARGB\_8888。也就是8*4=32位,一个像素点对应4个字节。RGB\_565:表示 16 位 RGB 位图。
在《Android自定义控件开发入门与实战》一书中提到:
![](https://img.kancloud.cn/5c/51/5c51df9b8d3edb0519559f6564ec87b7_1031x332.png)
- 介绍
- 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特效