来我的怀里
或者
让我住进你的心里
一仓央嘉措
一、什么是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 相关的知识,就讲到这里了,如果本文有帮到你,记得加关注哦。