Flutter绘制-07-Path

2,896 阅读8分钟

查看目录-->

直观理解

Path就是你要画的形状的线条路径,可以预想用笔画的过程:

  • 在纸上选择落笔点
  • 从落笔点向其他点画
    • 画直线
    • 画曲线
    • 画个形状
  • 一个形状画完后,可以抬笔移到另一个点,然后继续画。
  • 当线条画到最后,也就是最后一个点,可以看下最后一个点和当前线条第一个点是否一致,可以一致也可以不一致

Path就是你画的过程的路径记录。

方法汇总

选择落笔点:

调用move后,注意是抬笔后直接去两一个点,前后不连接:

  • moveTo(double x, double y),开启一个线条的起点,参数是绝对位置
  • relativeMoveTo(double dx, double dy),相对当前点位置,在(dx,dy)处重新下笔
画直线:
  • lineTo(double x, double y),画直线,到(x,y)
  • relativeLineTo(double dx, double dy),画直线,相对当前点位置(x,y),画到当前点位置的(dx,dy)处,按绝对位置,就是(x+dx,y+dy)

image.png

void _testMove(Canvas canvas, Size size) {
    Paint paint = new Paint();
    paint
      ..color = Colors.redAccent[200]
      ..style = PaintingStyle.stroke
      ..strokeCap = StrokeCap.round
      ..strokeWidth = 4;
    Path path = Path();
    path.moveTo(-0,0);
    path.lineTo(80, 80);
    path.relativeLineTo(0, -80);
    path.close();

    path.relativeMoveTo(-80,-160);
    path.lineTo(80, -80);
    path.relativeLineTo(0, -80);

    canvas.drawPath(path, paint);
  }
画弧线
  • void arcTo(Rect rect, double startAngle, double sweepAngle, bool forceMoveTo),当前path,连接一个独立的弧,用矩形描述弧线
    • forceMoveTo,当前path末端与新的弧的连接方式,false,表示二者相连,true表示直接move过去,不相连。通过参数确定的弧是一个独立的线,有自己的起始点,path的末端与弧的起点不一定一直,所以才有这个字段,用于确定二者是否相连。

image.png

void _drawArtto(Canvas canvas, Size size) {
    Paint paint = new Paint();
    paint
      ..color = Colors.redAccent[200]
      ..style = PaintingStyle.stroke
      ..strokeCap = StrokeCap.round
      ..strokeWidth = 4;

    Rect rect = Rect.fromCenter(center: Offset(0, 0), width: 300, height: 200);
    Path path = Path();
    path.moveTo(0, 0);
    path.lineTo(40, 40);
    path.arcTo(rect,0, pi * 1.2, false);
    canvas.drawPath(path, paint);

    canvas.translate(0, -200);

    path.reset();
    path.moveTo(0, 0);
    path.lineTo(40, 40);
    path.arcTo(rect,0, pi * 1.2, true);
    canvas.drawPath(path, paint);
    
  }
  • arcToPoint(Offset arcEnd, { Radius radius = Radius.zero, double rotation = 0.0, bool largeArc = false, bool clockwise = true, }) 指定一个绝对位置点,从当前path的末端画一个弧到该点 - largeArc,是否使用优弧, true 优弧,false 劣弧 - clockwise,是否顺时针画 - 这两个参数一块儿理解,顺时针画劣弧,顺时针画优弧,逆时针画劣弧,逆时针画优弧

image.png

void _drawArttoPoint(Canvas canvas, Size size) {
    Paint paint = new Paint();
    paint
      ..color = Colors.redAccent[200]
      ..style = PaintingStyle.stroke
      ..strokeCap = StrokeCap.round
      ..strokeWidth = 2;

    canvas.translate(0, -300);
    Path path = Path();
    path.moveTo(0, 0);
    path.lineTo(80, -40);
    path.arcToPoint(Offset(80, 40),
        radius: Radius.circular(60), largeArc: false, clockwise: false);
    canvas.drawPath(path, paint);

    canvas.translate(0, 150);
    path.reset();
    path.moveTo(0, 0);
    path.lineTo(80, -40);
    path.arcToPoint(Offset(80, 40),
        radius: Radius.circular(60), largeArc: true, clockwise: false);
    canvas.drawPath(path, paint);

    canvas.translate(0, 150);
    path.reset();
    path.moveTo(0, 0);
    path.lineTo(80, -40);
    path.arcToPoint(Offset(80, 40),
        radius: Radius.circular(60), largeArc: false, clockwise: true);
    canvas.drawPath(path, paint);

    canvas.translate(0, 150);
    path.reset();
    path.moveTo(0, 0);
    path.lineTo(80, -40);
    path.arcToPoint(Offset(80, 40),
        radius: Radius.circular(60), largeArc: true, clockwise: true);
    canvas.drawPath(path, paint);
  }

  • void relativeArcToPoint(Offset arcEndDelta, { Radius radius = Radius.zero, double rotation = 0.0, bool largeArc = false, bool clockwise = true, }) 指定一个相对当前点的位置点,从当前path的末端画一个弧到该点 - largeArc,是否使用优弧, true 优弧,false 劣弧 - clockwise,是否顺时针画 - 这两个参数一块儿理解,顺时针画劣弧,顺时针画优弧,逆时针画劣弧,逆时针画优弧

