您的位置:首页 > 其它

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

2016-07-08 16:55 495 查看
来我的怀里

或者

让我住进你的心里

一仓央嘉措


一、什么是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);




三、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个圆重合:



实际效果是这样的:



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

引用
启航
大神的话说:

这是由于屏幕显示与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度,最后绘制旋转后的绿色直线。



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

缩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)的圆圈。



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);




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

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




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 的同学就知道取的所选区域的反向。

效果图:



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);


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



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

7、图像混合 (PorterDuffXfermode)

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

public PorterDuffXfermode(PorterDuff.Mode mode)


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

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

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

效果图:



/**
* @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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: