【Android 自定义View之绘图】
基础图形的绘制
一、Paint与Canvas
绘图需要两个工具,笔和纸。这里的 Paint
相当于笔,而 Canvas
相当于纸,不过需要注意的是 Canvas
(画布)无限大,没有边界,切记理解成只有屏幕大小。我这里打个比方, Canvas
是整个天空,而屏幕是通过窗户看到的景色。
那么我需要改变画笔大小,粗细,颜色,透明度,字体样式等都需要在 Paint
里面设置;
同样要画出圆形,矩形,不规则形状都是在 Canvas
里面操作的。
Paint
Paint的基本设置函数
- mPaint.setAntiAlias(true) //设置是否抗锯齿;
- mPaint.setStyle(Paint.Style.FILL_AND_STROKE); //设置填充样式
- mPaint.setColor(Color.GREEN);//设置画笔颜色
- mPaint.setStrokeWidth(2);//设置画笔宽度
- mPaint.setShadowLayer(10,15,15,Color.RED);//设置阴影
1 、setAntiAlias(true) 设置是否抗锯齿
设置抗锯齿会使图像边缘更清晰一些,锯齿痕迹不会那么明显。
2、setStyle (Paint.Style style) 设置填充样式
Paint.Style
类型:
Paint.Style.FILL_AND_STROKE
填充且描边
Paint.Style.STROKE
描边
Paint.Style.FILL
填充
看下上面三种类型,这里以矩形为例:
3、setColor(@ColorInt int color) 设置画笔颜色
4、setStrokeWidth(float width) 设置画笔宽度
5、setShadowLayer(float radius, float dx, float dy, int shadowColor) 设置阴影
先来看看参数代表的含义:
radius : 表示阴影的倾斜度
dx : 水平位移
dy : 垂直位移
shadowColor : 阴影颜色
看一个简单的例子:
paint.setShadowLayer(5,10,10,Color.parseColor("#abc133"));
效果图:
这里你可能有疑问,为啥我自己演示了一篇却看不到矩形,圆形等图形的阴影,只能看到文本的阴影呢?那么我们需要注意的是:这个方法不支持硬件加速,所以我们要测试时必须先关闭硬件加速。
那么请加上setLayerType(LAYER_TYPE_SOFTWARE, null);
并且确保你的最小api8
以上。
Canvas
下文【Canvas详细讲解】有Canvas进一步说明
画布背景设置:
canvas.drawColor(Color.BLUE);
canvas.drawRGB(255, 255, 0);
这两个功能一样,都是用来设置背景颜色的。
我们只需要重写onDraw(Canvas canvas)
方法,就可以绘制你想要的图形了。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制图形
}
二、基本几何图形绘制
1、画直线drawLine
方法预览:
drawLine(float startX, float startY, float stopX, float stopY, @NonNull Paint paint)
参数:
startX : 开始点X坐标
startY : 开始点Y坐标
stopX : 结束点X坐标
stopY : 结束点Y坐标
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(5);
paint.setColor(Color.parseColor("#FF0000"));
canvas.drawLine(100,100,600,600,paint);
2、多条直线drawLines
方法预览:
drawLines(@Size(min=4,multiple=2) @NonNull float[] pts, int offset, int count, Paint paint)
drawLines(@Size(min=4,multiple=2) @NonNull float[] pts, @NonNull Paint paint)
参数:
pts : 是点的集合且大小最小为4而且是2的倍数。表示每2个点连接形成一条直线,pts 的组织方式为{x1,y1,x2,y2….}
offset : 集合中跳过的数值个数,注意不是点的个数!一个点是两个数值
count : 参与绘制的数值的个数,指pts[]里数值个数,而不是点的个数,因为一个点是两个数值
还是来看个例子:
@Override
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(5);
float [] pts={
50,100,100,200,200,300,300,400};
paint.setColor(Color.RED);
canvas.drawLines(pts,paint);
paint.setColor(Color.BLUE);
canvas.drawLines(pts,1,4,paint);//去掉第一个数50,取之后的4个数即100,100,200,200
}
红线:点(50,100)和点(100,200)连接成一条直线;点(200,300)和点(300,400)连接成直线。
蓝线:点(100,100)和点(200,200)连接成一条直线;
3、点及多个点drawPoint、drawPoints
方法预览:
drawPoint(float x, float y, @NonNull Paint paint)
drawPoints(@Size(multiple=2) @NonNull float[] pts, @NonNull Paint paint)
drawPoints(@Size(multiple=2) @NonNull float[] pts, int offset, int count, @NonNull Paint paint)
点的绘制和上面直线的绘制一样,我这里就不再累诉了。
4、矩形drawRect、drawRoundRect
方法预览:
drawRect(@NonNull RectF rect, @NonNull Paint paint)
drawRect(@NonNull Rect r, @NonNull Paint paint)
drawRect(float left, float top, float right, float bottom, @NonNull Paint paint)
区别RectF
与Rect
,RectF坐标系是浮点型;Rect坐标系是整形。
圆角矩形方法预览:
drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint)
drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, @NonNull Paint paint)
参数:
RectF : 绘制的矩形
rx : 生成圆角的椭圆X轴半径
ry : 生成圆角的椭圆Y轴的半径
RectF rect = new RectF(100, 10, 500, 300);
canvas.drawRoundRect(rect, 60, 20, paint);
5、圆形drawCircle
方法预览:
drawCircle(float cx, float cy, float radius, @NonNull Paint paint)
参数:
cx : 圆心X坐标
cy : 圆心Y坐标
radius : 半径
canvas.drawCircle(400,400,300,paint);
6、椭圆drawOval
方法预览:
drawOval(@NonNull RectF oval, @NonNull Paint paint)
drawOval(float left, float top, float right, float bottom, @NonNull Paint paint)
7、圆弧drawArc
方法预览:
drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter, @NonNull Paint paint)
drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle,
boolean useCenter, @NonNull Paint paint)
参数:
oval : 生成椭圆的矩形
startAngle : 弧开始的角度 (X轴正方向为0度,顺时针弧度增大)
sweepAngle : 绘制多少弧度 (注意不是结束弧度)
useCenter : 是否有弧的两边 true有两边 false无两边
画笔设置填充:
RectF rect=new RectF(0,0,300,400);
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.RED);
canvas.drawArc(rect,30,30,false,paint);
paint.setColor(Color.BLUE);
canvas.drawArc(rect,120,30,true,paint);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.GREEN);
canvas.drawArc(rect,0,360,true,paint);
paint.setColor(Color.RED);
canvas.drawArc(rect,-30,30,false,paint);
paint.setColor(Color.BLUE);
canvas.drawArc(rect,-120,30,true,paint);
说明:
引用:
路径(Path)
Path常用方法
方法 | 作用 | 备注 |
---|---|---|
moveTo | 移动起点 | 移动下一次操作的起点位置 |
lineTo | 连接直线 | 连接上一个点到当前点之间的直线 |
setLastPoint | 设置终点 | 重置最后一个点的位置 |
close | 闭合路劲 | 从最后一个点连接最初的一个点,形成一个闭合区域 |
addRect | 添加矩形 | 添加矩形到当前Path |
addRoundRect | 添加圆角矩形 | 添加圆角矩形到当前Path |
addOval | 添加椭圆 | 添加椭圆到当前Path |
addCircle | 添加圆 | 添加圆到当前Path |
addPah | 添加路劲 | 添加路劲到当前Path |
addArc | 添加圆弧 | 添加圆弧到当前Path |
arcTo | 圆弧 | 绘制圆弧,注意和addArc的区别 |
isEmpty | 是否为空 | 判定Path是否为空 |
isRect | 是否为矩形 | 判定Path是否是一个矩形 |
set | 替换路劲 | 用新的路劲替换当前路劲的所有内容 |
offset | 偏移路劲 | 对当前的路劲进行偏移 |
quadTo | 贝塞尔曲线 | 二次贝塞尔曲线的方法 |
cubicTo | 贝塞尔曲线 | 三次贝塞尔曲线的方法 |
rMoveTo rlineTo rQuadTo rCubicTo |
rXxx方法 | 不带r的方法是基于原点坐标系(偏移量),带r的基于当前点坐标系(偏移量) |
op | 布尔操作 | 对两个Path进行布尔运算(交集,并集)等操作 |
setFillType | 填充模式 | 设置Path的填充模式 |
getFillType | 填充模式 | 获取Path的填充 |
isInverseFillType | 是否逆填充 | 判断是否是逆填充模式 |
toggleInverseFillType | 相反模式 | 切换相反的填充模式 |
getFillType | 填充模式 | 获取Path的填充 |
incReserve | 提示方法 | 提示Path还有多少个点等待加入 |
computeBounds | 计算边界 | 计算Path的路劲 |
reset,rewind | 重置路劲 | 清除Path中的内容(reset相当于new Path , rewind 会保留Path的数据结构) |
transform | 矩阵操作 | 矩阵变换 |
Path方法使用详解
使用Path不仅可以绘制简单的图形(如圆形,矩形,直线等),也可以绘制复杂一些的图形(如正多边形,五角星等),还有绘制裁剪和绘制文本都会用到Path。由于方法比较多,我这里分组来讲下。
moveTo , lineTo , setLastPoint , close
先创建画笔:
paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(10);
paint.setColor(Color.parseColor("#FF0000"));
注意paint.setStyle(Paint.Style.FILL);
,设置画笔为实心。一些线条将在画布上看不见。
1、lineTo
首先我们来看看lineTo
,如果你直接moveTo
将看不出效果。
Path path = new Path();
path.lineTo(200,200);
path.lineTo(400,0);
canvas.drawPath(path,paint);
为了方便大家好观察坐标的变化,我在屏幕上画出了网格,每块网格的宽高都是100。由于第一次之前没有过操作,所以默认点就是原点(屏幕左上角),第一次lineTo就是坐标原点到(200,200)之间的直线。第二次*lineTo就是上一次结束点*位置(200,200)到(400,0)点之间的直线。
2、moveTo 和setLastPoint
方法预览:
moveTo(float x, float y)
setLastPoint(float dx, float dy)
这两个方法在作用上有相似之处,却是两个不同的东西,具体参考下表:
方法名 | 作用 | 是否影响之前的操作 | 是否影响之后的操作 |
---|---|---|---|
moveTo | 移动下一次操作的起点位置 | 否 | 是 |
setLastPoint | 改变上一次操作点的位置 | 是 | 是 |
来看看下面的例子:
Path path = new Path();
path.lineTo(200, 200);
path.moveTo(300,300);//moveTo
path.lineTo(400, 0);
canvas.drawPath(path, paint);
效果图:
Path path = new Path();
path.lineTo(200, 200);
path.setLastPoint(300,100);//setLastPoint
path.lineTo(400, 0);
canvas.drawPath(path, paint);
效果图:
当我们绘制线条之前,调用moveTo
和 setLastPoint
效果是一样的,都是对坐标原点(0,0)进行操作。
setLastPoint是重置上一次操作的最后一点,在执行完第一次lineTo的时候,最后一个点就是(200,200),setLastPoint更改(200,200)为(300,100),所以在执行的时候就是(300,100)到(400, 0)之间的连线了。
3、close
方法预览
public void close()
close
方法连接最后一个点和最初一个点(如果两个点不重合)形成一个闭合的图形。
path.moveTo(100,100);
path.lineTo(500,100);
path.lineTo(300,400);
path.close();
canvas.drawPath(path, paint);
效果图:
上图中可以看到lineTo(500,100)
直线和lineTo(300,400)
直线,而close
方法就是连接(300,400),(100,100)两点,形成一个闭合的区域。
注意:close的作用的封闭路径,如果连接最后一个点和最初一个点任然无法形成闭合的区域,那么close什么也不做。
quadTo,cubicTo
二次贝塞尔曲线以及三次贝塞尔曲线。
1、quadTo
方法预览
public void quadTo(float x1, float y1, float x2, float y2)
quadTo
方法其中 (x1,y1) 为控制点,(x2,y2)为结束点。
path.moveTo(100,400);
path.quadTo(300, 100, 400, 400);
canvas.drawPath(path, paint);
效果图:
2、cubicTo
方法预览:
public void cubicTo(float x1, float y1, float x2, float y2, float x3, float y3)
cubicTo
方法比quadTo
方法多了一个点坐标,那么其中(x1,y1) 为控制点,(x2,y2)为控制点,(x3,y3) 为结束点。
path.moveTo(100, 400);
path.cubicTo(100, 400, 300, 100, 400, 400);
canvas.drawPath(path, paint);
绘制的图形和上面的quadTo
绘制的图形是一样的。我们去掉moveTo
来看看运行的效果图:
如果你想了解贝塞尔曲线公式,请链接这里
addXxx和arcTo
主要是向Path
中添加基本图形以及区分addArc
和arcTo
1、添加基本图形
方法预览:
//圆形
addCircle(float x, float y, float radius, Path.Direction dir)
//椭圆
addOval(RectF oval, Path.Direction dir)
addOval(float left, float top, float right, float bottom, Path.Direction dir)
//矩形
addRect(RectF rect, Path.Direction dir)
addRect(float left, float top, float right, float bottom, Path.Direction dir)
//圆角矩形
addRoundRect(RectF rect, float rx, float ry, Path.Direction dir)
addRoundRect(float left, float top, float right, float bottom, float rx, float ry, Path.Direction dir)
addRoundRect(RectF rect, float[] radii, Path.Direction dir)
addRoundRect(float left, float top, float right, float bottom, float[] radii, Path.Direction dir)
我们仔细观察上面的方法,在最后都有一个Path.Direction
,这是个什么东东呢?
Direction的意思是方向,指导,趋势。点进去跟一下你会发现Direction是一个枚举类型(Enum)分别有CW(顺时针),CCW(逆时针)两个常量。那么它的作用主要有以下两点:
序号 | 作用 |
---|---|
1 | 在添加图形时确定闭合顺序(各个点的记录顺序) |
2 | 对自相交图形的渲染结果有影响 |
我们先来看看闭合顺序的问题,添加一个矩形看看:
path.addRect(100, 200, 500, 400, Path.Direction.CW);
canvas.drawPath(path, paint);
我将上面的代码CW
改成CCW
再运行一次,结果一模一样。
想看到区别就要用到setLastPoint
(重置最后一个点的坐标)。我们来这样变变代码:
path.addRect(100, 200, 500, 400, Path.Direction.CW);
path.setLastPoint(200,400);
canvas.drawPath(path, paint);
效果立马现行:
为什么图形会发生奇怪的变化呢。我们先来分析一下,绘制一个矩形至少需要对角线的两个点,根据这两个点计算出四条边然后把四条边按照顺序连接起来。上图的起始坐标是(100,200)按着顺时针的方向连接(500,200),(500,400),(100,400)最后连接(100,200)形成一个矩形。setLastPoint
是重置上一个操作点坐标及改变(100,400)为(200,400),所以出现了上图的效果。
接下来我们看看逆时针的情况:
path.addRect(100, 200, 500, 400, Path.Direction.CCW);
path.setLastPoint(400,300);
canvas.drawPath(path, paint);
效果图:
我们理清楚了闭合的问题,相交问题与设置填充模式有关。
我以addCircle
方法来讲解添加图形
path.addCircle(300,300,200, Path.Direction.CW);