自定义View中Canvas之Path的详解

版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明作者和出处。 https://blog.csdn.net/Jsagacity/article/details/81741175

上一篇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技术文章



参考资料:https://www.jianshu.com/p/9ad3aaae0c63

猜你喜欢

转载自blog.csdn.net/Jsagacity/article/details/81741175