上一篇Canvas的绘制图形只能绘制一些常规的,比如点、线、圆、椭圆、矩形等的。如果想要绘制更复杂的图形,那么就得靠Path了。
Path的定义:
Path类将多种符合路径(多个轮廓,如直线段、二次曲线、立方曲线等)封装在其内部的几何路径。
Path的绘制:
通过设置Paint.Style的FILL(只描内容)、STROKE(只描边)、FILL_AND_STROKE(描边和内容),然后调用canvas.drawPath(path, paint);Path还可以用于剪切或者在路径上绘制文本canvas.drawTextOnPath()。
Path有两个构造函数
Path() // 空的构造函数
Path(Path src) //创建一个新的路径,并且从src路径里赋值内容
Path一些常用的API:
功能分类 | Path的常用API | 备注 |
线操作 | lineTo、rLineTo | 绘制线 |
点操作 | moveTo、rMoveTo | 改变后面操作的起始点位置 |
setLastPoint | 改变前面操作中最后点的位置 | |
常规图形操作 | addRect | 绘制矩形 |
addRoundRect | 绘制圆角矩形 | |
addCircle | 绘制圆 | |
addOval | 绘制椭圆 | |
addArc、arcTo | 绘制圆弧 | |
闭合path操作 | close | 如果连接Path起点和终点能形成一个闭合图形,则会将起点和终点连接起来形成一个闭合图形 |
贝塞尔曲线 | quadTo、rQuadTo、cubicTo、rCubicTo | 贝塞尔曲线 |
线操作
lineTo(float x, float y) //添加当前点到目标点(x,y)构成的直线到path
rLineTo(float dx, float dy) //基于当前坐标系,即以path最后的那个点
//为坐标系原点(0,0),如果前面没有path的点,默认是屏幕左上角(0,0)
注:lineTo、rLineTo起始点默认是屏幕左上角的坐标系原点(0,0)
演示一下:
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE); //只描边
paint.setColor(Color.BLUE);
paint.setStrokeWidth(10f);
paint.setAntiAlias(true); //设置抗锯齿
paint.setDither(true); //设置防抖动
Path path = new Path();
//屏幕左上角(0,0)到(200,200)画一条直线
path.lineTo(200, 200);
//(200, 200)到(200, 400)画一条直线
path.lineTo(200, 400);
//以(200, 400)为起始点(0,0)偏移量为(200, 400)画一条直线,
//其终点坐标实际在屏幕的位置为(400, 800)
path.rLineTo(400, 800);
canvas.drawPath(path, paint);
结果:
点操作
moveTo(float x, float y) //改变接下来操作的起点位置为(x,y)
rMoveTo(float dx, float dy) //接下来要操作的起点位置为(x+dx,y+dy)
setLastPoint(float dx, float dy) //改变前一步操作点的位置,会改变前一步的操作
先对比一下moveTo()和rMoveTo()的区别,演示一下:
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE); //只描边
paint.setColor(Color.BLUE);
paint.setStrokeWidth(10f);
paint.setAntiAlias(true); //设置抗锯齿
paint.setDither(true); //设置防抖动
Path path = new Path();
//将坐标系原点从(0,0)移动到(200,200)
path.moveTo(200,200);
//画从(200,200)到(400,400)之间的直线
path.lineTo(400, 400);
// path.rMoveTo(100, 0); //暂时注释
path.lineTo(800, 400);
canvas.drawPath(path, paint);
暂时注释了rMoveTo()方法,看看结果:
然后解掉rMoveTo()方法的注释,意思是下一步的起点位置由(400, 400)变为(400+100, 400+0),即(500,400),结果:
再来看看moveTo()和setLastPoint()的区别,同样以上的代码,加上path.setLastPoint(800, 200)方法:
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE); //只描边
paint.setColor(Color.BLUE);
paint.setStrokeWidth(10f);
paint.setAntiAlias(true); //设置抗锯齿
paint.setDither(true); //设置防抖动
Path path = new Path();
//将坐标系原点从(0,0)移动到(200,200)
path.moveTo(200,200);
//画从(200,200)到(400,400)之间的直线
path.lineTo(400, 400);
path.setLastPoint(800, 200);
path.lineTo(800, 400);
canvas.drawPath(path, paint);
结果:
红线原本是设置setLastPoint(800, 200)之前的路径,在设置之后,影响到了前一步lineTo(400, 400)的操作,使之变成了lineTo(800, 200),结果就如图了。
得出结论: rMoveTo()影响的是后面操作的起点位置,并不会影响之前的操作;而setLastPoint()改变前一步操作最后一个点的位置,不仅影响前一步操作,同时也会影响后一步操作。
绘制常规图形
//绘制圆
addCircle(float x, float y, float radius, Direction dir)
//绘制椭圆
addOval(RectF oval, Direction dir)
addOval(float left, float top, float right, float bottom, Direction dir)
//绘制矩形
addRect(RectF rect, Direction dir)
addRect(float left, float top, float right, float bottom, Direction dir)
//绘制圆角矩形
addRoundRect(RectF rect, float rx, float ry, Direction dir)
addRoundRect(float left, float top, float right, float bottom, float rx, float ry,Direction dir)
addRoundRect(RectF rect, float[] radii, Direction dir)
addRoundRect(float left, float top, float right, float bottom, float[] radii,Direction dir)
所有API里面都有一个共同的参数Direction:
Direction | 备注 |
---|---|
Path.Direction.CCW | counter-clockwise ,沿逆时针方向绘制 |
Path.Direction.CW | clockwise ,沿顺时针方向绘制 |
Direction其用法演示:
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE); //只描边
paint.setColor(Color.BLUE);
paint.setStrokeWidth(2f);
paint.setTextSize(40f);
paint.setAntiAlias(true); //设置抗锯齿
paint.setDither(true); //设置防抖动
Path path = new Path();
//以(600,600)为圆心,300为半径绘制圆
//Path.Direction.CW顺时针绘制圆 Path.Direction.CCW逆时针绘制圆
path.addCircle(600, 600, 300, Path.Direction.CCW);
//沿path绘制文字
canvas.drawTextOnPath("我是Layne,在测试Direction,这是CCW逆时针绘制圆", path, 0, 0, paint);
canvas.drawPath(path, paint);
结果对比:
其他图形绘制示例:
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE); //只描边
paint.setColor(Color.BLUE);
paint.setStrokeWidth(10f);
paint.setAntiAlias(true); //设置抗锯齿
paint.setDither(true); //设置防抖动
Path path = new Path();
//以(400,200)为圆心,半径为100绘制圆
path.addCircle(550, 200, 100, Path.Direction.CW);
//绘制椭圆
RectF rectF = new RectF(100, 350, 500, 600);
//第一种方法绘制椭圆
path.addOval(rectF, Path.Direction.CW);
//第二种方法绘制椭圆
path.addOval(600, 350, 1000, 600, Path.Direction.CW);
//绘制矩形
RectF rect = new RectF(100, 650, 500, 900);
//第一种方法绘制矩形
path.addRect(rect, Path.Direction.CW);
//第一种方法绘制矩形
path.addRect(600, 650, 1000, 900, Path.Direction.CCW);
//绘制圆角矩形
RectF roundRect = new RectF(100, 950, 300, 1100);
//第一种方法绘制圆角矩形
path.addRoundRect(roundRect, 20, 20, Path.Direction.CW);
//第二种方法绘制圆角矩形
path.addRoundRect(350, 950, 550, 1100, 10, 50, Path.Direction.CCW);
//第三种方法绘制圆角矩形
//float[] radii中有8个值,依次为左上角,右上角,右下角,左下角的rx,ry
RectF roundRectT = new RectF(600, 950, 800, 1100);
path.addRoundRect(roundRectT, new float[]{50, 50, 50, 50, 50, 50, 0, 0}, Path.Direction.CCW);
//第四种方法绘制圆角矩形
path.addRoundRect(850, 950, 1050, 1100,new float[]{0, 0, 0, 0,50, 50, 50, 50}, Path.Direction.CCW);
canvas.drawPath(path, paint);
结果:
绘制圆弧
//绘制圆弧
addArc(RectF oval, float startAngle, float sweepAngle)
addArc(float left, float top, float right, float bottom, float startAngle,float sweepAngle)
//forceMoveTo:是否强制将path最后一个点移动到圆弧起点,
//true是强制移动,即为不连接两个点;false则连接两个点
arcTo(RectF oval, float startAngle, float sweepAngle,boolean forceMoveTo)
arcTo(RectF oval, float startAngle, float sweepAngle)
arcTo(float left, float top, float right, float bottom, float startAngle,float sweepAngle, boolean forceMoveTo)
addArc()和arcTo()都是添加圆弧到path中,不过他们之间还是有区别的。addArc()是直接添加圆弧到path中;而arcTo()会判断要绘制圆弧的起点与绘制圆弧之前path中最后的点是否是同一个点,如果不是同一个点的话,就会连接两个点。
演示一下:
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE); //只描边
paint.setColor(Color.BLUE);
paint.setStrokeWidth(20f);
paint.setAntiAlias(true); //设置抗锯齿
paint.setDither(true); //设置防抖动
Path path = new Path();
//在F(400, 200, 700, 500)区域内绘制一个270度的圆弧
RectF rectF = new RectF(400, 200, 700, 500);
path.addArc(rectF, 0, 270);
//在(400, 600, 600, 800)区域内绘制一个90度的圆弧,并且不连接两个点
RectF rectFTo = new RectF(400, 600, 700, 900);
path.arcTo(rectFTo, 0, 180, true); //等价于path.addArc(rectFTo, 0, 180);
canvas.drawPath(path, paint);
结果:
把上面代码修改成连接两点:
path.arcTo(rectFTo, 0, 180, false); //等价于path.addArc(rectFTo, 0, 180);
结果:
闭合path操作
如果path的重点和起始点不是同一个点的话,那么path.close()就会连接这两个点,形成一个封闭的图形。演示一下:
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE); //只描边
paint.setColor(Color.BLUE);
paint.setStrokeWidth(20f);
paint.setAntiAlias(true); //设置抗锯齿
paint.setDither(true); //设置防抖动
Path path = new Path();
//在F(400, 200, 700, 500)区域内绘制一个270度的圆弧
RectF rectF = new RectF(400, 200, 700, 500);
path.addArc(rectF, 0, 270);
// path.close(); //先注释
canvas.drawPath(path, paint);
结果:
接下来解掉path.close()的注释,再运行一次,结果:
贝塞尔曲线
贝塞尔曲线就麻烦多了,也复杂多了。这里不详细说明了,再网上找到一篇说明比较详细的文章。
安卓自定义View进阶 - 贝塞尔曲线
这里直接复制了里面的二阶曲线的实现:
public class CanvasView extends View {
private Paint mPaint;
private int centerX, centerY;
private PointF start, end, control;
public CanvasView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mPaint.setColor(Color.BLACK);
mPaint.setStrokeWidth(8);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setTextSize(60);
start = new PointF(0, 0);
end = new PointF(0, 0);
control = new PointF(0, 0);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
centerX = w / 2;
centerY = h / 2;
// 初始化数据点和控制点的位置
start.x = centerX - 200;
start.y = centerY;
end.x = centerX + 200;
end.y = centerY;
control.x = centerX;
control.y = centerY - 100;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 根据触摸位置更新控制点,并提示重绘
control.x = event.getX();
control.y = event.getY();
invalidate();//刷新View,重新绘制
return true;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制数据点和控制点
mPaint.setColor(Color.GRAY);
mPaint.setStrokeWidth(20);
canvas.drawPoint(start.x, start.y, mPaint);
canvas.drawPoint(end.x, end.y, mPaint);
canvas.drawPoint(control.x, control.y, mPaint);
// 绘制辅助线
mPaint.setStrokeWidth(4);
canvas.drawLine(start.x, start.y, control.x, control.y, mPaint);
canvas.drawLine(end.x, end.y, control.x, control.y, mPaint);
// 绘制贝塞尔曲线
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth(8);
Path path = new Path();
path.moveTo(start.x, start.y);
path.quadTo(control.x, control.y, end.x, end.y);
canvas.drawPath(path, mPaint);
}
}
结果:
关注个人公众号「Android 零零柒」,主推Android技术文章