阅读 1845

【我的 Flutter 开源库 】 - 虚线绘制库 dash_painter

0.前言

有很多人问我如何绘制虚线,一直没有这方面需求,没有太在意。现在想一下,通过路径测量实现虚线绘制应该是非常简单的。就抽了点空,顺手写个好用的虚线路径绘制工具,不然平时画个辅助线啥的确实挺费劲。


该绘制工具 dash_painter 已经上传到 pub

圆角矩形圆形

1. 实现的绘制

如下画板,通过路径绘制出一条直线,这应该是绘制最基础的东西了,不多介绍。下面来看一下如何实现将它变成一条虚线

class TolyPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    canvas.translate(size.width / 2, size.height / 2);
    Paint paint = Paint()..style = PaintingStyle.stroke
      ..color=Colors.orangeAccent..strokeWidth = 2;

    Path path = Path();
    path.moveTo(-100, 0);
    path.lineTo(100, 0);

    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(covariant TolyPainter oldDelegate) => false;
}
复制代码

2.绘制虚线 - level1

为了方便管理和拓展,可以将虚线对象分离出一个类 DashPainter。既然要画虚线,自然要明确相关的虚线参数,这里先来个简单的。  虚线的 单线长间距 分别使用 stepspan 表示,如下是一个 step:20, span: 10 的虚线。

class DashPainter {
  final double step;
  final double span;

  const DashPainter({this.step = 2, this.span = 2,});

  double get partLength => step + span;

  void paint(Canvas canvas, Path path, Paint paint) {
    final PathMetrics pms = path.computeMetrics();
    pms.forEach((PathMetric pm) {
      final int count = pm.length ~/ partLength;
      for (int i = 0; i < count; i++) {
        canvas.drawPath(
            pm.extractPath(partLength * i, partLength * i + step), paint);
      }
      final double tail = pm.length % partLength;
      canvas.drawPath(pm.extractPath(pm.length-tail, pm.length), paint);
    });
  }
}
复制代码

实现的逻辑也非常简单: 对路径进行 computeMetrics,然后根据份数遍历绘制截取的路径即可。使用时也非常简单,只要一句即可化实为虚

const DashPainter(span: 10, step: 20).paint(canvas, path, paint);
复制代码

通过控制 stepspan 参数,可以控制虚线的显示效果。

step:6, span: 6step:6, span: 4

其实到这里,就可以让 任意路径 虚线化,如下的圆角矩形和圆形:

final Path path = Path();
path.addRRect(RRect.fromRectAndRadius(
   Rect.fromCircle(center: Offset.zero, radius: 100),
   Radius.circular(20),
 ));
 const DashPainter(span: 4, step: 9).paint(canvas, path, paint);
复制代码
圆角矩形圆形

2.绘制虚线 - level2

除了虚线,有时还会有点划线 的需求,如下

  • 单点划线

  • 双点划线

  • 三点划线


代码实现如下,增加了 pointCountpointWidth两个属性,分别表示点划线数点划线长。其实整体思路是不变的, stepspan 还是那个含义,只不过单体的长度 pointLineLength 需要根据 pointCountpointWidth 进行加长,如下图所示:

class DashPainter {
  const DashPainter({
    this.step = 2,
    this.span = 2,
    this.pointCount = 0,
    this.pointWidth,
  });

  final double step;
  final double span;
  final int pointCount;
  final double pointWidth;

  void paint(Canvas canvas, Path path, Paint paint) {
    final PathMetrics pms = path.computeMetrics();
    final double pointLineLength = pointWidth ?? paint.strokeWidth;
    final double partLength =
        step + span * (pointCount + 1) + pointCount * pointLineLength;

    pms.forEach((PathMetric pm) {
      final int count = pm.length ~/ partLength;
      for (int i = 0; i < count; i++) {
        canvas.drawPath(
            pm.extractPath(partLength * i, partLength * i + step), paint,);
        for (int j = 1; j <= pointCount; j++) {
          final start =
              partLength * i + step + span * j + pointLineLength * (j - 1);
          canvas.drawPath(
            pm.extractPath(start, start + pointLineLength),
            paint,
          );
        }
      }
      final double tail = pm.length % partLength;
      canvas.drawPath(pm.extractPath(pm.length - tail, pm.length), paint);
    });
  }
}
复制代码

这样就可以完成一下很棒的东西,比如点画线圆

final Path path = Path();
path.moveTo(-200, 0);
path.lineTo(200, 0);
path.moveTo(0, -200);
path.lineTo(0, 200);
path.addOval(Rect.fromCircle(center: Offset.zero, radius: 100));
const DashPainter(
    span: 4, // 空格长
    step: 10, // 实线长
    pointCount: 2, // 点划线个数
    pointWidth: 2 // 点划线长
).paint(canvas, path, paint);
复制代码

3.装饰绘制

可能很多人不会自定义画板自己绘制,或只想简单地使用。其实除了 CustomPainter 还有其他地方有 canvas。比如 Decoration 。我们可以自定义 DashDecoration 的装饰,方便使用。这里只是一个简单的使用,可以基于此封装一下配置属性。

class DashDecoration extends Decoration {

  @override
  BoxPainter createBoxPainter([onChanged]) => const DashBoxPainter();
}

class DashBoxPainter extends BoxPainter {
  final Color color;
  const DashBoxPainter({this.color});
  
  @override
  void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
    canvas.save();
    final Paint paint = Paint()
      ..style = PaintingStyle.stroke
      ..color = Colors.orangeAccent
      ..strokeWidth = 1;
    final Path path = Path();

    canvas.translate(
      offset.dx + configuration.size.width / 2,
      offset.dy + configuration.size.height / 2,
    );

    final Rect zone = Rect.fromCenter(
      center: Offset.zero,
      width: configuration.size.width,
      height: configuration.size.height,
    );

    path.addRRect(RRect.fromRectAndRadius(
      zone,
      Radius.circular(20),
    ));

    const DashPainter(span: 4, step: 9).paint(canvas, path, paint);
    canvas.restore();
  }
}
复制代码

这属性配置些在库中已经封装,可以直接使用,如下实现一个渐变的单点画线圆角虚线框

Container(
  width: 100,
  height: 100,
  decoration: DashDecoration(
      pointWidth: 2,
      step: 5,
      pointCount: 1,
      radius: Radius.circular(15),
      gradient: SweepGradient(colors: [
        Colors.blue,
        Colors.red,
        Colors.yellow,
        Colors.green
      ])),
  child: Icon(
    Icons.add,
    color: Colors.orangeAccent,
    size: 40,
  ),
),
复制代码

本文就到这里,这个工具还有很多优化拓展的空间,后面有时间或灵感时会持续维护,希望能对你有所帮助。

文章分类
Android
文章标签