【我的 Flutter 開源庫 】 - 虛線繪製庫 dash_painter

張風捷特烈發表於2021-05-10
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,
  ),
),
複製程式碼

本文就到這裡,這個工具還有很多優化擴充的空間,後面有時間或靈感時會持續維護,希望能對你有所幫助。

相關文章