Android开发之Path详解

本文详细介绍Android中Path类的各种绘制方法,包括直线、曲线、圆弧等的绘制方式及布尔运算等高级特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、xxxTo方法

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)

1、lineTo(float x, float y)

绘制直线:从当前画笔位置出发,连接终点(x,y)。

示例如下:

path.lineTo(300,300);
canvas.drawPath(path,paint);

在这里插入图片描述

2、moveTo(float x, float y)

移动画笔:从当前画笔位置移动到终点(x,y)

示例如下:

path.moveTo(100,100);
path.lineTo(300,300);
canvas.drawPath(path,paint);

在这里插入图片描述

3、arcTo

3.1、arcTo(RectF oval, float startAngle, float sweepAngle)

绘制圆弧:从当前画笔位置出发,连线到内切矩形区域oval的圆弧的起始角度startAngle位置(X轴正方向为0°),顺时针旋转绘制圆弧,旋转度数为sweepAngle(sweepAngle为负时则逆时针旋转)

示例如下:

RectF rectF = new RectF(100,100,300,400);
path.arcTo(rectF,0,180);
canvas.drawPath(path,pathPaint);
3.2、arcTo(RectF oval, float startAngle, float sweepAngle,boolean forceMoveTo)

绘制圆弧:forceMoveTofalse,则用法和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);

在这里插入图片描述

3.3、arcTo(float left, float top, float right, float bottom, float startAngle,float sweepAngle, boolean forceMoveTo)

绘制圆弧:与arcTo(RectF oval, float startAngle, float sweepAngle,boolean forceMoveTo)用法一样

4、quadTo(float x1, float y1, float x2, float y2)

绘制二阶贝塞尔曲线:从path画笔当前位置出发,以(x₁,y₁)为控制点,向终点(x₂,y₂)绘制一条二阶贝塞尔曲线

示例如下:

path.moveTo(100,100);
path.quadTo(200,0,400,100);
canvas.drawPath(path,pathPaint);

在这里插入图片描述

5、cubicTo(float x1, float y1, float x2, float y2,float x3, float y3)

绘制三阶贝塞尔曲线:从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方法

在这里插入图片描述
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);

效果都是一样的:
在这里插入图片描述

三、addXxx方法

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的矩阵

1、addArc(RectF oval, float startAngle, float sweepAngle)

添加圆弧:addArc两个方法使用起来与arcTo(RectF oval, float startAngle, float sweepAngle,boolean forceMoveTo)中forceMoveTo设置为true效果一致,就不展开赘述了

2、addCircle(float x, float y, float radius, Direction dir)

添加圆形:以点(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);

在这里插入图片描述

3、addOval(RectF oval, Direction dir)

添加椭圆:在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);

在这里插入图片描述

4、addRect(RectF rect, Direction dir)

添加矩形:添加一个区域为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);

在这里插入图片描述

5、addRoundRect

5.1、addRoundRect(RectF rect, float rx, float ry, Direction dir)

添加统一圆角的圆角矩形:添加一个区域为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);

在这里插入图片描述

5.2、addRoundRect(RectF rect, float[] radii, Direction dir)

添加非统一圆角的圆角矩形:添加一个区域为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);

在这里插入图片描述

6、addPath(Path src)

添加一组名为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);

在这里插入图片描述

6.1、addPath(Path src, float dx, float dy)

添加一组名为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);

在这里插入图片描述

6.2、addPath(Path src, Matrix matrix)

添加一组名为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中

注: 这里大多数方法都比较简单,有些之前已经应用过,就不展开来讲了,下面介绍一下其中比较特别且常用的几个方法。

1、op(Path path, Op op) 布尔运算

前面的表格我们提到参数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);

在这里插入图片描述
下面我们对这两个Path进行布尔运算:

1.1、DIFFERENCE(差集)

若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的运算结果,效果都是一样的
在这里插入图片描述

1.2、REVERSE_DIFFERENCE(差集)

若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);
}

在这里插入图片描述

1.3、INTERSECT(交集)

若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);
}

在这里插入图片描述

1.4、UNION(并集)

若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);
}

在这里插入图片描述

1.5、XOR(异或)

若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);
}

在这里插入图片描述

2、setLastPoint(float dx, float dy)

当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);

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值