1、Canvas
1.1、save()
为了实现一些效果不得不对画布进行操作,但是操作完了,画布的状态也改变了,这会严重影响后面的画图操作。如果能对画布的大小和状态(旋转角度、扭曲)
进行实时保存和恢复就好了,这时就可以使用Canvas.save()
对画布的状态进行保存,将其放入特定的栈中。注意:使用save()并不会创建新的画布,只是保存画布的状态而已。
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawColor(Color.RED)
//保存当前画布大小,即整屏
canvas.save()
canvas.clipRect(Rect(100, 100, 800, 800))
canvas.drawColor(Color.GREEN)
//恢复整屏画布
canvas.restore()
canvas.drawColor(Color.BLUE)
}
在上面例子中,我们先将画布填充为红色,然后保存画布的状态。然后对画布进行裁剪并填充为绿色,然后还原画布,将其填充为蓝色。整个过程如下:
image.png
1.2、saveLayer()
saveLayer有两个构造函数
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 bounds:要保存的区域所对应的矩形
- int saveFlags::取值有 ALL_SAVE FLAG、MATRIX_SAVE FLAG、CLIP_SAVE FLAG、HAS_ALPHA_LAYER_SAVE FLAG、FULL_COLOR_LAYER_SAVE_FLAG 和 CLIP_TO_LAYER_SAVE_FLAG ,其中 ALL_SAVE_FLAG表示保存全部内容。
下面以Xfermode为例,来看看saveLayer()函数做了什么
class XfermodeView(context: Context, attributeSet: AttributeSet) : View(context, attributeSet) {
private val paint = Paint()
private val bitmapWidth = 400
private val bitmapHeight = 400
private val srcBitmap = createSrcBitmap()
private val dstBitmap = createDstBitmap()
init {
setLayerType(LAYER_TYPE_SOFTWARE, null)
paint.style = Paint.Style.FILL
}
private fun createSrcBitmap(): Bitmap {
val srcBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888)
paint.setColor(Color.BLUE)
val canvas = Canvas(srcBitmap)
canvas.drawRect(Rect(0, 0, bitmapWidth, bitmapHeight), paint)
return srcBitmap
}
private fun createDstBitmap(): Bitmap {
val dstBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888)
paint.setColor(Color.YELLOW)
val canvas = Canvas(dstBitmap)
canvas.drawOval(RectF(0f, 0f, bitmapWidth.toFloat(), bitmapHeight.toFloat()), paint)
return dstBitmap
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawColor(Color.GREEN)
val layerId =
canvas.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), null, Canvas.ALL_SAVE_FLAG)
canvas.drawBitmap(dstBitmap, 0f, 0f, paint)
paint.setXfermode(PorterDuffXfermode(PorterDuff.Mode.SRC_IN))
canvas.drawBitmap(
srcBitmap,
(dstBitmap.width * 1.0f) / 2,
(dstBitmap.height * 1.0f) / 2,
paint
)
paint.setXfermode(null)
canvas.restoreToCount(layerId)
}
}
从上面代码可以看到,在saveLayer()前,我们先将整个画布填充为了绿色,如下图
image.png
如果把saveLayer()去掉,效果图如下
image.png
可以看到这个效果是不正确的,这又是问什么呢?这就要从saveLayer()的作用说起。
1.2.1、saveLayer()的作用
saveLayer()函数被调用后,会生成一个全透明的画布,画布大小就是我们指定的RectF矩形区域的大小。在调用saveLayer()之后所有的绘图操作都是在这个新生成的画布上进行的。
而使用Xfermode进行图像混合时,在设置Xfermode之前,会把之前画布上所有的图像作为目标图像,而在调用saveLayer()时,新生成的画布上只有一个圆形图像,其他部分为透明的,所以除与圆形相交的区域都是空白像素。
如果没有调用saveLayer()的话,在设置Xfermode前,画布先是被填充了绿色,然后使用canvas.drawBitmap()生成了一个新的透明图层
,在图层上绘制了一个黄色圆形。所以在绘制源图像时,目标图像是没有透明像素的,所以源图像也全部显示了。
1.2.2、saveLayer()和save()的区别
如果把上面例子的中的saveLayer()方法替换成了save()呢?
效果如下图
发现和去掉saveLayer()的效果完全一样。
这也说明了save()并没有新建画布,它只是保存当前画布的状态而已
2、画布与图层
- 图层:每次调用Canvas.drawXXX()函数都会生成一个透明度层来绘制这个图形。
- 画布:每块画布都是一个Bitmap,所有的图像都是画在这个Bitmap上的。而调用Canvas.drawXXX()会生成一个透明图层,然后在这个图层上绘制图形,绘制完成后,就覆盖在画布上。所以如果我们连续调用5次draw函数就会生成5个透明图层,画完之后依次覆盖在画布上显示。
画布分为两种:一种是View的原始画布,通过onDraw(Canvas canvas)函数传入的,一种是人造画布,通过saveLayer()、new Canvas(Bitmap bitmap)等函数,人为的新建一个画布。尤其是saveLayer(),一旦调用,以后所有drawXXX()所画的图像都是在这个画布上,只有调用restore()、restoreToCount()之后,才会回到原画布上进行绘制。
3、saveLayer()和saveLayerAlpha()
3.1、saveLayer()
- 1、saveLayer()会创建一个新的透明的画布,在调用saveLayer()后的任何对画布的操作都是针对新的画布的,并不会对之前的画布有影响。
- 2、通过Rect()指定新建画布的大小
有一点需要注意:我们应该按需指定新建画布的大小,并不需要每次都新建一块屏幕大小的画布,因为这样可能会造成不必要的内存浪费,而且还有可能造成内存溢出。比如:分辨率为1024X768的机器,一个像素占8个字节,则新建一个屏幕大小的画布所占内存为1024X768X8=6.2MB,所以在新建画布时,需要按需创建。
3.1、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,这个也很好理解,就是创建一个带有透明度的画布,取值范围[0~255]。
4、restore()和restoreToCount()
这两个函数针对的是同一个栈,无论是save()还是saveLayer()保存画布使用的都是同一个栈,所以完全可以通用,不同的是restore()默认退出栈顶的画布,还原状态,而restoreToCount(int count)则表示一直退栈,直到把指定的索引的画布退出即可。
网友评论