美文网首页
Canvas与图层

Canvas与图层

作者: code希必地 | 来源:发表于2020-12-18 11:47 被阅读0次

    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()呢?
    效果如下图

    image.png
    发现和去掉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)则表示一直退栈,直到把指定的索引的画布退出即可。

    相关文章

      网友评论

          本文标题:Canvas与图层

          本文链接:https://www.haomeiwen.com/subject/gehxnktx.html