Path即路径,可以将其想象成一条或多条线段,它们可能是直的,也可能是弯曲的。一般来说只有一条,即连贯的,除非以下两种情况:
1、调用
addXXX方法,直接添加固定形状的路径。
2、调用moveTo函数改变绘制起始位置。
1、Path的方向
将封闭形状(例如矩形,椭圆形)添加到Path时,需要指定Path.Direction,主要用于当多个封闭形状相交时,相交区域应该填充,还是镂空,需要配合FillType一起使用:
| Direction | 解释 |
|---|---|
| Path.Direction.CW | 顺时针 |
| Path.Direction.CCW | 逆时针 |
2、Path填充模式
setFillType(FillType fillType)
FileType有以下取值:WINDING(默认值)、EVEN_ODD 、INVERSE_WINDING 、INVERSE_EVEN_ODD
- 1、WINDING(默认值)
这里画了6个图形,1和2是关于y=300对称,方向相反,其余的都是以(300,300)为中心,他们的边界已用黑线在图中标记。
WINDING判断一个像素点是被填充,还是被镂空的规则如下:
1、从该点引出一条
假想线到外层区域,如图中红线。
2、计算从该点出发所穿过的边界,如果遇到顺时针,则+1,逆时针,则-1。
3、计算的结果,如果为0,则表示该点被镂空,否则该点被填充颜色。
- 2、EVEN_ODD
在原先代码中,设置FileType为EVEN_ODD:
EVEN_ODD判断一个像素点是被填充,还是被镂空的规则如下:
1、从该点引出一条
假想线到外层区域。
2、计算从该点出发所穿过的边界数量。
3、如果穿过数量为奇数,表示被填充,偶数则被镂空。
- 3、INVERSE_WINDING
在原先代码中,设置FileType为INVERSE_WINDING:
INVERSE_WINDING判断一个像素点是被填充,还是被镂空的规则如下:
1、从该点引出一条
假想线到外层区域。
2、计算从该点出发所穿过的边界,如果遇到顺时针,则+1,逆时针,则-1。
3、计算的结果,如果为0,则表示该点被填充颜色,否则该点被镂空。
- 4、INVERSE_EVEN_ODD
在原先代码中,设置FileType为INVERSE_EVEN_ODD:
INVERSE_EVEN_ODD判断一个像素点是被填充,还是被镂空的规则如下:
1、从该点引出一条
假想线到外层区域。
2、计算从该点出发所穿过的边界数量。
3、如果穿过数量为偶数,表示被填充,奇数则被镂空。
3、重置路径
rewind()
清除FillType及所有的直线、曲线、点的数据,但会保留数据结构,这样可以实现快速重用,提高一定的性能。
reset()
新建一个路径对象,它的所有数据空间都会被回收并重新分配,但不会清除FillType。
- 对比:
rewind()函数不会清除内存,但会清除FillType
reset()函数会清除内存,但不会清除FillType
4、PathMeasure
PathMeasure是Path的一个辅助类,虽然它跟绘制无关,但它有以下2个主要作用:
1、获取Path中某个子线段
2、获取Path中某个位置的坐标和切线角度
这意味着它配合属性动画,能够实现各种Path特效。
PathMeasure需要关联一个创建好的path,以及指定forceClose,表示Path是否需要闭合,它会影响path的测量结果。
- 1、获取路径的长度
float getLength()
- 2、分割路径
boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)
startWithMoveTo为true时,表示将dst中的终点,设置为本次分割结果的起始点。
截取的结果跟Path的方向有关。
会将截取的Path片段添加到dst中,而不是替换dst中的内容。
- 3、获取路径上某点的坐标和切线
3.1、方式1:
boolean getPosTan(float distance, float pos[], float tan[])
pos[0]、pos[1]分别表示当前distance位置的x,y坐标
tan表示该点的切线与单位圆相交的坐标点,可通过以下公式求出角度。
double degress = Math.atan2(tan[0], tan[1]) * 180.0 / Math.PI;
3.2、方式2:
boolean getMatrix(float distance, Matrix matrix, int flags)
这个函数用于得到路径上某一长度的位置以及该位置的正切值的矩阵。
flags可指定以下参数,并且可通过|来同时传入:
-
POSITION_MATRIX_FLAG:表示获取位置信息 -
TANGENT_MATRIX_FLAG:表示获取切线信息 -
4、跳转到下一条线段
boolean nextContour()
由于Path可以包含多条线段,但getLength()、getSegment()还是其他函数,都只针对第一条线段进行计算,而nextContour()就是用于跳转到下一条曲线的函数。
5、贝塞尔曲线
- 二阶贝塞尔曲线
void quadTo(float x1, float y1, float x2, float y2)
void rQuadTo(float dx1, float dy1, float dx2, float dy2)
- 三阶贝塞尔曲线
void cubicTo(float x1, float y1, float x2, float y2, float x3, float y3)
void rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3)
注意:函数前带r的贝塞尔函数,表示坐标位置都是相对位置,参照于Path中前一个线段的终点。
下面解释几种贝塞尔曲线在真实场景下的实际应用。
5.1、平滑线条
利用贝塞尔曲线,可以改善书写时的边缘锯齿。
public class DrawBezierView extends View {
private Path mPath;
private Paint mPaint;
private float mPreX;
private float mPreY;
public DrawBezierView(Context context) {
super(context);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(5);
mPath = new Path();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mPreX = event.getX();
mPreY = event.getY();
mPath.moveTo(mPreX, mPreY);
break;
case MotionEvent.ACTION_MOVE:
float endX = (event.getX() + mPreX) / 2;
float endY = (event.getY() + mPreY) / 2;
mPath.quadTo(mPreX, mPreY, endX, endY);
mPreX = event.getX();
mPreY = event.getY();
invalidate();
break;
}
return true;
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawPath(mPath, mPaint);
}
}