image.png

void _drawRelativeArttoPoint(Canvas canvas, Size size) {
    Paint paint = new Paint();
    paint
      ..color = Colors.redAccent[200]
      ..style = PaintingStyle.stroke
      ..strokeCap = StrokeCap.round
      ..strokeWidth = 2;

    canvas.translate(0, -300);
    Path path = Path();
    path.moveTo(0, 0);
    path.lineTo(80, -40);
    path.relativeArcToPoint(Offset(0, 80),
        radius: Radius.circular(60), largeArc: false, clockwise: false);
    canvas.drawPath(path, paint);

    canvas.translate(0, 150);
    path.reset();
    path.moveTo(0, 0);
    path.lineTo(80, -40);
    path.relativeArcToPoint(Offset(0, 40),
        radius: Radius.circular(60), largeArc: true, clockwise: false);
    canvas.drawPath(path, paint);

    canvas.translate(0, 150);
    path.reset();
    path.moveTo(0, 0);
    path.lineTo(80, -40);
    path.relativeArcToPoint(Offset(0, 40),
        radius: Radius.circular(60), largeArc: false, clockwise: true);
    canvas.drawPath(path, paint);

    canvas.translate(0, 150);
    path.reset();
    path.moveTo(0, 0);
    path.lineTo(80, -40);
    path.relativeArcToPoint(Offset(0, 40),
        radius: Radius.circular(60), largeArc: true, clockwise: true);
    canvas.drawPath(path, paint);
  }
贝塞尔线段

添加从当前点到曲线的贝塞尔线段。

  • void conicTo(double x1, double y1, double x2, double y2, double w) 给定点(x2,y2),使用控制点(x1,y1)和权重w:
    • 如果权重大于1,则曲线为双曲线;
    • 如果重量等于1,它就是抛物线;
    • 如果是小于1,它是一个椭圆。

相当于当前path末端、(x1,y1)、(x2 y2) 和w,共同控制一条曲线的生成。w越大,曲线越尖。

image.png

void _drawAconicTo(Canvas canvas, Size size) {
    final Offset p1 = Offset(80, -150);
    final Offset p2 = Offset(160, 0);

    Path path = Path();
    Paint paint = Paint()
      ..color = Colors.purpleAccent
      ..strokeWidth = 2
      ..style = PaintingStyle.stroke;
    path.moveTo(0, 0);
    path.lineTo(0, -50);
    //抛物线
    path.conicTo(p1.dx, p1.dy, p2.dx, p2.dy, 3);
    canvas.translate(0, -150);
    canvas.drawPath(path, paint);

    path.reset();
    path.moveTo(0, 0);
    path.lineTo(0, -50);
    path.conicTo(p1.dx, p1.dy, p2.dx, p2.dy, 1);
    canvas.translate(0, 150);
    canvas.drawPath(path, paint);

    path.reset();
    path.moveTo(0, 0);
    path.lineTo(0, -50);
    path.conicTo(p1.dx, p1.dy, p2.dx, p2.dy, 0.5);
    canvas.translate(0, 150);
    canvas.drawPath(path, paint);
  }
  • void relativeConicTo(double x1, double y1, double x2, double y2, double w) 和conicTo一样,只是点的描述从绝对位置变为了相对位置
画贝塞尔曲线
  • void cubicTo(double x1, double y1, double x2, double y2, double x3, double y3) 添加从当前点到给定点(x3,y3)的三次贝塞尔曲线段,使用控制点(x1,y1)和(x2,y2)。
  • void relativeCubicTo(double x1, double y1, double x2, double y2, double x3, double y3) 画三次贝塞尔曲线 相对位置
  • void quadraticBezierTo(double x1, double y1, double x2, double y2) 使用控制点(x1,y1)添加从当前点到给定点(x2,y2)的二次贝塞尔曲线段。
  • void relativeQuadraticBezierTo(double x1, double y1, double x2, double y2) 画二次贝塞尔曲线 相对位置

