版权声明:本文为博主原创文章,未经博主允许不得转载。
本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。
一、概述
Canvas 我们经常把它理解为画板,但是,实际上 Canvas 是一个封装类,它包含了四个东西:
1、画板:用来保存像素的bitmap
2、画布或者画纸: Canvas 在 Bitmap 上进行绘制操作 ---- (Layer—saveLayer操作时,新建一个透明的画布图层)
3、绘制的东西
4、绘制的画笔 Paint
二、Canvas 的基本方法
1.画直线
canvas.drawLine(0, 0, 100, 100, paint);
drawLine(float startX, float startY, float stopX, float stopY, Paint paint)
startX: 起始点 x 坐标
startY :起始点 y坐标
stopX: 结束点 x 坐标
stopY :结束点 y坐标
paint :画笔
2.画多条直线
float []pts = {0,0,100,100,200,200,300,300};
canvas.drawLines(pts, paint);
drawLines(float[] pts, Paint paint)
pts : 直线的端点数组,每 4 个点为一条直线
drawLines(float[] pts, int offset, int count, Paint paint)
pts : 直线的端点数组,每 4 个点为一条直线
offset : 跳过的数据个数,跳过的数据不参与直线绘制
count : 实际参与绘制的数据个数
3.画点
canvas.drawPoint(50, 50, paint);
drawPoint(float x, float y, Paint paint)
x : 点的 x 坐标
y : 点的 y 坐标
4.画多个点
float []pts = {0,0,100,100,200,200,300,300};
canvas.drawPoints(pts, paint);
drawPoints(float[] pts, Paint paint)
pts : 点的坐标数组,每 2个点为一个点
drawPoints(float[] pts, int offset, int count, Paint paint)
pts : 点的坐标数组,每 2个点为一个点
offset : 跳过的数据个数,跳过的数据不参与点绘制
count : 实际参与绘制的数据个数
5.画矩形
RectF r = new RectF(100, 100, 400, 500);
canvas.drawRect(r, paint);
canvas.drawRect(100, 100, 400, 500, paint);
drawRect(RectF rect, Paint paint)
rect : 矩形区域
drawRect(Rect r, Paint paint)
r : 矩形区域
drawRect(float left, float top, float right, float bottom, Paint paint)
left : 左边的 x 坐标
top :上边的 y 坐标
right : 右边的 x 坐标
bottom : 底边的 y 坐标
注:RectF 和 Rect 主要区别是精度,RectF 使用的是 float, Rect 使用的是 int。
6.画圆角矩形
RectF r = new RectF(100, 100, 400, 500);
canvas.drawRoundRect(r, 30, 30, paint);
canvas.drawRoundRect(100, 100, 400, 500, 30, 30, paint);
drawRoundRect(RectF rect, float rx, float ry, Paint paint)
rect : 矩形区域
rx:x方向上的圆角半径。
ry:y方向上的圆角半径。
drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, Paint paint)
left : 左边的 x 坐标
top :上边的 y 坐标
right : 右边的 x 坐标
bottom : 底边的 y 坐标
rx:x方向上的圆角半径。
ry:y方向上的圆角半径。
7.画圆
canvas.drawCircle(300, 300, 200, paint);
drawCircle(float cx, float cy, float radius, Paint paint)
cx : 圆心的x坐标
cy : 圆心的y坐标
radius : 圆的半径
8.画椭圆
RectF r = new RectF(100, 100, 400, 500);
canvas.drawOval(r, paint);
canvas.drawOval(100, 100, 400, 500, 30, 30, paint);
drawOval(RectF oval, Paint paint)
oval : 外切矩形区域
drawOval(float left, float top, float right, float bottom, Paint paint)
left : 外切矩形左边的 x 坐标
top :外切矩形上边的 y 坐标
right : 外切矩形右边的 x 坐标
bottom :外切矩形 底边的 y 坐标
9.画圆弧
RectF r = new RectF(100, 100, 400, 500);
canvas.drawArc(r, 0, 90, true, paint);
canvas.drawArc(100, 100, 400, 500, 0, 90, true, paint);
drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)
oval : 圆弧所在椭圆外切矩形
startAngle : 圆弧的起始角度(0 为 x轴正方向)
sweepAngle : 圆弧的角度
useCenter:是否显示半径连线,true表示显示圆弧与圆心的半径连线,false表示不显示
drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, Paint paint)
left : 圆弧所在外切矩形左边的 x 坐标
top :圆弧所在外切矩形上边的 y 坐标
right : 圆弧所在外切矩形右边的 x 坐标
bottom :圆弧所在外切矩形 底边的 y 坐标
startAngle : 圆弧的起始角度(0 为 x轴正方向)
sweepAngle : 圆弧的角度
useCenter:是否显示半径连线,true表示显示圆弧与圆心的半径连线,false表示不显示
把 useCenter 改为 false:
10.画路径
Path path = new Path();
//画笔落笔的位置
path.moveTo(100, 100);
//移动
path.lineTo(200, 100);
path.lineTo(200, 200);
path.cubicTo(250, 200, 400, 250, 450, 400);
path.close();
canvas.drawPath(path, paint);
drawPath(Path path, Paint paint)
path : 路径
path 部分不在这边细讲。
三、Canvas 画区域
Canvas 除了画常用的形状之外,还能画 Region ,它表示的 Canvas 图层上的一块封闭的区域。由于区域是不规则的,所以在绘制的时候会被切割成多个矩形,通过迭代进行绘制。
Paint paint = new Paint();
paint.setColor(Color.RED);
//样式必须设为 STROKE,否则看不出效果
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(2);
RectF r = new RectF(100, 100, 600, 800);
Path path = new Path();
path.addOval(r, Path.Direction.CCW);
//创建一块矩形的区域
Region region = new Region(100, 100, 600, 800);
Region region1 = new Region();
//path的椭圆区域和矩形区域进行交集
region1.setPath(path, region);
//结合区域迭代器使用(得到图形里面的所有的矩形区域)
RegionIterator iterator = new RegionIterator(region1);
Rect rect = new Rect();
//通过迭代,进行区域的绘制
while (iterator.next(rect)) {
canvas.drawRect(rect, paint);
}
区域的叠加规则
区域叠加规则枚举类 Op 的源码:
public enum Op {
DIFFERENCE(0),
INTERSECT(1),
UNION(2),
XOR(3),
REVERSE_DIFFERENCE(4),
REPLACE(5);
Op(int nativeInt) {
this.nativeInt = nativeInt;
}
/**
* @hide
*/
public final int nativeInt;
}
效果图中 A 为调用者, B 为叠加上来的区域。
op(Rect r, Op op)
r: 叠加上去的矩形
op : 叠加规则
op(int left, int top, int right, int bottom, Op op)
left : 叠加上去的矩形左边的 x 坐标
top :叠加上去的矩形上边的 y 坐标
right : 叠加上去的矩形右边的 x 坐标
bottom :叠加上去的矩形 底边的 y 坐标
op : 叠加规则
op(Region region, Op op)
region: 叠加上去的区域
op : 叠加规则
四、Canvas 坐标系
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(10);
RectF r = new RectF(0, 0, 400, 500);
paint.setColor(Color.GREEN);
canvas.drawRect(r, paint);
//平移
canvas.translate(50, 50);
paint.setColor(Color.BLUE);
canvas.drawRect(r, paint);
上方代码先绘制一个矩形,然后进行平移操作,后再次绘制一个矩形。**在 translate(50, 50) 里平移的是 Canvas 的绘图坐标系,而不是 Canvas。**我们可以稍微证明一下。
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(10);
RectF r = new RectF(0, 0, 400, 500);
paint.setColor(Color.GREEN);
canvas.drawRect(r, paint);
//平移
canvas.translate(50, 50);
canvas.drawColor(Color.YELLOW);
paint.setColor(Color.BLUE);
canvas.drawRect(r, paint);
在平移后给画布填充颜色,发现整个屏幕都充满,说明平移的不是画布。
Canvas里面牵扯两种坐标系:Canvas 自己的坐标系、绘图坐标系。
1.Canvas 自己的坐标系
它就在View的左上角,做坐标原点往右是X轴正半轴,往下是Y轴的正半轴,有且只有一个,唯一不变。
2…Canvas 绘图坐标系
它不是唯一不变的,它与Canvas的Matrix有关系,当Matrix发生改变的时候,绘图坐标系对应的进行> > 改变。
用画直线的方法把平移前后的坐标系画出来。可以发现平移之后坐标系变了。
RectF r = new RectF(0, 0, 400, 500);
paint.setColor(Color.GREEN);
canvas.drawRect(r, paint);
canvas.drawLine(0,0,canvas.getWidth(),0,paint);// X 轴
canvas.drawLine(0,0,0,canvas.getHeight(),paint);// Y 轴
canvas.translate(50, 50);
paint.setColor(Color.BLUE);
canvas.drawRect(r, paint);
canvas.drawLine(0,0,canvas.getWidth(),0,paint);// X 轴
canvas.drawLine(0,0,0,canvas.getHeight(),paint);// Y 轴
继续进行旋转45度。
RectF r = new RectF(0, 0, 400, 500);
paint.setColor(Color.GREEN);
canvas.drawRect(r, paint);
canvas.drawLine(0,0,canvas.getWidth(),0,paint);// X 轴
canvas.drawLine(0,0,0,canvas.getHeight(),paint);// Y 轴
canvas.translate(50, 50);
paint.setColor(Color.BLUE);
canvas.drawRect(r, paint);
canvas.drawLine(0,0,canvas.getWidth(),0,paint);// X 轴
canvas.drawLine(0,0,0,canvas.getHeight(),paint);// Y 轴
canvas.rotate(45);
paint.setColor(Color.YELLOW);
canvas.drawLine(0,0,canvas.getWidth(),0,paint);// X 轴
canvas.drawLine(0,0,0,canvas.getHeight(),paint);// Y 轴
结论:绘制坐标的变化跟矩阵 Matrix 有关。Matrix又是通过我们设置translate、rotate、scale、skew来进行改变的
3.Canvas 绘图坐标系的重置
Canvas 绘制坐标系的变化过程是不可逆的,只能通过 save 和 restore 方法来保存和还原变化操作
RectF r = new RectF(0, 0, 400, 500);
paint.setColor(Color.GREEN);
canvas.drawRect(r, paint);
canvas.drawLine(0,0,canvas.getWidth(),0,paint);// X 轴
canvas.drawLine(0,0,0,canvas.getHeight(),paint);// Y 轴
canvas.save();
canvas.translate(50, 50);
paint.setColor(Color.BLUE);
canvas.drawRect(r, paint);
canvas.drawLine(0,0,canvas.getWidth(),0,paint);// X 轴
canvas.drawLine(0,0,0,canvas.getHeight(),paint);// Y 轴
canvas.restore();
canvas.rotate(45);
paint.setColor(Color.YELLOW);
canvas.drawLine(0,0,canvas.getWidth(),0,paint);// X 轴
canvas.drawLine(0,0,0,canvas.getHeight(),paint);// Y 轴
在第一次变化坐标前 save,绘制坐标系后 restore, 再次进行坐标变化是从最开始的坐标系进行变化。说明第一次变化后,坐标系重置还原了。
五、Canvas的状态保存—状态栈、Layer栈
1.状态栈 – save、 restore 方法
1.只是保存和还原变换操作 Matrix 以及 Clip 剪裁,
2.可以通过 restoretoCount 直接还原到对应栈的保存状态。
3.不是真正的图层关系
2.Layer栈
1.真正的图层关系,saveLayer 的时候都会新建一个透明的图层(离屏 Bitmap-离屏缓冲),并且会将saveLayer 之前的一些 Canvas 操作延续过来
2. 后续的绘图操作都在新建的layer上面进行
3.当我们调用restore 或者 restoreToCount 时会更新到对应的图层和画布上
saveLayer 可以实现所有 save 的实现效果。save 只是在栈中保存 Matrix 以及 Clip 剪裁的变换记录,当开始绘制的时候,根据栈中保存的变换记录得出最终的绘制坐标,从而进行绘制。saveLayer 是把前面的操作保存成一个 Layer 存入栈中,再新建一个 Layer,后续绘制在新的 Layer 上进行,新绘制的 Layer 可以根据 saveLayer 的时候传的参数进行选择保存上一个 Layer 的哪些 Matrix 以及 Clip 剪裁的变换。
目前个人除了在使用 Xfermode 的时候,用 save() 方法的话,原先绘制的背景会对混合结果造成影响,所以要用 saveLayer()。其他地方使用暂时没有发现区别。