自定义View之绘图篇(六):Canvas那些你应该知道的变换

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u012551350/article/details/51858309

来我的怀里
或者
让我住进你的心里

                                            一仓央嘉措

一、什么是Canvas?

什么是Canvas?官方文档是这么介绍的:


The Canvas class holds the “draw” calls. To draw something, you need 4 basic components: A Bitmap to hold the pixels, a Canvas to host the draw calls (writing into the bitmap), a drawing primitive (e.g. Rect,Path, text, Bitmap), and a paint (to describe the colors and styles for the drawing).

Canvas 类是用于绘图的,绘制图形,你需要4个基本元素:

  • 画在哪。画在Bitmap上。(相当于纸张,我们把图画在纸张上面)

  • 怎么画。(调用canvas执行绘图操作。比如canvas.drawCircle(),canvas.drawLine(),canvas.drawPath()将我们需要的图像画出来。)

  • 画的内容。(比如我想在纸张画一朵花,根据自己需求画圆,画直线,画路径等)

  • 用什么画。(在纸张上画一朵花,肯定是用笔来画的,这里的笔指的是 Paint)

Canvas 画布无限大,它并没有边界。怎么来理解这句话呢?打个比方:画布就是窗外的景色,而手机屏幕就是窗口,你在窗口看到窗外的景色是有限的。同样我也可以把图形画到屏幕之外,通过对 Canvas 的变换与操作,让屏幕之外的图形显示到屏幕里面。

二、Canvas 绘图

Canvas 绘制一些常见的图形:

        mPaint.setColor(Color.RED);
        //绘制直线
        canvas.drawLine(100,100,600,100,mPaint);

        //绘制矩形
        canvas.drawRect(100,200,600,400,mPaint);

        //绘制路劲
        mPaint.setTextSize(60);
        mPaint.setStrokeWidth(2);
        mPaint.setStyle(Paint.Style.FILL);
        canvas.drawText("我是一颗石头",100,500,mPaint);

can

三、Canvas 的变换与操作

有时候我们还需要对 Canvas 做一些操作,比如旋转,裁剪,平移等等。

  • canvas.translate 平移

  • canvas.rotate 旋转

  • canvas.scale 缩放

  • canvas.skew 错切

  • canvas.clipRect 裁剪

  • canvas.save和canvas.restore 保存和恢复

  • PorterDuffXfermode 图像混合 (paint相关方法)

哟也,我们来挨着看一看。

1、(平移)translate

canvas.translate() 方法是用来实现画布平移的,画布的原状是以左上角为原点,向右是X轴正方向,向下是Y轴正方向,我相信大家已经非常熟悉了。

方法预览:

translate(float dx, float dy)

参数含义:

float dx:水平方向平移的距离,正数指向正方向(向右)平移的量,负数指向负方向(向左)平移的量
flaot dy:垂直方向平移的距离,正数指向正方向(向下)平移的量,负数指向负方向(向上)平移的量

这段代码中,同一个圆形,在画布平移前画一次,平移后再画一次,大家会觉得结果会怎样?


        mPaint.setColor(Color.RED);
        canvas.drawCircle(200,200,200,mPaint);

        mPaint.setColor(Color.GREEN);
        canvas.translate(200,200);
        canvas.drawCircle(200,200,200,mPaint);

你觉得最终的效果图是不是2个圆重合:

can

实际效果是这样的:

can

蛋都碎了一地,为啥红色的框框没有移动呢?

引用启航大神的话说:


这是由于屏幕显示与Canvas根本不是一个概念!Canvas是一个很虚幻的概念,相当于一个透明图层(用过PS的同学应该都知道),每次Canvas画图时(即调用Draw系列函数),都会产生一个透明图层,然后在这个图层上画图,画完之后覆盖在屏幕上显示。

translate 函数其实实际相当于平移坐标系,即平移坐标系的原点的位置。


2、旋转(Rotate)

旋转画布,看起来和图片旋转效果有点类似。默认是围绕坐标系原点旋转,同理也可以设定中心点旋转。

方法预览:

rotate(float degrees)

rotate(float degrees, float px, float py)

第一个构造函数 degrees 参数表示旋转的度数。正数表示顺时针旋转,负数表示逆时针旋转,0 度表示水平X轴方向。默认以坐标原点作为中心点。

第二个构造函数 px , py 表示中心点坐标。