image.png

void _testCubicTo(Canvas canvas, Size size) {
    final Offset p1 = Offset(80, -150);
    final Offset p2 = Offset(160, 0);
    final Offset p3 = Offset(160, -160);

    Path path = Path();
    Paint paint = Paint()
      ..color = Colors.purpleAccent
      ..strokeWidth = 2
      ..style = PaintingStyle.stroke;
    path.moveTo(0, 0);
    path.lineTo(0, -50);
    //抛物线
    path.quadraticBezierTo(p1.dx, p1.dy, p2.dx, p2.dy);
    canvas.translate(0, -150);
    canvas.drawPath(path, paint);

    path.reset();
    path.moveTo(0, 0);
    path.lineTo(0, -50);
    path.cubicTo(p1.dx, p1.dy, p2.dx, p2.dy,p3.dx,p3.dy);
    canvas.translate(0, 250);
    canvas.drawPath(path, paint);

  }
画形状

path add形状后,与当前path没有连接关系,所add的图形都是一个个独立的形状。

  • void addArc(Rect oval, double startAngle, double sweepAngle) 添加一个弧形
  • void addOval(Rect oval) 添加一个椭圆
  • void addPath(Path path, Offset offset, {Float64List? matrix4}) 添加一个新的子路径,该子路径由给定的“path”偏移量和给定的“offset”组成。如果指定了“matrix4”,则在将矩阵转换为给定偏移量后,该路径将由该矩阵转换。矩阵是按列主顺序存储的4x4矩阵。
  • void extendWithPath(Path path, Offset offset, {Float64List? matrix4}) 在路径最后再添加一个其他路径
  • void addPolygon(List points, bool close) 添加一段折线
  • void addRect(Rect rect) 添加一个矩形
  • void addRRect(RRect rrect) 添加一个圆角矩形

image.png

void _testAdd(Canvas canvas, Size size) {

    Path path = Path();
    Paint paint = Paint()
      ..color = Colors.purpleAccent
      ..strokeWidth = 2
      ..style = PaintingStyle.stroke;
    path.moveTo(0, 0);
    path.lineTo(0, -50);
    path.addRect(Rect.fromPoints(Offset(50,50), Offset(100,100)));

    path.addRRect(RRect.fromRectXY(Rect.fromPoints(Offset(50,-150), Offset(100,-50)),10,10));

    path.addOval(Rect.fromPoints(Offset(150,50), Offset(180,100)));

    Path path2 = Path();
    path2.moveTo(0, 0);
    path2.addArc(Rect.fromPoints(Offset(0,0), Offset(-180,180)), 0, pi);
    path.addPath(path2, Offset(0,100));
//    canvas.translate(-150, -150);
    canvas.drawPath(path, paint);

  }
矩阵处理

注意调用方法后返回一个新的path,原来的path不变:

  • Path transform(Float64List matrix4) 返回一个新的path,该path是原path做完矩阵变化后的处理 应用的是矩阵的平移、缩放、旋转等操作。
  • Path shift(Offset offset) 返回一个新的path,该path是原path平移后的处理。可以理解为trasform的一个平移快捷方式方式

image.png

void _testTransform(Canvas canvas, Size size) {

    Path path = Path();
    Paint paint = Paint()
      ..color = Colors.purpleAccent
      ..strokeWidth = 2
      ..style = PaintingStyle.stroke;
    path.moveTo(0, 0);
    path.lineTo(0, -50);
    path.lineTo(-50, -50);
    Path path2 = path.transform(Float64List.fromList([
      1,    0, 0, 0,
      0,    1, 0, 0,
      0,    0, 1, 0,
      -100, -100, 0, 1,
    ]));
    canvas.drawPath(path, paint);
    canvas.drawPath(path2, paint);

  }
路径操作
  • close() 将路径起始点与终点连接在一起
  • reset() 路径清空
  • PathMetrics computeMetrics({bool forceClosed = false}) 获得路径轮廓,指的是从第一笔到路径最后一笔过程中的各种操作,比如lineto addArc等。moveTo不算在内。可以理解为是诸多操作的集合。可以进行遍历,遍历的每个item是一个操作的描述信息
    • PathMetrics foreach后,每个item是一个PathMetric
    • PathMetric
      • PathMetric.length,当前操作的路径总长度
      • PathMetric.getTangentForOffset(),获取某一操作的路径上的具体的点的信息,参数值范围 0-PathMetric.length;
      • Tangent t = pm.getTangentForOffset(pm.length * 0.5) Tangent是当前路径某一点的信息
        • position,点的位置信息
        • angle,点的角度信息
  • bool contains(Offset point) 判断一个点是不是在路径上
  • Rect getBounds() 反馈当前路径的最外层矩形

