Path
类中提供了一套xxxTo
方法,其作用是从起点到终点移动path画笔并绘制线(moveTo方法只移动path画笔不绘制线),线有直线和曲线
。
方法汇总如下表所示:
方法名 | 参数解析 |
---|---|
lineTo(float x, float y) | 绘制直线,x:终点x坐标值,y:终点y坐标值 |
moveTo(float x, float y) | 移动画笔,x:终点x坐标值,y:终点y坐标值 |
arcTo(RectF oval, float startAngle, float sweepAngle) | 绘制圆弧,oval:圆弧矩形区域,startAngle:起始角度,sweepAngle:圆弧旋转的角度 |
arcTo(RectF oval, float startAngle, float sweepAngle,boolean forceMoveTo) | 绘制圆弧,oval:圆弧矩形区域,startAngle:起始角度,sweepAngle:圆弧旋转的角度,forceMoveTo:是否在绘制圆弧前移动(moveTo)path画笔位置 |
arcTo(float left, float top, float right, float bottom, float startAngle,float sweepAngle, boolean forceMoveTo) | 绘制圆弧,left、top、right、bottom组成圆弧矩形区域,startAngle:起始角度,sweepAngle:圆弧旋转的角度,forceMoveTo:是否在绘制圆弧前移动(moveTo)path画笔位置 |
quadTo(float x1, float y1, float x2, float y2) | 绘制二阶贝塞尔曲线,控制点坐标:(x1,y1),终点坐标:(x2,y2) |
cubicTo(float x1, float y1, float x2, float y2,float x3, float y3) | 绘制三阶贝塞尔曲线,其中控制点1坐标为(x1,y1),控制点2坐标为(x2,y2),终点坐标为(x3,y3) |
绘制直线:从当前画笔位置出发,连接终点(x,y)。
示例如下:
path.lineTo(300,300);
canvas.drawPath(path,paint);
移动画笔:从当前画笔位置移动到终点(x,y)
示例如下:
path.moveTo(100,100);
path.lineTo(300,300);
canvas.drawPath(path,paint);
绘制圆弧:从当前画笔位置出发,连线到内切
矩形区域oval
的圆弧的起始角度startAngle
位置(X轴正方向为0°),顺时针旋转绘制圆弧,旋转度数为sweepAngle
(sweepAngle为负时则逆时针旋转)
示例如下:
RectF rectF = new RectF(100,100,300,400);
path.arcTo(rectF,0,180);
canvas.drawPath(path,pathPaint);
绘制圆弧:若
forceMoveTo
为false
,则用法和arcTo(RectF oval, float startAngle, float sweepAngle)
一样,绘制圆弧之前不会移动(moveTo)path画笔位置
。若为true
,先强制调用moveTo移动path画笔至圆弧起点
,再绘制圆弧。PS:如果调用arcTo(RectF oval, float startAngle, float sweepAngle,boolean forceMoveTo)方法之前没有对path进行任何操作,则forceMoveTo设置true或false效果都和设置true一样
示例如下,注意对比之间的差异:
RectF rectF = new RectF(100,100,300,400);
path.moveTo(100,100);
path.arcTo(rectF,0,180,false);
path.close();
canvas.drawPath(path,pathPaint);
RectF rectF = new RectF(100,100,300,400);
path.moveTo(100,100);
path.arcTo(rectF,0,180,true);
path.close();
canvas.drawPath(path,pathPaint);
RectF rectF = new RectF(100,100,300,400);
path.arcTo(rectF,0,180,false);
path.close();
canvas.drawPath(path,pathPaint);
绘制圆弧:与arcTo(RectF oval, float startAngle, float sweepAngle,boolean forceMoveTo)用法一样
绘制二阶贝塞尔曲线:从path画笔当前位置出发,以(x₁,y₁)为控制点,向终点(x₂,y₂)绘制一条二阶贝塞尔曲线
示例如下:
path.moveTo(100,100);
path.quadTo(200,0,400,100);
canvas.drawPath(path,pathPaint);
绘制三阶贝塞尔曲线:从path画笔当前位置出发,以(x1,y1)为控制点1,以(x2,y2)为控制点2,向终点(x3,y3)绘制一条三阶贝塞尔曲线
示例如下:
path.moveTo(100,100);
path.cubicTo(200,0,300,90,500,100);
canvas.drawPath(path,pathPaint);
圆形
其实也是由四段三阶贝塞尔曲线
组成,我们绘制其中两段看看效果即可,示例如下:
path.moveTo(300,200);
path.cubicTo(300,200+100*0.551915024494f,200+100*0.551915024494f,300,200,300);
path.moveTo(200-20,300);
path.cubicTo(200-100*0.551915024494f-20,300,100-20,200+100*0.551915024494f,100-20,200);
canvas.drawPath(path,pathPaint);
rXxxTo
方法的r意思是relative,即相对
的意思,方法有四个,如上图所示,其功能与对应的xxxTo方法一样,区别在于rXxxTo方法在绘制Path时是以当前path画笔位置为坐标原点,即相对于path画笔位置进行绘制,而xxxTo方法的坐标原点则与当前canvas坐标原点一致。
例如,我们使用xxxTo方法:
path.moveTo(100,100);
path.lineTo(300,300);
canvas.drawPath(path, pathPaint);
上述代码是从(100,100)到(300,300)绘制一条直线,那么如果用rXxxTo方法,相对(100,100)这个点绘制直线,则终点应为(300-100,300-100),即终点设为(200,200),如下所示:
path.moveTo(100,100);
path.rLineTo(200,200);
canvas.drawPath(path, pathPaint);
Path
类中还提供了一套addXxx
方法,字面理解就是添加一段相应的线,线可以是曲线
、完整的圆形
、矩形
等,甚至可以是另一组Path的线
。所谓添加的意思,我个人理解就是在绘制这段线前,移动(moveTo)path画笔位置到线的起始位置,然后再绘制线
,也就是说添加的这段线,与之前绘制的Path是分离的(除非后绘制的这段线的起始点与之前Path的终点一致)
。
方法汇总如下表所示:
方法名 | 参数解析 |
---|---|
addArc(RectF oval, float startAngle, float sweepAngle) | 添加圆弧,oval:圆弧矩形区域,startAngle:起始角度,sweepAngle:圆弧旋转的角度 |
addArc(float left, float top, float right, float bottom, float startAngle,float sweepAngle) | 添加圆弧,left、top、right、bottom组成圆弧矩形区域,startAngle:起始角度,sweepAngle:圆弧旋转的角度。ps:此方法在API 19以上有效 |
addCircle(float x, float y, float radius, Direction dir) | 添加圆形,x:圆形圆心的x坐标,y:圆形圆心的y坐标,radius:圆形半径,dir:线的闭合方向(CW顺时针方向 |
addOval(RectF oval, Direction dir) | 添加椭圆,oval:椭圆内切的矩形区域,dir:线的闭合方向(CW顺时针方向 |
addOval(float left, float top, float right, float bottom, Direction dir) | 添加椭圆,left、top、right、bottom组成椭圆内切的矩形区域,dir:线的闭合方向(CW顺时针方向 |
addRect(RectF rect, Direction dir) | 添加矩形,rect:矩形区域,dir:线的闭合方向(CW顺时针方向 |
addRect(float left, float top, float right, float bottom, Direction dir) | 添加矩形,left、top、right、bottom组成矩形区域,dir:线的闭合方向(CW顺时针方向 |
addRoundRect(RectF rect, float rx, float ry, Direction dir) | 添加统一圆角的圆角矩形,rect:矩形区域,rx:椭圆圆角的横轴半径,ry:椭圆圆角的纵轴半径,dir:线的闭合方向(CW顺时针方向 |
addRoundRect(float left, float top, float right, float bottom, float rx, float ry,Direction dir) | 添加统一圆角的圆角矩形,left、top、right、bottom组成矩形区域,rx:椭圆圆角的横轴半径,ry:椭圆圆角的纵轴半径,dir:线的闭合方向(CW顺时针方向 |
addRoundRect(RectF rect, float[] radii, Direction dir) | 添加非统一圆角的圆角矩形,rect:矩形区域,radii:矩形四个椭圆圆角的横轴半径和纵轴半径的数组,一共8个数值,dir:线的闭合方向(CW顺时针方向 |
addRoundRect(float left, float top, float right, float bottom, float[] radii,Direction dir) | 添加非统一圆角的圆角矩形,left、top、right、bottom组成矩形区域,radii:矩形四个椭圆圆角的横轴半径和纵轴半径的数组,一共8个数值,dir:线的闭合方向(CW顺时针方向 |
addPath(Path src) | 添加一组Path,src:要添加的Path |
addPath(Path src, float dx, float dy) | 添加一组平移后的Path,src:要添加的Path,dx:平移的x坐标,dy:平移的y坐标 |
addPath(Path src, Matrix matrix) | 添加一组经过矩阵变换后的Path,src:要添加的Path,matrix:3x3的矩阵 |
添加圆弧:addArc两个方法使用起来与arcTo(RectF oval, float startAngle, float sweepAngle,boolean forceMoveTo)中forceMoveTo设置为true效果一致,就不展开赘述了
添加圆形:以点(x,y)为圆心,添加一个半径长为radius的圆形,绘制起始角度为0°(x轴方向),绘制方向通过dir的值而定,dir为CW时顺时针绘制,dir为CCW时逆时针绘制。
方法比较简单,主要是对比CW和CCW的区别,我们用canvas.drawTextOnPath方法突显顺时针和逆时针绘制的效果,示例如下:
path.addCircle(200,150,100, Path.Direction.CW);//顺时针绘制
canvas.drawPath(path,pathPaint);
canvas.drawTextOnPath("绘制顺序", path, 0, 0, paint);
path.addCircle(200,150,100, Path.Direction.CCW);//逆时针绘制
canvas.drawPath(path,pathPaint);
canvas.drawTextOnPath("绘制顺序", path, 0, 0, paint);
添加椭圆:在oval矩形区域中,添加一个内切的椭圆,绘制起始角度为0°(x轴方向),绘制方向通过dir的值而定,dir为CW时顺时针绘制,dir为CCW时逆时针绘制。
注:
addOval(RectF oval, Direction dir)和addOval(float left, float top, float right, float bottom, Direction dir)效果是一样的,就不分开讲了。
示例如下:
RectF rectF = new RectF(100,100,400,250);
path.addOval(rectF, Path.Direction.CW);
canvas.drawPath(path,pathPaint);
添加矩形:添加一个区域为rect的矩形,绘制起点为左上角,绘制方向通过dir的值而定,dir为CW时顺时针绘制,dir为CCW时逆时针绘制。
注:
addRect(RectF rect, Direction dir)和addRect(float left, float top, float right, float bottom, Direction dir)效果是一样的,就不分开讲了
示例如下:
RectF rectF = new RectF(100,100,400,250);
path.addRect(rectF, Path.Direction.CW);
canvas.drawPath(path,pathPaint);
canvas.drawTextOnPath("绘制顺序", path, 0, 0, paint);
添加统一圆角的圆角矩形:添加一个区域为rect的圆角矩形,四个角的圆角大小一致,圆角的横轴半径为rx,纵轴半径为ry,dir为CW时顺时针绘制,绘制起点为左下角,dir为CCW时逆时针绘制,绘制起点为左上角(注意对比顺时针和逆时针的绘制起点)。
注:
addRoundRect(RectF rect, float rx, float ry, Direction dir)和addRoundRect(float left, float top, float right, float bottom, float rx, float ry,Direction dir)效果是一样的,就不分开讲了。
示例如下:
RectF rectF = new RectF(100,100,400,350);
path.addRoundRect(rectF,60,30,Path.Direction.CW);//顺时针
canvas.drawPath(path,pathPaint);
canvas.drawTextOnPath("绘制顺序", path, 0, 0, paint);
RectF rectF = new RectF(100,100,400,350);
path.addRoundRect(rectF,60,30,Path.Direction.CCW);//逆时针
canvas.drawPath(path,pathPaint);
canvas.drawTextOnPath("绘制顺序", path, 0, 0, paint);
添加非统一圆角的圆角矩形:添加一个区域为rect的圆角矩形,四个角的圆角的横轴和纵轴半径由radii数组中的8个数值决定,dir为CW时顺时针绘制,绘制起点为左下角,dir为CCW时逆时针绘制,绘制起点为左上角(注意对比顺时针和逆时针的绘制起点)。
注:
需要注意的是,如果radii数组中的元素小于8,系统会抛出错误信息radii[] needs 8 values,如下图所示:
注:
addRoundRect(RectF rect, float[] radii, Direction dir)和addRoundRect(float left, float top, float right, float bottom, float[] radii,Direction dir)效果是一样的,就不分开讲了。
RectF rectF = new RectF(100,100,400,350);
float[] radii = {60,30,30,70,100,100,10,40};
path.addRoundRect(rectF,radii,Path.Direction.CW);
canvas.drawPath(path,pathPaint);
canvas.drawTextOnPath("绘制顺序", path, 0, 0, paint);
添加一组名为src的Path副本
Path copyPath = new Path();
copyPath.moveTo(100,100);
copyPath.lineTo(150,200);
copyPath.quadTo(200,100,350,200);
copyPath.lineTo(100,250);
copyPath.close();
path.addPath(copyPath);
canvas.drawPath(path,pathPaint);
添加一组名为src的Path副本,然后将其进行平移,x轴上的平移距离为dx,y轴上的平移距离为dy
Path copyPath = new Path();
copyPath.moveTo(100,100);
copyPath.lineTo(150,200);
copyPath.quadTo(200,100,350,200);
copyPath.lineTo(100,250);
copyPath.close();
path.addPath(copyPath,300,0);//向x轴正方向平移300像素距离
canvas.drawPath(path,pathPaint);
添加一组名为src的Path副本,然后将其进行矩阵变换,矩阵为matrix(3x3的矩阵)
Path copyPath = new Path();
copyPath.moveTo(100,100);
copyPath.lineTo(150,200);
copyPath.quadTo(200,100,350,200);
copyPath.lineTo(100,250);
copyPath.close();
Matrix mMatrix = new Matrix();
mMatrix.setScale(1,-1);//以x轴为中线进行翻转
mMatrix.postRotate(90);//以坐标轴原点为中心点顺时针旋转90°
path.addPath(copyPath,mMatrix);
canvas.drawPath(path,pathPaint);
方法名 | 参数解析 |
---|---|
setFillType(FillType ft) | 设置Path的填充模式,ft:填充类型,有EVEN_ODD ,INVERSE_EVEN_ODD ,WINDING ,INVERSE_WINDING 四种模式 |
getFillType() | 获取当前Path的填充模式 |
isInverseFillType() | 判断当前Path填充模式是否是反向规则(INVERSE_XXX) |
toggleInverseFillType() | 当前Path的填充模式与其反向规则模式进行相互切换 |
方法名 | 参数解析 |
---|---|
close() | 封闭当前Path,连接起点和终点 |
reset() | 清空Path中的所有直线和曲线,保留填充模式设置,不保留Path上相关的数据结构 |
rewind() | 清空Path中的所有直线和曲线,不保留填充模式设置,但会保留Path上相关的数据结构,以便高效地复用 |
set(Path src) | 用名为src的Path替换当前的Path |
op(Path path, Op op) | 当前Path与名为path的Path进行布尔运算(取差集、交集、并集等),op:运算逻辑,有DIFFERENCE(差集),REVERSE_DIFFERENCE(差集),INTERSECT(交集),UNION(并集),XOR(异或)五种运算逻辑可选。ps:此方法在API 19以上有效 |
offset(float dx, float dy) | 平移当前Path,x轴上平移的距离为dx,y轴上平移的距离为dy |
offset(float dx, float dy, Path dst) | 平移名为dst的Path,x轴上平移的距离为dx,y轴上平移的距离为dy |
transform(Matrix matrix) | 对当前Path进行矩阵变换,矩阵为matrix(3x3矩阵) |
transform(Matrix matrix, Path dst) | 对名为dst的Path进行矩阵变换,矩阵为matrix(3x3矩阵) |
setLastPoint(float dx, float dy) | 设置终点,设置当前Path最后一个点的位置为(dx,dy) |
isEmpty() | 判断当前Path是否为空 |
isConvex() | 判断当前Path围成的图形是否凸多边形。ps:此方法在API 21以上有效 |
isRect(RectF rect) | 判断当前Path是否为矩形,如是,则将当前Path存储到新建的rect中 |
注:
这里大多数方法都比较简单,有些之前已经应用过,就不展开来讲了,下面介绍一下其中比较特别且常用的几个方法。
前面的表格我们提到参数op共有五种运算逻辑可选,下面我们就来看看这五种运算逻辑是如何影响两个Path之间的关系的,我们先用不同的颜色绘制出一个矩形和一个圆形,观察一下它们的位置和关系:
Path path1 = new Path();
path1.addRect(100,100,300,300, Path.Direction.CW);
pathPaint.setColor(Color.GREEN);
canvas.drawPath(path1,pathPaint);
Path path2 = new Path();
path2.addCircle(300,250,100,Path.Direction.CW);
pathPaint.setColor(Color.RED);
canvas.drawPath(path2,pathPaint);
若op方法的调用关系为path1.op(path2, Path.Op.DIFFERENCE),则运算结果是path1减去与path2的交集后剩下的部分,即path1与path2的并集减去path2部分
Path path1 = new Path();
path1.addRect(100,100,300,300, Path.Direction.CW);
Path path2 = new Path();
path2.addCircle(300,250,100,Path.Direction.CW);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
path1.op(path2, Path.Op.DIFFERENCE);//path1与path2进行布尔运算,结果保存至path1
canvas.drawPath(path1,pathPaint);
}
//也可以这样写
Path path3 = new Path();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
path3.op(path1,path2,Path.Op.DIFFERENCE);//path1与path2进行布尔运算,结果保存至path3
canvas.drawPath(path3,pathPaint);
}
可以用path1.op直接运算,也可以新建一个path3保存path1和path2的运算结果,效果都是一样的
若op方法的调用关系为path1.op(path2, Path.Op.REVERSE_DIFFERENCE),则运算结果是path2减去与path1的交集后剩下的部分,即path1与path2的并集减去path1部分
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
path1.op(path2, Path.Op.REVERSE_DIFFERENCE);//path1与path2进行布尔运算,结果保存至path1
canvas.drawPath(path1,pathPaint);
}
//也可以这样写
Path path3 = new Path();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
path3.op(path1,path2,Path.Op.REVERSE_DIFFERENCE);//path1与path2进行布尔运算,结果保存至path3
canvas.drawPath(path3,pathPaint);
}
若op方法的调用关系为path1.op(path2, Path.Op.INTERSECT),则运算结果是path1与path2的交集
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
path1.op(path2, Path.Op.INTERSECT);//path1与path2进行布尔运算,结果保存至path1
canvas.drawPath(path1,pathPaint);
}
//也可以这样写
Path path3 = new Path();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
path3.op(path1,path2,Path.Op.INTERSECT);//path1与path2进行布尔运算,结果保存至path3
canvas.drawPath(path3,pathPaint);
}
若op方法的调用关系为path1.op(path2, Path.Op.UNION),则运算结果是path1与path2的并集
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
path1.op(path2, Path.Op.UNION);//path1与path2进行布尔运算,结果保存至path1
canvas.drawPath(path1,pathPaint);
}
//也可以这样写
Path path3 = new Path();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
path3.op(path1,path2,Path.Op.UNION);//path1与path2进行布尔运算,结果保存至path3
canvas.drawPath(path3,pathPaint);
}
若op方法的调用关系为path1.op(path2, Path.Op.XOR),则运算结果是path1与path2的并集减去path1与path2的交集
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
path1.op(path2, Path.Op.XOR);//path1与path2进行布尔运算,结果保存至path1
canvas.drawPath(path1,pathPaint);
}
//也可以这样写
Path path3 = new Path();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
path3.op(path1,path2,Path.Op.XOR);//path1与path2进行布尔运算,结果保存至path3
canvas.drawPath(path3,pathPaint);
}
当Path在调用setLastPoint方法之前执行了某项操作时(绘制直线或曲线等),会将该操作的终点强制设置为(dx,dy)并连线(线的曲直取决于该操作本身是绘制直线还是曲线)
理解这个方法之前,首先我们要知道无论是使用addXxx方法还是xxxTo方法等在绘制过程中其实都是根据一堆点的集合,按顺序连线(直线或曲线)后绘制出Path最终的样子,setLastPoint方法正是改变此方法调用之前点的集合中最后一个点的位置。下面我们通过封闭图形(矩形)和非封闭图形(一段圆弧)的例子更好地理解这个方法。
//用绿线绘制一个矩形
path.addRect(new RectF(100,100,300,300), Path.Direction.CW);
pathPaint.setColor(Color.GREEN);
canvas.drawPath(path,pathPaint);
//强制设置最后一个点为(150,250),用红线绘制观察变化
path.reset();
path.addRect(new RectF(100,100,300,300), Path.Direction.CW);
path.setLastPoint(150,250);
pathPaint.setColor(Color.RED);
canvas.drawPath(path,pathPaint);
//用绿线绘制一个旋转180°的圆弧
path.addArc(new RectF(100,100,300,300),0,180);
pathPaint.setColor(Color.GREEN);
canvas.drawPath(path,pathPaint);
//强制设置最后一个点为(200,200),用红线绘制观察变化
path.reset();
path.addArc(new RectF(100,100,300,300),0,180);
path.setLastPoint(200,200);
pathPaint.setColor(Color.RED);
canvas.drawPath(path,pathPaint);