Android画板_Canvas

Java中2D图像绘制的主要类, 俗称画布. 提供最基础的2D图像绘制功能

画笔常用样式

Android2D绘图中画笔是不不可或的, 有了画板当然得有画笔. 关键类是Paint, 画笔拥有丰富的自定义属性, 可以通过setter设置这些属性. 这里我只会简单的介绍下. 详细的画笔介绍我将重新写一篇文章.

1
2
3
4
5
6
7
8
// 1.创建一个画笔
private Paint mPaint = new Paint();
// 2.初始化画笔
mPaint.setColor(Color.BLACK); //画笔颜色
mPaint.setStyle(Paint.Style.FILL); //画笔样式为填充
mPaint.setStrokeWidth(10f); //画笔宽度为10px
mPaint.setAntiAlias(true); // 画笔 抗锯齿/边缘平滑

setStyle()有三个参数 如下:

1
2
3
STROKE //描边
FILL //填充
FILL_AND_STROKE //描边加填充

图像绘制

Canvas提供绘制基本图像的方法, 每次原点以屏幕左上角为准的绝对坐标.

Canvas常用API

操作类型 相关API 备注
绘制颜色 drawColor, drawRGB, drawARGB 使用单一颜色填充整个画布
绘制基本形状 drawPoint, drawPoints, drawLine, drawLines, drawRect, drawRoundRect, drawOval, drawCircle, drawArc 依次为 点、线、矩形、圆角矩形、椭圆、圆、圆弧
绘制图片 drawBitmap, drawPicture 绘制位图和图片
绘制文本 drawText, drawPosText, drawTextOnPath 依次为 绘制文字、绘制文字时指定每个文字位置、根据路径绘制文字
绘制路径 drawPath 绘制路径,绘制贝塞尔曲线时也需要用到该函数
顶点操作 drawVertices, drawBitmapMesh 通过对顶点操作可以使图像形变,drawVertices直接对画布作用、 drawBitmapMesh只对绘制的Bitmap作用
画布剪裁 clipPath, clipRect 设置画布的显示区域
画布快照 save, restore, saveLayerXxx, restoreToCount, getSaveCount 依次为 保存当前状态、 回滚到上一次保存的状态、 保存图层状态、 回滚到指定状态、 获取保存次数
画布变换 translate, scale, rotate, skew 依次为 位移、缩放、 旋转、错切
Matrix(矩阵) getMatrix, setMatrix, concat 实际上画布的位移,缩放等操作的都是图像矩阵Matrix, 只不过Matrix比较难以理解和使用,故封装了一些常用的方法。

绘制点

可以绘制一个点,也可以绘制一组点,如下:

1
2
3
4
5
6
canvas.drawPoint(200, 200, mPaint); //在坐标(200,200)位置绘制一个点
canvas.drawPoints(new float[]{ //绘制一组点,坐标位置由float数组指定
500,500,
500,600,
500,700
},mPaint);

绘制直线

绘制直线需要两个点,初始点和结束点,同样绘制直线也可以绘制一条或者绘制一组:

1
2
3
4
5
canvas.drawLine(300,300,500,600,mPaint); // 在坐标(300,300)(500,600)之间绘制一条直线
canvas.drawLines(new float[]{ // 绘制一组线 每四数字(两个点的坐标)确定一条线
100,200,200,200,
100,300,200,300
},mPaint);

绘制矩形

确定确定一个矩形最少需要对角线的两个点的坐标值,这里采用左上角和右下角的两个点的坐标。

关于绘制矩形,Canvas提供了三种重载方法,第一种就是提供四个数值(矩形左上角和右下角两个点的坐标)来确定一个矩形进行绘制。 其余两种是先将矩形封装为Rect或RectF(实际上仍然是用两个坐标点来确定的矩形),

Rect和RectF区别

  1. 精度不一样。Rect是使用int类型作为数值,RectF是使用float类型作为数值
  2. 方法也并不是完全一致

然后传递给Canvas绘制,如下:

1
2
3
4
5
6
7
8
9
10
// 第一种
canvas.drawRect(100,100,800,400,mPaint);
// 第二种
Rect rect = new Rect(100,100,800,400);
canvas.drawRect(rect,mPaint);
// 第三种
RectF rectF = new RectF(100.1f,100.1f,800.1f,400.1f);
canvas.drawRect(rectF,mPaint);

Rect的四个参数分别为矩形左上角和右下角的两个坐标

绘制圆角矩形

绘制圆角矩形也提供了两种重载方式,如下:

1
2
3
4
5
6
// 第一种, 矩形Rect实际就是包含两个坐标值所以同样可以用坐标代替Rect
RectF rectF = new RectF(100,100,800,400);
canvas.drawRoundRect(rectF,30,30,mPaint);
// 第二种
canvas.drawRoundRect(100,100,800,400,30,30,mPaint);

