前言:当我们进行自定义控件的时候,为了实现更多更炫酷的效果,我们通常都需要在Canvas(画布)
上绘制各种东西。而谷歌也提供了很多的API方法,能让我们更方便更快捷的实现想要的效果。
Canvas
public class Canvas
extends Object
java.lang.Object
↳ android.graphics.Canvas
Canvas
类包含“绘制”调用。要绘制一些东西,你需要4个基本组件:一个用于保存像素的位图,一个用于托管绘图调用的Canvas(写入位图),一个绘图基元(例如Rect,Path,text,Bitmap)和一个paint(描述绘图的颜色和样式)。
在介绍画布方法之前,先简单说下画笔的常用方法,毕竟我们要看到的效果都是用画笔实现的:
reset(): 重置画笔
setColor(int color) :给画笔设置颜色值
setARGB(int a, int r, int g, int b) :同样是设置颜色,但是利用ARGB分开设置
setAlpha(int a): 设置画笔透明度
setStyle(Paint.Style style) :设置画笔样式,取值有
Paint.Style.FILL :填充内部
Paint.Style.FILL_AND_STROKE :填充内部和描边
Paint.Style.STROKE :仅描边
setStrokeWidth(float width) :设置画笔宽度
setAntiAlias(boolean aa): 设置画笔是否抗锯齿
setStrokeCap(Paint.Cap cap) 设置线冒样式,取值有Cap.ROUND(圆形线冒)、Cap.SQUARE(方形线冒)、Paint.Cap.BUTT(无线冒)`
setStrokeJoin(Paint.Join join) :设置线段连接处样式,取值有:Join.MITER(结合处为锐角)、Join.Round(结合处为圆弧)、Join.BEVEL(结合处为直线)
下面介绍下Canvas 的常用方法,先看下总的内容:
一、画布变换
1、translate(位移)
/**
* 画布向(100,100)方向平移
*
* @param dx 向X轴方向平移100px
* @param dy 向Y轴方向平移100px
*/
canvas.translate(100, 100);
看下效果图:
注意
:translate()是可以叠加的,而且这里实际上移动的是坐标,画布的位置并没有变,也就是说坐标原点变到了原来(100,100)的位置
2、rotate(旋转)
/**
* 原点为中心,旋转45度(顺时针方向为正方向 )
*
* @param degrees 旋转角度
*/
canvas.rotate(45);
效果图如下:
旋转还有另外一个方法:canvas.rotate(float degrees, float px, float py);
/**
* Preconcat the current matrix with the specified rotation.
*
* @param degrees The amount to rotate, in degrees
* @param px The x-coord for the pivot point (unchanged by the rotation)
* @param py The y-coord for the pivot point (unchanged by the rotation)
*/
public final void rotate(float degrees, float px, float py) {
if (degrees == 0.0f) return;
translate(px, py);
rotate(degrees);
translate(-px, -py);
}
根据源码可以很明显的看到,其实就是先平移到指定位置,在指定位置进行了旋转之后又对画布做了返回平移处理,这样就实现了以自定义点为中心旋转画布
的效果。
3、scale(缩放)
/**
* 在X轴方向放大为原来2倍,Y轴方向方大为原来的4倍
*
* @param sx X轴的放大倍数
* @param sy Y轴的放大倍数
*/
canvas.scale(2, 4);
效果图如下:
缩放的另外一个方法:canvas.scale(float sx, float sy, float px, float py);
/**
* 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) {
if (sx == 1.0f && sy == 1.0f) return;
translate(px, py);
scale(sx, sy);
translate(-px, -py);
}
同理,也是先实现平移,在指定位置缩放之后又对画布做了返回平移处理,从而实现了以自定义点为中心缩放画布
的效果。
4、skew(倾斜)
/**
* 在Y轴方向倾斜
*
* @param sx x轴方向上倾斜的对应角度
* @param sy Y轴方向上倾斜的对应角度
*/
canvas.skew(0,1f);
注意
:这里的倾斜的角度都是tan值,比如要在x轴方向上倾斜60度,那么小数值对应:tan 60 = 根号3 = 1.732!因为效果不好画,就不上效果图了,毕竟这个方法用的也少。
二、绘制方法(drawXXX)
1、drawPoint、drawPoints(绘制点)
/**
* 绘制出一个点
*
* @param x X轴方向上的位置
* @param y Y轴方向上的位置
* @param Paint 画笔对象
*/
canvas.drawPoint(200, 200, redPaint);
点的大小取决于画笔的大小,不光可以绘制出单个点,也可以绘制出多个点:
float pts[] = {200, 200, 250, 250, 300, 300, 350, 350};
/**
* 绘制出多个点
*
* @param pts 要绘制点的float数组,格式是[x0 y0 x1 y1 x2 y2 ...],每两组一个点,最后不足两个的,忽略最后的值
* @param Paint 画笔对象
*/
canvas.drawPoints(pts, redPaint);
看效果图:
2、drawLine、drawLines(绘制线)
/**
* 绘制出一条线
*
* @param startX 线的起点的X坐标
* @param startY 线的起点的Y坐标
* @param stopX 线的终点的X坐标
* @param stopY 线的终点的X坐标
* @param Paint 画笔对象
*/
canvas.drawLine(200, 200, 400, 400, redPaint);
线的大小也取决于画笔的大小,并且也是可以绘制出多条线:
float[] pts = new float[]{100, 100, 200, 200, 200, 100, 300, 100};
/**
* 同时绘制多条线。
*
* @param pts 要绘制线的float数组,格式是每四个一组为一条线。最后不足四个,就忽略那些值。
* @param Paint 画笔对象
*/
canvas.drawLines(pts, redPaint);
看一下效果图:
3、drawRect、drawRoundRect(绘制矩形)
/**
* 矩形 (第一种方法)
*
* @param left int 矩形左侧的X坐标
* @param top int 矩形顶部的Y坐标
* @param right int 矩形右侧的X坐标
* @param bottom int 矩形底部的Y坐标
*/
Rect rect = new Rect(100, 100, 300, 300);
/**
* 绘制矩形
*
* @param rect 矩形对象
* @param paint 画笔对象
*/
canvas.drawRect(rect, redPaint);
/**
* 矩形 (第三种方法)
*
* @param left float 矩形左侧的X坐标
* @param top float 矩形顶部的Y坐标
* @param right float 矩形右侧的X坐标
* @param bottom float 矩形底部的Y坐标
*/
RectF rectF = new RectF((float) 100.3, 100, 300, 300);
/**
* 绘制矩形
*
* @param rectF 矩形对象
* @param paint 画笔对象
*/
canvas.drawRect(rectF, redPaint);
/**
* 绘制矩形 (第三种方法)
*
* @param left float 矩形左侧的X坐标
* @param top float 矩形顶部的Y坐标
* @param right float 矩形右侧的X坐标
* @param bottom float 矩形底部的Y坐标
* @param paint 画笔对象
*/
canvas.drawRect((float) 100.3, 100, 300, 300, redPaint);
OK,看下效果:
Canvas 也给我们提供了圆角矩形的方法,我们来看下:
/**
* 绘制圆角矩形 (第一种方法)
*
* @param rectF 矩形对象
* @param rx 围绕角落的椭圆的X半径
* @param ry 围绕角落的椭圆的Y半径
* @param paint 画笔对象
*/
canvas.drawRoundRect(rectF,20,20,redPaint);
/**
* 绘制圆角矩形 (第二种方法)
*
* @param left float 矩形左侧的X坐标
* @param top float 矩形顶部的Y坐标
* @param right float 矩形右侧的X坐标
* @param bottom float 矩形底部的Y坐标
* @param rx 围绕角落的椭圆的X半径
* @param ry 围绕角落的椭圆的Y半径
* @param paint 画笔对象
*/
canvas.drawRoundRect((float) 100.3, 100, 300, 300,20,20,redPaint);
效果图如下:
4、drawOval(绘制椭圆)
/**
* 绘制椭圆 (第一种方法)
*
* @param rectF 矩形对象要绘制椭圆的矩形边界
* @param paint 画笔对象
*/
canvas.drawOval(rectF, redPaint);
/**
* 绘制椭圆 (第二种方法)
*
* @param left float 矩形边界左侧的X坐标
* @param top float 矩形边界顶部的Y坐标
* @param right float 矩形边界右侧的X坐标
* @param bottom float 矩形边界底部的Y坐标
* @param paint 画笔对象
*/
canvas.drawOval((float) 100.3, 100, 300, 200, redPaint);
5、drawCircle(绘制圆)
/**
* 绘制圆
*
* @param cx 要绘制的圆的中心的X坐标
* @param cy 要绘制的圆的中心的Y坐标
* @param radius 要绘制的圆的半径
* @param paint 画笔对象
*/
canvas.drawCircle(200,200,100,redPaint);
继续上效果图:
6、drawArc(绘制圆弧)
/**
* 绘制圆弧
*
* @param oval RectF对象,椭圆的边界用于定义弧的形状和大小
* @param startAngle 开始的角度。(水平向右为0度顺时针反向为正方向)
* @param sweepAngle 扫过的角度
* @param useCenter 是否和中心连线
* @param paint 画笔对象
*/
canvas.drawArc(rectF, 0, 90, true, redPaint);
效果图如下:
7、drawColor、drawRGB、drawARGB(绘制颜色)
/**
* 绘制颜色 (第一种方法)
*
* @param color 颜色值
*/
canvas.drawColor(Color.RED);
/**
* 绘制颜色 (第二种方法)
*
* @param r 要绘制到画布上的颜色的红色成分,范围在0-255之间
* @param g 要绘制到画布上的颜色的绿色成分,范围在0-255之间
* @param b 要绘制到画布上的颜色的蓝色成分,范围在0-255之间
*/
canvas.drawRGB(255, 0, 0);
/**
* 绘制颜色 (第三种方法)
* @param a 要绘制到画布上的颜色的alpha分量(0-255)
* @param r 要绘制到画布上的颜色的红色成分,范围在0-255之间
* @param g 要绘制到画布上的颜色的绿色成分,范围在0-255之间
* @param b 要绘制到画布上的颜色的蓝色成分,范围在0-255之间
*/
canvas.drawARGB(255,255,0,0);
8、drawText, drawTextOnPath(绘制文本)
/**
* 绘制文本 (第一种方法)
*
* @param text String,绘制的文本内容
* @param x float 正在绘制的文本原点的X坐标
* @param y float 正在绘制的文本原点的Y坐标
* @param paint 画笔对象
*/
canvas.drawText("绘制文本内容", 100, 100, redPaint);
/**
* 绘制文本 (第二种方法)
*
* @param text String,绘制的文本内容
* @param start int 要从第几个字开始绘制
* @param end int 要绘制到第几个文字
* @param x float 正在绘制的文本原点的X坐标
* @param y float 正在绘制的文本原点的Y坐标
* @param paint 画笔对象
*/
canvas.drawText("绘制文本内容", 2,4,100, 100, redPaint);
/**
* 绘制文本 (第三种方法)
*
* @param chars String,绘制的文本内容
* @param index int 要从第几个字开始绘制
* @param end int 要绘制文字的数量
* @param x int 正在绘制的文本原点的X坐标
* @param y int 正在绘制的文本原点的Y坐标
* @param paint 画笔对象
*/
canvas.drawText(new char[]{'绘', '制', '文', '本', '内', '容'}, 2, 4, 100, 100, redPaint);
/**
* 绘制文本 (第四种方法)
*
* @param text CharSequence 绘制的文本内容
* @param start int 要从第几个字开始绘制
* @param end int 要绘制到第几个文字
* @param x float 正在绘制的文本原点的X坐标
* @param y float 正在绘制的文本原点的Y坐标
* @param paint 画笔对象
*/
canvas.drawText("绘制文本内容", 2,4,100, 100, redPaint);
/**
* 根据路径去绘制文本
*
* @param text String 绘制的文本内容
* @param path 路径
* @param hOffset float 距离路径开始位置的偏移量
* @param vOffset float 距离路径上下的偏移量(可以为负数)
* @param paint 画笔对象
*/
canvas.drawTextOnPath("绘制文本内容", path, 10, -50, redPaint);
/**
* 根据路径去绘制文本
*
* @param text char[] 绘制的文本内容
* @param index int 要从第几个字开始绘制
* @param end int 要绘制文字的数量
* @param path 路径
* @param hOffset float 距离路径开始位置的偏移量
* @param vOffset float 距离路径上下的偏移量(可以为负数)
* @param paint 画笔对象
*/
canvas.drawTextOnPath(new char[]{'绘', '制', '文', '本', '内', '容'}, 2, 4, path,10, -50, redPaint);
下面看下根据路径绘制文本的效果:
8、drawBitmap(绘制位图)
/**
* 绘制位图 (第一种方法)
*
* @param bitmap 要绘制的位图
* @param left 绘制位图左侧的位置
* @param top 绘制位图顶部的位置
* @param paint 画笔对象 可以为null
*/
canvas.drawBitmap(bitmap, 100, 100, null);
/**
* 绘制位图 (第二种方法)
*
* @param bitmap 要绘制的位图
* @param src Rect 对当前图片进行裁剪,根据图片绘制的区域
* @param dst Rect 对图片的大小进行缩放和移动,根据画布绘制显示的区域
* @param paint 画笔对象 可以为null
*/
canvas.drawBitmap(bitmap, new Rect(100, 100, bitmap.getWidth(), bitmap.getHeight()),new Rect(100, 100, bitmap.getWidth(), bitmap.getHeight()), null);
/**
* 绘制位图 (第三种方法)
*
* @param bitmap 要绘制的位图
* @param src Rect 对当前图片进行裁剪,根据图片绘制的区域
* @param dst RectF 对图片的大小进行缩放和移动,根据画布绘制显示的区域
* @param paint 画笔对象 可以为null
*/
canvas.drawBitmap(bitmap, new Rect(100, 100, bitmap.getWidth(), bitmap.getHeight()),new RectF(100, 100, bitmap.getWidth(), bitmap.getHeight()), null);
/**
* 绘制位图 (第四种方法)
*
* @param bitmap 要绘制的位图
* @param matrix Rect 用于在绘制位图时变换位图的矩阵
* @param paint 画笔对象 可以为null
*/
canvas.drawBitmap(bitmap, new Matrix(), null);
下面我们看下第二种方法实现的效果:
9、drawPath(绘制路径)
/**
* 绘制路径
*
* @param path 要绘制的路径
* @param paint 画笔对象
*/
canvas.drawPath(path, redPaint);
使用Path不仅能够绘制简单图形,也可以绘制这些比较复杂的图形。另外,根据路径绘制文本和剪裁画布都会用到Path。
三、画布快照和回滚
1、SaveFlags
数据类型 | 名称 | 简介 |
---|---|---|
int | ALL_SAVE_FLAG | 默认,保存全部状态 |
int | CLIP_SAVE_FLAG | 保存剪辑区 |
int | CLIP_TO_LAYER_SAVE_FLAG | 剪裁区作为图层保存 |
int | FULL_COLOR_LAYER_SAVE_FLAG | 保存图层的全部色彩通道 |
int | HAS_ALPHA_LAYER_SAVE_FLAG | 保存图层的alpha(不透明度)通道 |
int | MATRIX_SAVE_FLAG | 保存Matrix信息(translate, rotate, scale, skew) |
2、Save
把当前的画布的状态进行保存,然后放入特定的栈中。
// 保存全部状态
public int save ()
// 根据saveFlags参数保存一部分状态
public int save (int saveFlags)
3.saveLayerXxx
save()方法保存的是整个Canvas,而saveLayer()则可以选择性的保存某个区域的状态。
// 无图层alpha(不透明度)通道
public int saveLayer (RectF bounds, Paint paint)
public int saveLayer (RectF bounds, Paint paint, int saveFlags)
public int saveLayer (float left, float top, float right, float bottom, Paint paint)
public int saveLayer (float left, float top, float right, float bottom, Paint paint, int saveFlags)
// 有图层alpha(不透明度)通道
public int saveLayerAlpha (RectF bounds, int alpha)
public int saveLayerAlpha (RectF bounds, int alpha, int saveFlags)
public int saveLayerAlpha (float left, float top, float right, float bottom, int alpha)
public int saveLayerAlpha (float left, float top, float right, float bottom, int alpha, int saveFlags)
4、restore
状态回滚:用save 方法保存后,从栈顶取出一个状态然后根据内容进行恢复,也就是说将Canvas还原成最近的一个save() 的状态。
5、restoreToCount
save()方法还会有一个返回值,我们也可以调用restoreToCount(int saveCount)方法,将这个返回值作为参数传递进去,就可以将Canvas还原成某一个特定的save()状态。
canvas.translate(100,100); // 平移(100,100)
int save1 = canvas.save(); // 保存Canvas状态(状态1)
canvas.scale(2, 2); // 放大2倍
int save2 = canvas.save(); // 保存Canvas状态(状态2)
canvas.restore(); // 返回最新的save状态,即状态2
canvas.restoreToCount(save1);// 手动指定的返回到 状态1
6、getSaveCount
获取保存的次数,即状态栈中保存状态的数量。
四、画布裁剪
clipPath, clipRect
/**
* 绘制路径
*
* @param path 要绘制的路径
* @param paint 画笔对象
*/
canvas.drawPath(path, redPaint);//画出贝塞尔曲线
/**
* 画布裁剪
*
* @param RectF 与当前裁剪相交的矩形
*/
canvas.clipRect(rectF);
/**
* 画布裁剪
*
* @param Rect 与当前裁剪相交的矩形
*/
canvas.clipRect(rect);
/**
* 画布裁剪
*
* @param left float 裁剪矩形左边的X坐标
* @param top float 裁剪矩形顶部的Y坐标
* @param right float 裁剪矩形右侧的X坐标
* @param bottom float 裁剪矩形底部的Y坐标
*/
canvas.clipRect(0,0,300,300);
/**
* 画布裁剪
*
* @param left int 裁剪矩形左边的X坐标
* @param top int 裁剪矩形顶部的Y坐标
* @param right int 裁剪矩形右侧的X坐标
* @param bottom int 裁剪矩形底部的Y坐标
*/
canvas.clipRect(0,0,300,300);
/**
* 画布裁剪
*
* @param path 裁剪Path包括的范围,Path所包括的范围不是空的才有效
*/
canvas.clipPath(path);