PathMetrics示例,在每个操作的路径一半的位置画一个点,下面有四个操作,因此有四个点: image.png

void _testComputeMetrics(Canvas canvas, Size size) {

    Path path = Path();
    Paint paint = Paint()
      ..color = Colors.purpleAccent
      ..strokeWidth = 2
      ..style = PaintingStyle.stroke;
    path.moveTo(0, 0);
    path.lineTo(0, -50);
    path.addRect(Rect.fromPoints(Offset(50,50), Offset(100,100)));

    path.addRRect(RRect.fromRectXY(Rect.fromPoints(Offset(50,-150), Offset(100,-50)),10,10));

    path.addOval(Rect.fromPoints(Offset(150,50), Offset(180,100)));

    PathMetrics pms = path.computeMetrics(forceClosed: false);
    pms.forEach((pm) {
      Tangent t = pm.getTangentForOffset(pm.length * 0.5);
      canvas.drawCircle(
          t.position, 5, Paint()..color = Colors.black);
    });
    canvas.drawPath(path, paint);

  }

一个乱七八糟的形状,🤦‍

重点看底下的代码使用,形状就算了,惭愧!!!

image.png

void _drawPath(Canvas canvas, Size size) {
    Paint paint = new Paint();
    paint
      ..color = Colors.redAccent[200]
      ..style = PaintingStyle.stroke
      ..strokeCap = StrokeCap.round
      ..strokeWidth = 4;
    Path path = Path();
    path.moveTo(0, 0);
    path.lineTo(80, 80);
    path.relativeLineTo(0, -80);
    path.arcTo(Rect.fromPoints(Offset(0,-50), Offset(100,-80)), 0, pi/2, false);
    path.arcToPoint(Offset(0,-100),largeArc: true);
    path.relativeArcToPoint(Offset(90,-90));
    path.conicTo(200, -200, 100, -100, 1);
    path.relativeConicTo(200, -200, 100, -100, 1);
    path.cubicTo(0, -100, -100, 0, -100, 100);
    path.relativeCubicTo(0, -100, -100, 0, -100, 100);
    path.quadraticBezierTo(100, 100, 200, 100);
    path.relativeQuadraticBezierTo(-100, 100, 0, 100);
    path.addArc(Rect.fromPoints(Offset(0,-50), Offset(100,-80)), 0, pi);
    path.addOval(Rect.fromPoints(Offset(0,-50), Offset(100,-80)));
    path.addPath(Path()..lineTo(100, 100), Offset.zero);
    path.addPolygon([Offset(0,0),Offset(100,100)], false);
    path.addRect(Rect.fromPoints(Offset(0,-50), Offset(100,-80)));
    path.addRRect(RRect.fromRectXY(Rect.fromPoints(Offset(0,-50), Offset(100,-80)), 100, 200));

    PathMetrics pm = path.computeMetrics();
    bool iscontaint = path.contains(Offset(0,0));
    print("iscontain = "+iscontaint.toString());

    path.extendWithPath(Path()..moveTo(-100, -200)..lineTo(100,100), Offset.zero);
    path.close();
    canvas.drawPath(path, paint);

    Rect bound = path.getBounds();
    canvas.drawRect(bound, paint);

    canvas.drawPath(path.transform(Float64List.fromList([
      1, 0, 0, 0,
      0, 1, 0, 0,
      0, 0, 1, 0,
      0, -200, 0, 1,
    ])), paint);

    canvas.drawPath(path.shift(Offset(-100,0)), paint);
//    path
//      ..relativeMoveTo(0, 0)
//      ..relativeLineTo(100, 120)
//      ..relativeLineTo(-10, -60)
//      ..relativeLineTo(
//        60,
//        -10,
//      )
//      ..close();
//    canvas.drawPath(path, paint);
//    path.reset();
//    path
//      ..relativeMoveTo(-200, 0)
//      ..relativeLineTo(100, 120)
//      ..relativeLineTo(-10, -60)
//      ..relativeLineTo(
//        60,
//        -10,
//      )
//      ..close();
//
//    canvas.drawPath(path, paint);
  }