30,30这两个参数为圆角矩形的圆角的两个半径值

当rx大于宽度的一半,ry大于高度的一半时,实际上是无法计算出圆弧的,所以drawRoundRect对大于该数值的参数进行了限制(修正),凡是大于一半的参数均按照一半来处理. 而在rx为宽度的一半,ry为高度的一半时,刚好是一个椭圆.

绘制椭圆

相对于绘制圆角矩形,绘制椭圆就简单的多了,绘制椭圆实际上就是绘制一个矩形的内切图形. 所以他只需要一个矩形矩形作为参数:

1
2
3
4
5
6
// 第一种
RectF rectF = new RectF(100,100,800,400);
canvas.drawOval(rectF,mPaint);
// 第二种
canvas.drawOval(100,100,800,400,mPaint);

绘制圆

绘制圆形也比较简单, 注意圆的起始点是圆心. 如下:

1
canvas.drawCircle(500,500,400,mPaint); // 绘制一个圆心坐标在(500,500),半径为400 的圆。

绘制弧度

canvas绘制弧形实际上是用一个矩形来决定弧形的弧长和半径值, 因为矩形的两边可以得到任意的椭圆.

从图可以看出实际上就是通过开始角度扫过角度来截取这个弧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void drawArc (float left,
float top,
float right,
float bottom,
float startAngle, // 开始角度
float sweepAngle, // 扫过角度
boolean useCenter, // 是否使用中心
Paint paint)
void drawArc (RectF oval,
float startAngle,
float sweepAngle,
boolean useCenter,
Paint paint)

是否使用中心区别示意图

1
canvas.drawArc(0, 0, 200, 100, -90, 90, true, mPaint);

true

false

画布操作

以当前View的左上角为原点的相对坐标. 画布操作并不会改变原有的图像, 而是以当前位置进行偏移, 在下次使用画布绘制图像时将发生变化.

位移

1
2
3
4
5
6
7
8
9
// 在坐标原点绘制一个黑色圆形
mPaint.setColor(Color.BLACK);
canvas.translate(200,200);
canvas.drawCircle(0,0,100,mPaint);
// 在坐标原点绘制一个蓝色圆形
mPaint.setColor(Color.BLUE);
canvas.translate(200,200);
canvas.drawCircle(0,0,100,mPaint);

缩放

scale不仅能缩放画布同时也能够翻转画布

在参数为正数时按照比例缩放, 为负数时为翻转画布且按比例缩放, 1为原始大小, 0 为不显示

1
2
3
public void scale (float sx, float sy) // 缩放比例
public final void scale (float sx, float sy, float px, float py) // 新增缩放中心坐标
缩放比例 描述
负数 以坐标轴为基准向上翻转
正数 以坐标轴为基准向下翻转
1 原始大小
0 不显示

旋转

Android的旋转角度正数增加是顺时针

1
2
3
public void rotate (float degrees) // 旋转角度
public final void rotate (float degrees, float px, float py) // 增加的两个参数为旋转中心点坐标

错切

使画布产生角度倾斜

1
public void skew (float sx, float sy) // 分别对应XY轴的角度正切值

正切值 = 对边/邻边

快照和回滚

也可以叫做画布的保存和恢复

相关API 简介
save 把当前的画布的状态进行保存,然后放入特定的栈中
saveLayerXxx 新建一个图层,并放入特定的栈中(状态栈)
restore 把栈中最顶层的画布状态取出来,并按照这个状态恢复当前的画布
restoreToCount 弹出指定位置及其以上所有的状态,并按照指定位置的状态进行恢复
getSaveCount 获取栈中内容的数量(即保存次数)

状态栈

每次保存都在会存入一个栈列模式的容器中(遵守先进后出)

保存

1
2
public int save () // 保存全部状态
public int save (int saveFlags) // 根据saveFlags参数保存一部分状态

SavaFlags

参数 描述
ALL_SAVE_FLAG 默认,保存全部状态
CLIP_SAVE_FLAG 保存剪辑区
CLIP_TO_LAYER_SAVE_FLAG 剪裁区作为图层保存
FULL_COLOR_LAYER_SAVE_FLAG 保存图层的全部色彩通道
HAS_ALPHA_LAYER_SAVE_FLAG 保存图层的alpha(不透明度)通道
MATRIX_SAVE_FLAG 保存Matrix信息( translate, rotate, scale, skew)

绘制图片

绘制图片分为两种方式drawPicturedrawBitmap

Picture

Picture在Android中相当于一个视频录像机, 将绘制的图片依次保存起来

