Canvas详解

2020-07-04  本文已影响0人  echoSuny

Canvas,称之为画布,在Android自定义view中十分重要,几乎所有的view都是在Canvas上进行绘制的。

画布与图层的概念

Canvas的获取方式

1 重写onDraw()或dispatchDraw()

在自定义view时一般都需要重写onDraw()或者dispatchDraw()方法。在这两个方法参数中都有一个Canvas对象。这个Canvas对象是view中的Canvas对象,利用这个Canvas对象绘图,效果会直接反应在view中。

2 利用Bitmap创建

直接只用构造函数

public Canvas(Bitmap bitmap) { }

或者

Canvas canvas = new Canvas();
canvas.setBitmap(bitmap);

最后一种则是调用SurfaceHolder.lockCanvas()函数来获得Canvas对象。

drawXXX

canvas有很多绘制的函数,都是以draw开头,并且有很多功能都是一样的,只不过传的参数不同罢了。不过可以大致分为以下几种:

Canvas变换

除了上面的绘制函数之外,还可以对画布进行一些操作,如平移,旋转,缩放,裁剪等

平移 translate

要想对Canvas进行平移,需要使用其中的translate()函数。既然要进行平移,肯定要有一个原点作为参考点。默认的左上角为原点(0 , 0)。x轴向右为正方向,y轴向下为正方向。

  override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        val paint1 = Paint()
        paint1.apply {
            color = Color.GREEN
            style = Paint.Style.STROKE
            strokeWidth = 8f
        }

        canvas.drawRect(0f, 0f, 400f, 300f, paint1)

        canvas.translate(100f, 100f)

        val paint2 = Paint()
        paint2.apply {
            color = Color.RED
            style = Paint.Style.STROKE
            strokeWidth = 8f
        }
        canvas.drawRect(0f, 0f, 400f, 300f, paint2)
    }

上面代码中显示构建了一个绿色的画笔,然后画了一个矩形。接着把画布的原点平移到了(100,100),然后构建一个红色的画笔,并绘制了一个一样大小的矩形。根据绘制出来的图形我们可以得到的结果有:

旋转 rotate
        val paint1 = Paint()
        paint1.apply {
            color = Color.GREEN
            style = Paint.Style.STROKE
            strokeWidth = 8f
        }

        canvas.drawRect(300f, 200f, 700f, 500f, paint1)

        canvas.rotate(20f)

        val paint2 = Paint()
        paint2.apply {
            color = Color.RED
            style = Paint.Style.STROKE
            strokeWidth = 8f
        }
        canvas.drawRect(300f, 200f, 700f, 500f, paint2)

需要注意的是,如果不指定旋转中心点的话,默认还是原点。

缩放 scale
        val paint1 = Paint()
        paint1.apply {
            color = Color.GREEN
            style = Paint.Style.STROKE
            strokeWidth = 8f
        }

        canvas.drawRect(300f, 200f, 700f, 500f, paint1)

        canvas.scale(1f,0.5f)

        val paint2 = Paint()
        paint2.apply {
            color = Color.RED
            style = Paint.Style.STROKE
            strokeWidth = 8f
        }
        canvas.drawRect(300f, 200f, 700f, 500f, paint2)

x轴方向保持不变,y轴上缩放百分之50


扭曲 skew

        val paint1 = Paint()
        paint1.apply {
            color = Color.GREEN
            style = Paint.Style.STROKE
            strokeWidth = 8f
        }

        canvas.drawRect(300f, 200f, 700f, 500f, paint1)

        canvas.skew(0.6f,0f)

        val paint2 = Paint()
        paint2.apply {
            color = Color.RED
            style = Paint.Style.STROKE
            strokeWidth = 8f
        }
        canvas.drawRect(300f, 200f, 700f, 500f, paint2)

skew()里的两个参数分别为角度的正切值。例如在一个角为30度的直角三角形中,30度角对应的直角边长度为斜边的一半。假设30度对应的直角边长度为1,那么斜边就为2,根据勾股定理则可以求出另外一条边长的平方为3,那么开方之后则约为1.732。那么对应的三十度的正切值就为对边1比上邻边1.732,约等于0.57。上面代码中则直接给了0.6,相当于把画布在x轴方向上倾斜了30度左右。


image.png

图层与画布

public int saveLayer(RectF bounds, Paint paint) { }
public int saveLayer(float left, float top, float right, float bottom, Paint paint, int saveFlags) { }

这两个函数的功能是一样的,都是为了保存指定矩形区域的Canvas的内容,只不过参数不同。其中saveFlags有很多种,包括:

    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.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
        // 绘制源图像 源图像是一个正方形 原点为目标图像的圆心
        canvas.drawBitmap(srcBitmap, 400f / 2, 400f / 2, paint)
        paint.xfermode = null
        // 恢复画布
        canvas.restoreToCount(layerId)
    }

下面把canvas.saveLayer()和canvas.restoreToCount()两行注释掉,再来看一下效果:


这其中的区别就是因为调用saveLayer()之后会创建一个新的透明画布,并在这个画布上绘制。于是根据代码来看,紧接着就会绘制一个黄色的圆在这个透明的画布上。(由于绿色是在saveLayer()之前绘制的,所以这个透明画布上只有一个黄色的圆)。下面使用了Xfermode来绘制源图像。由于Xfermode的存在,会把之前画布上的所有内容作为源图像。而之前的画布正是调用saveLayer()生成的透明画布,此时上面有一个黄色的圆。而每次调用canvas的drawXXX()系列函数的时候,都会创建一个新的图层进行绘制,绘制完了之后又会叠加到最近的画布上。因为Xfermode使用了SRC_IN的算法(SRC_IN算法是以显示源图像为主,当源图像和目标图像相交时,利用目标图像的透明度来改变源图像的透明度。如果目标图像透明度为0时,原图像就完全不显时),而除了圆形之外都是透明的,所以源图像正方形除了和圆相交的部分都变成透明的了。整个流程大概是这样的:



不使用saveLayer()的大概过程是这样的:



与saveLayer()类似的还有一个saveLayerAlpha()函数。区别就是后者带有透明度。

未完待续。。。

上一篇 下一篇

猜你喜欢

热点阅读