💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[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)