相关方法 简介
public int getWidth () 获取宽度
public int getHeight () 获取高度
public Canvas beginRecording (int width, int height) 开始录制 (返回一个Canvas,在Canvas中所有的绘制都会存储在Picture中)
public void endRecording () 结束录制
public void draw (Canvas canvas) 将Picture中内容绘制到Canvas中
public static Picture createFromStream (InputStream stream) (已废弃)通过输入流创建一个Picture
public void writeToStream (OutputStream stream) (已废弃)将Picture中内容写出到输出流中

录制

1
2
3
4
5
6
Picture picture = new Picture(); // 开始录制
Canvas record = picture.beginRecording(500, 500); // 开始录制, 后面的尺寸表示绘制的大小
record.translate(250,250);
record.drawColor(Color.BLACK);
record.drawCircle(0, 0, 100, mPaint); // 绘制到屏幕中心
picture.endRecording(); // 结束录制

需要注意的是, 结束录制后, 在开始录制时创建的Canvas对象record不能再次被使用. 否则ANR

播放

录制完了当然得”播放”才行, 也就是绘制到屏幕上, 需要注意的是绘制用的画板不能是录制时picture.beginRecording(500, 500);创建的画板, 提供三种方法绘制:

使用Picture提供的draw方法绘制, 据说不常用因为在比较低的系统上会影响Canvas的状态

1
picture.draw(canvas); // 此canvas在绘制时会代替"record"完成绘制工作

使用Canvas提供的drawPicture方法绘制

1
2
3
4
5
public void drawPicture (Picture picture)
public void drawPicture (Picture picture, Rect dst) // 拉伸至匹配矩形
public void drawPicture (Picture picture, RectF dst) // 拉伸至匹配矩形, 精度更高

将Picture包装成为PictureDrawable,使用PictureDrawable的draw方法绘制。

1
2
3
PictureDrawable pictureDrawable = new PictureDrawable(picture);
pictureDrawable.setBounds(0,0,500,picture.getHeight());
pictureDrawable.draw(canvas);

Bitmap

想要将位图绘制到屏幕上, 首先得获取到位图对象

位图对象一般都是位图通过BitmapFactory转换成Bitmap, 具体实现我就不多说了, 官方写的很明白.

Canvas有六种drawBitmap()方法重载:

普通绘制

1
2
3
void drawBitmap (Bitmap bitmap, // 位图对象
Matrix matrix, // 改变位图位置的矩阵, 默认 new Matrix()
Paint paint) // 画笔, 可以为null

坐标绘制

控制位图的原点坐标

1
2
3
4
void drawBitmap (Bitmap bitmap,
float left, // 位图原点X坐标
float top, // 位图原点Y坐标
Paint paint)

截取绘制

对位图进行局部绘制和拉伸

1
2
3
4
void drawBitmap (Bitmap bitmap,
Rect src, // 绘制区域大小
Rect dst, // 拉伸区域大小
Paint paint)

举例:绘制图片的一部分, 但是拉伸至两倍

1
2
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
canvas.drawBitmap(bitmap, new Rect(0,0,100, 250), new Rect(0,0,200,250), null);

绘制文字

画笔文字

标题 相关方法 备注
大小 setTextSize 设置文本字体大小,值必须大于0,单位像素
字体 setTypeface 设置字体
对齐 setTextAlign 左对齐(LEFT),居中对齐(CENTER),右对齐(RIGHT)
测量 measureText 测量文本大小(注意,请在设置完文本各项参数后调用)

指定基线

文字的基点在文字左下角位置, 如图箭头所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void drawText (String text, // 需要显示的字符串
float x, // 坐标x
float y, // 坐标y
Paint paint)
void drawText (CharSequence text,
int start, // 从字符串的开始索引开始绘制, 左闭右开原则
int end, // 结束索引
float x,
float y,
Paint paint)
void drawText (String text, // CharSequence 是 String的超类接口
int start,
int end,
float x,
float y,
Paint paint)
void drawText (char[] text, // 字符数组形式
int index, // 开始索引位置
int count, // 截取字符数量(闭合区间)
float x,
float y,
Paint paint)

指定每个字符位置

drawPosText方法已经被废弃

  1. 字符串的每个字符都需要指定坐标, 否则crash.
  2. 性能不佳.
  3. 不支持emoji等特殊字符, 否则crash.
1
2
3
4
5
6
7
8
9
void drawPosText (String text,
float[] pos, // 每个字符的坐标
Paint paint)
void drawPosText (char[] text,
int index,
int count,
float[] pos,
Paint paint)

示例:

1
canvas.drawPosText(str, new float[]{100,100,200,200,300, 300},mPaint);

指定文字路径

1
2
3
4
5
6
7
8
9
10
11
12
13
void drawTextOnPath (String text,
Path path,
float hOffset,
float vOffset,
Paint paint)
void drawTextOnPath (char[] text,
int index,
int count,
Path path,
float hOffset,
float vOffset,
Paint paint)