接着我们来看一个例子:

        mPaint.setColor(Color.RED);
        //未旋转的直线
        canvas.drawLine(200, 200, 600, 200,mPaint);
        //顺时针旋转30度
        canvas.rotate(30);
        mPaint.setColor(Color.GREEN);
        canvas.drawLine(200, 200, 600, 200,mPaint);

这里是以第一个构造函数为例的,首先绘制红色的不带旋转的直线,然后顺时针旋转画布30度,最后绘制旋转后的绿色直线。

can

注意:旋转画布和图片旋转之间的区别。


缩3、放(scale )

画布缩放,同样也有2个构造方法。方法预览:

scale(float sx, float sy)

scale(float sx, float sy, float px, float py)

第一个构造函数,参数 sx表示 水平方向的缩放比例,大于1为放大,小于1为缩小。比如 水平方向为100个px,缩放比例为1.5f,那么实际的像素为100*1.5=150px 。 参数 sy表示垂直方向的缩放比例。

第二个方法源码如下:

    /**
     * Preconcat the current matrix with the specified scale.
     *
     * @param sx The amount to scale in X
     * @param sy The amount to scale in Y
     * @param px The x-coord for the pivot point (unchanged by the scale)
     * @param py The y-coord for the pivot point (unchanged by the scale)
     */
    public final void scale(float sx, float sy, float px, float py) {
        translate(px, py);
        scale(sx, sy);
        translate(-px, -py);
    }

px 和 py 分别为缩放的基准点,从源码上可以非常清楚的看出和 scale(float sx , float sy)的差别:

translate(px, py);
scale(sx, sy);
translate(-px, -py);

先将画布平移px,py,然后scale,scale结束之后再将画布平移回原基准点。

具体我们来看看下面例子:

        canvas.save();
        mPaint.setColor(Color.RED);
        //未缩放的圆形
        canvas.drawCircle(200, 200, 100, mPaint);

        //画布缩放
        canvas.scale(1.5f, 1.5f);
        mPaint.setColor(Color.GREEN);
        canvas.drawCircle(200, 200, 100, mPaint);
        canvas.restore();

        canvas.scale(1.5f, 1.5f, 200, 200);
        mPaint.setColor(Color.YELLOW);
        canvas.drawCircle(200, 200, 100, mPaint);

save,restore后面会详解介绍,用于画布的保存和恢复。红色圆圈为未缩放画布,绿色为放大1.5倍的圆圈,黄色为先平移(200,200)再放大1.5倍再平移(-200,-200)的圆圈。

can


4、错切(skew)

画布扭曲的方法预览:

skew(float sx, float sy)

参数:

float sx:将画布在x方向上倾斜相应的角度,sx倾斜角度的tan值
float sy:将画布在y轴方向上倾斜相应的角度,sy为倾斜角度的tan值

下面的例子,我将矩形在 x 方向倾斜30度,tan30=1/√3 约等于 0.56。

        mPaint.setColor(Color.RED);
        //未旋转的圆形
        canvas.drawRect(100, 100, 500, 400, mPaint);

        canvas.skew(0.56f, 0f);
        mPaint.setColor(Color.GREEN);
        canvas.drawRect(100, 100, 500, 400, mPaint);

can

可以从效果图当中看出来,我们设置 x 方向倾斜,反而 y 方向倾斜了,这就是为什么要叫做错切了。你心中一定又会有疑问?每个点的坐标又是怎么计算的呢?那下面我们一起来分析下:

canvas.drawRect(100, 100, 500, 400, mPaint);

can

A(100,100),B(500,100),C(500,400),D(100,400)设置画布 x 方向倾斜0.56f,y 方向没有变化。

A 点横坐标倾斜后的值为 A点的横坐标+A点的横坐标*倾斜值
B 点横坐标倾斜后的值为 B点的横坐标+A点的横坐标*倾斜值
C 点横坐标倾斜后的值为 C点的横坐标+矩形宽度*倾斜值
D 点横坐标倾斜后的值为 D点的横坐标+矩形宽度*倾斜值


5、裁剪画布(clip系列函数)

裁剪画布是利用 Clip系列函数,通过与Rect、Path、Region取交、并、差等集合运算来获得最新的画布形状(默认取交集)。除了调用Save、Restore函数以外,这个操作是不可逆的,Canvas画布一但被裁剪,就不能再被恢复。

