前言
- 自定义View是Android开发者必须了解的基础;而Path类的使用在自定义View绘制中发挥着非常重要的作用
- 网上有大量关于自定义View中Path类的文章,但存在一些问题:内容不全、思路不清晰、简单问题复杂化等等
- 今天,我将全面总结自定义View中Path类的使用,我能保证这是市面上的最全面、最清晰、最易懂的
- 文章较长,建议收藏等充足时间再进行阅读
- 阅读本文前请先阅读自定义View基础 - 最易懂的自定义View原理系列
目录
1. 简介
- 定义:路径,即无数个点连起来的线
-
作用:设置绘制的顺序 & 区域
Path只用于描述顺序 & 区域,单使用Path无法产生效果
-
应用场景:绘制复杂图形(如心形、五角星等等)
Path类封装了由直线和曲线(2、3次贝塞尔曲线)构成的几何路径。
2. 基础
2.1 开放路径与闭合路径的区别
2.2 如何判断点在图形内还是图形外
- 判断方法分为奇偶规则 & 非零环绕规则,具体介绍如下:
举例说明1:(奇偶规则)
由上图知:
- p1发出的射线与图形相交1个点,即奇数点,所以P1点在图形内
- p2发出的射线与图形相交2个点,即偶数点,所以P2点在图形内
举例说明2:(非零环绕数规则)
从上面方法分析到,任何图形都是由点连成线组成的,是具备方向的,看下图:(矩形是顺时针)
- p1发出的射线与图形相交1个点,矩形的右侧线从左边射到右边,环绕数-1,最终环绕数为-1,故p1在图形内部。
- p2发出的射线与图形相交2个点:矩形的右侧边从左边射到右边
环绕数-1;矩形的下侧边从右边射到左边,环绕数+1,最终环绕数为0.故p2在图形外部
3. 具体使用
3.1 对象创建
// 使用Path首先要new一个Path对象
// Path的起点默认为坐标为(0,0)
Path path = new Path();
// 特别注意:建全局Path对象,在onDraw()按需修改;尽量不要在onDraw()方法里new对象
// 原因:若View频繁刷新,就会频繁创建对象,拖慢刷新速度。
3.2 具体方法使用
因为path类的方法都是联合使用,所以下面将一组组方法进行介绍。
第一组:设置路径
采用moveTo()、setLastPoint()、lineTo()、close()
组合
moveTo(float x, float y) ;
lineTo(float x, float y) ;
close() ;
- 可使用
setLastPoint()
设置当前点位置(代替moveTo()
) - 二者区别:
实例介绍:(含setLastPoint()
与moveTo()
)
path.lineTo(400, 500);
path.moveTo(300, 300) ;
path.lineTo(900, 800);
path.close();
canvas.drawPath(path, mPaint1);
path.lineTo(400, 500);
path.setLastPoint(300, 300) ;
path.lineTo(900, 800);
path.lineTo(200, 700);
path.close();
canvas.drawPath(path, mPaint1);
- 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
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
关于重置路径
- 重置Path有两个方法:
reset()
和rewind()
- 两者区别在于:
方法 | 是否保留FillType设置 | 是否保留原有数据结构 |
---|
Path.reset() | 是 | 否 |
Path.rewind() | 否 | 是 |
FillType
影响显示效果;数据结构
影响重建速度- 所以一般选择
Path.reset()
由于较简单,此处不作过多展示。
第二组: 添加路径
采用addXxx()、arcTo()
组合
2.1 添加基本图形
-
作用:在Path路径中添加基本图形
如圆形路径、圆弧路径等等
-
具体使用
public void addArc (RectF oval, float startAngle, float sweepAngle)
public void arcTo (RectF oval, float startAngle, float sweepAngle)
public void arcTo (RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)
addCircle(float x, float y, float radius, Path.Direction dir)
addOval(RectF oval, Path.Direction dir)
addRect(RectF rect, Path.Direction dir)
addRoundRect(RectF rect, float rx, float ry, Path.Direction dir)
- 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
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
主要说一下dir这个参数:
dir = Direction = 图形的方向,为枚举类型:
- CW:clockwise,顺时针
- CCW:counter-clockwise,逆时针
图形的方向影响的是:
- 添加图形时确定闭合顺序(各个点的记录顺序)
- 图形的渲染结果(是判断图形渲染的重要条件)
图形绘制的本质:先画点,再将点连接起来。所以,点与点之间是存在一个先后顺序的;顺时针和逆时针用于确定这些点的顺序。
下面实例将说明:
// 为了方便观察,平移坐标系
canvas.translate(350, 500)
// 顺时针
path.addRect(0, 0, 400, 400, Path.Direction.CW)
// 逆时针
// path.addRect(0,0,400,400, Path.Direction.CCW)
canvas.drawPath(path,mPaint1)
关于加入图形路径后会影响路径的起点,实例如下:
canvas.translate(400,500);
path.lineTo(-100,0);
path.lineTo(-100,200);
path.lineTo(200,200);
path.close();
canvas.drawPath(path,paint);
canvas.translate(400,500);
path.lineTo(-100,0);
path.addCircle(0,0,100, Path.Direction.CCW);
path.lineTo(-100,200);
path.lineTo(200,200);
path.close();
canvas.drawPath(path,paint);
- 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
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
这里着重说明:添加圆弧路径(addArc与arcTo)
public void addArc (RectF oval, float startAngle, float sweepAngle)
public void arcTo (RectF oval, float startAngle, float sweepAngle)
public void arcTo (RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
具体请看下面实例
canvas.translate(350, 500);
path.lineTo(50, 200);
path.addArc(new RectF(200, 200, 300, 300), 0, 180);
path.arcTo(new RectF(200, 200, 300, 300), 0, 180);
canvas.drawPath(path, mPaint1);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
2.2 添加路径
public void addPath (Path src)
public void addPath (Path src, float dx, float dy)
public void addPath (Path src, Matrix matrix)
canvas.translate(350, 500);
Path pathRect = new Path();
Path pathCircle = new Path();
pathRect.addRect(-200, -200, 200, 200, Path.Direction.CW);
pathCircle.addCircle(0, 0, 100, Path.Direction.CW);
pathRect.addPath(pathCircle, 0, 200);
canvas.drawPath(pathRect,mPaint1);
- 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
- 26
- 27
- 28
- 29
第三组:判断路径属性
public boolean isEmpty ()
// 例子:
Path path = new Path();
path.isEmpty();
path.lineTo(100,100);
public boolean isRect (RectF rect)
// 实例
path.lineTo(0,400);
path.lineTo(400,400);
path.lineTo(400,0);
path.lineTo(0,0);
RectF rect = new RectF();
boolean b = path.isRect(rect);
public void set (Path src)
// 实例
// 设置一矩形路径
Path path = new Path();
path.addRect(-200,-200,200,200, Path.Direction.CW);
Path src = new Path();
src.addCircle(0,0,100, Path.Direction.CW);
path.set(src);
canvas.drawPath(path,mPaint);
public void offset (float dx, float dy)
// 方法2
// 参数dst:存储平移后的路径状态,但不影响当前path
// 可通过dst参数绘制存储的路径
public void offset (float dx, float dy, Path dst)
// 为了方便观察,平移坐标系
canvas.translate(350, 500);
path = new Path();
path.addCircle(0, 0, 100, Path.Direction.CW);
Path dst = new Path();
path.offset(400, 0, dst);
canvas.drawPath(path, mPaint1);
mPaint1.setColor(Color.RED);
canvas.drawPath(dst,mPaint1);
- 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
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
第四组:设置路径填充颜色
- 在Android中,有四种填充模式,具体如下
均封装在Path类中
填充模式 | 介绍 |
---|
EVEN_ODD | 奇偶规则 |
INVERSE_EVEN_ODD | 反奇偶规则 |
WINDING | 非零环绕数规则 |
INVERSE_WINDING | 反非零环绕数规则 |
请记住两个填充规律:
从我之前的文章(1)自定义View基础 - 最易懂的自定义View原理系列提到,图形是存在方向的(画图 = 连接点成的线 = 有连接顺序)。
path.setFillType()
path.getFillType()
path.isInverseFillType()
path.toggleInverseFillType()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
实例1:(奇偶规则)
// 为了方便观察,平移坐标系
canvas.translate(350, 500)
// 在Path中添加一个矩形
path.addRect(-200, -200, 200, 200, Path.Direction.CW)
// 设置Path填充模式为 奇偶规则
path.setFillType(Path.FillType.EVEN_ODD)
// 反奇偶规则
// path.setFillType(Path.FillType.INVERSE_EVEN_ODD)
// 画出路径
canvas.drawPath(path, mPaint1)
举例2:(非零环绕规则)
// 为了方便观察,平移坐标系
canvas.translate(550, 550)
// 在路径中添加大正方形
// 逆时针
path.addRect(-400, -400, 400, 400, Path.Direction.CCW)
// 在路径中添加小正方形
// 顺时针
// path.addRect(-200, -200, 200, 200, Path.Direction.CW)
// 设置为逆时针
path.addRect(-200, -200, 200, 200, Path.Direction.CCW)
// 设置Path填充模式为非零环绕规则
path.setFillType(Path.FillType.WINDING)
// 设置反非零环绕数规则
// path.setFillType(Path.FillType.INVERSE_WINDING)
// 绘制Path
canvas.drawPath(path, mPaint1)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
第五组:布尔操作
- 作用:两个路径Path之间的运算
- 应用场景:用简单的图形通过特定规则合成相对复杂的图形。
- 具体使用
boolean op (Path path, Path.Op op)
path1.op(path2, Path.Op.DIFFERENCE);
boolean op (Path path1, Path path2, Path.Op op)
path3.op(path1, path2, Path.Op.DIFFERENCE)
之间的运算方式(即Path.Op参数)如下
举例:
// 为了方便观察,平移坐标系
canvas.translate(550, 550)
// 画两个圆
// 圆1:圆心 = (0,0),半径 = 100
// 圆2:圆心 = (50,0),半径 = 100
path1.addCircle(0, 0, 100, Path.Direction.CW)
path2.addCircle(50, 0,100, Path.Direction.CW)
// 取两个路径的异或集
path1.op(path2, Path.Op.XOR)
// 画出路径
canvas.drawPath(path1, mPaint1)
4. 贝赛尔曲线
- 数据点:指路径的起始点和终止点;
- 控制点:决定了路径的弯曲轨迹;
- n+1阶贝塞尔曲线 = 有n个控制点;
- (1阶 = 一条直线,高阶可以拆解为多条低阶曲线)
Canvas提供了画二阶 & 三阶贝塞尔曲线的方法,下面是具体方法:
quadTo(float x1, float y1, float x2, float y2)
rQuadTo(float x1, float y1, float x2, float y2)
cubicTo(float x1, float y1, float x2, float y2, float x3, float y3)
rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3)
此处只简单介绍贝塞尔曲线,想详细理解可以参考这篇文章。
5. 总结
- 通过阅读本文,相信你已经全面了解Path类的使用;