Clip系列函数方法预览:

  • clipRect(RectF rect, Op op)

  • clipRect(Rect rect, Op op)

  • clipRect(RectF rect)

  • clipRect(Rect rect)

  • clipRect(float left, float top, float right, float bottom, Op op)

  • clipRect(float left, float top, float right, float bottom)

  • clipRect(int left, int top, int right, int bottom)

  • clipPath(Path path, Op op)

  • clipPath(Path path)

  • clipRegion(Region region, Op op)

  • clipRegion(Region region)

裁剪的函数比较多哈,使用难度都不是很大。来看一个简单的例子:

 canvas.drawColor(Color.GREEN);
 canvas.clipRect(new Rect(200, 200, 500, 400), Region.Op.DIFFERENCE);
 canvas.drawColor(Color.YELLOW);

先把画布背景涂成绿色,然后裁剪矩形,这里去的是差集(Region.Op.DIFFERENCE),用过 ps 的同学就知道取的所选区域的反向。

效果图:

can


6、保存和恢复(save()、restore())

对画布的平移,旋转,缩放,错切,裁剪都是不可逆的操作,如果我们还需要返回到原始状态对画布进行操作怎么办呢?细心的同学肯定知道 save(),restore() 方法在上文已经出现过了,对的,它就是用来对画布状态的保存与恢复,这样我们就可以愉快的进行可逆操作了。

方法预览:

save():每次调用 save() 函数,都会把当前的画布的状态进行保存,然后放入特定的栈中;
restore():每当调用 restore() 函数,就会把栈中最顶层的画布状态取出来,并按照这个状态恢复当前的画布,并在这个画布上做画。

来看一个简单的例子,首先把整个画布涂成绿色,然后保存画布,接着裁剪矩形,给裁剪后的画布涂上黄色,然后恢复画布,给画布涂上红色:

        canvas.drawColor(Color.GREEN);

        //保存当前画布
        canvas.save();

        canvas.clipRect(new Rect(200, 200, 500, 400));

        canvas.drawColor(Color.YELLOW);

        //恢复画布
        canvas.restore();

        canvas.drawColor(Color.RED);

阶段图如下,最终为全屏红色:

can

注意:在使用canvas.save和canvas.restore时最好配对使用,若restore( )的调用次数比save( )多可能会造成异常。


7、图像混合 (PorterDuffXfermode)

PorterDuffXfermode 图像混合的构造函数如下:

public PorterDuffXfermode(PorterDuff.Mode mode)  

参数PorterDuff.Mode表示混合模式,枚举值有18个。

自定义View之绘图篇(五):圆形水波,已经对图形混合有了初步的介绍,我这里就不再重复写了。

我们在项目开发中,经常会用到这样的功能:显示圆角图片。 具体来看看它是怎么实现的:

效果图:

can

    /**
     * @param bitmap 原图
     * @param pixels 圆角大小
     * @return
     */
    public Bitmap getRoundCornerBitmap(Bitmap bitmap, float pixels) {
        //获取bitmap的宽高
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();

        Bitmap cornerBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Paint paint = new Paint();
        Canvas canvas = new Canvas(cornerBitmap);
        paint.setAntiAlias(true);

        canvas.drawRoundRect(new RectF(0, 0, width, height), pixels, pixels, paint);
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        canvas.drawBitmap(bitmap, null, new RectF(0, 0, width, height), paint);

        //绘制边框
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(6);
        paint.setColor(Color.GREEN);
        canvas.drawRoundRect(new RectF(0, 0, width, height), pixels, pixels, paint);

        return cornerBitmap;
    }

1、首先通过Bitmap cornerBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);生成cornerBitmap 实例,注意了Bitmap 只能通过静态方法来获取它的实例,并不能直接 new出来。

2、绘制圆角矩形canvas.drawRoundRect(new RectF(0, 0, width, height), pixels, pixels, paint);

3、为Paint设置PorterDuffXfermode。参数 PorterDuff.Mode.SRC_IN 取交集。

4、绘制原图。canvas.drawBitmap(bitmap, null, new RectF(0, 0, width, height), paint);

5、绘制边框圆角。 canvas.drawRoundRect(new RectF(0, 0, width, height), pixels, pixels, paint);

简单的13行代码就实现了圆角带边框的效果。

Canvas 相关的知识,就讲到这里了,如果本文有帮到你,记得加关注哦

欢迎关注github

展开阅读全文

没有更多推荐了,返回首页