import 'dart:math' as math; import 'dart:ui' as ui; import 'package:flutter/material.dart'; // from https://stackoverflow.com/a/71099304/786656 class DashedPathPainter extends CustomPainter { final Path originalPath; final Color pathColor; final double strokeWidth; final double dashGapLength; final double dashLength; late DashedPathProperties _dashedPathProperties; DashedPathPainter({ required this.originalPath, required this.pathColor, this.strokeWidth = 3.0, this.dashGapLength = 5.0, this.dashLength = 10.0, }); @override void paint(Canvas canvas, Size size) { _dashedPathProperties = DashedPathProperties( path: Path(), dashLength: dashLength, dashGapLength: dashGapLength, ); final dashedPath = _getDashedPath(originalPath, dashLength, dashGapLength); canvas.drawPath( dashedPath, Paint() ..style = PaintingStyle.stroke ..color = pathColor ..strokeWidth = strokeWidth, ); } @override bool shouldRepaint(DashedPathPainter oldDelegate) => oldDelegate.originalPath != originalPath || oldDelegate.pathColor != pathColor || oldDelegate.strokeWidth != strokeWidth || oldDelegate.dashGapLength != dashGapLength || oldDelegate.dashLength != dashLength; Path _getDashedPath( Path originalPath, double dashLength, double dashGapLength, ) { final metricsIterator = originalPath.computeMetrics().iterator; while (metricsIterator.moveNext()) { final metric = metricsIterator.current; _dashedPathProperties.extractedPathLength = 0.0; while (_dashedPathProperties.extractedPathLength < metric.length) { if (_dashedPathProperties.addDashNext) { _dashedPathProperties.addDash(metric, dashLength); } else { _dashedPathProperties.addDashGap(metric, dashGapLength); } } } return _dashedPathProperties.path; } } class DashedPathProperties { double extractedPathLength; Path path; final double _dashLength; double _remainingDashLength; double _remainingDashGapLength; bool _previousWasDash; DashedPathProperties({ required this.path, required double dashLength, required double dashGapLength, }) : assert(dashLength > 0.0, 'dashLength must be > 0.0'), assert(dashGapLength > 0.0, 'dashGapLength must be > 0.0'), _dashLength = dashLength, _remainingDashLength = dashLength, _remainingDashGapLength = dashGapLength, _previousWasDash = false, extractedPathLength = 0.0; bool get addDashNext { if (!_previousWasDash || _remainingDashLength != _dashLength) { return true; } return false; } void addDash(ui.PathMetric metric, double dashLength) { // Calculate lengths (actual + available) final end = _calculateLength(metric, _remainingDashLength); final availableEnd = _calculateLength(metric, dashLength); // Add path final pathSegment = metric.extractPath(extractedPathLength, end); path.addPath(pathSegment, Offset.zero); // Update final delta = _remainingDashLength - (end - extractedPathLength); _remainingDashLength = _updateRemainingLength( delta: delta, end: end, availableEnd: availableEnd, initialLength: dashLength, ); extractedPathLength = end; _previousWasDash = true; } void addDashGap(ui.PathMetric metric, double dashGapLength) { // Calculate lengths (actual + available) final end = _calculateLength(metric, _remainingDashGapLength); final availableEnd = _calculateLength(metric, dashGapLength); // Move path's end point ui.Tangent tangent = metric.getTangentForOffset(end)!; path.moveTo(tangent.position.dx, tangent.position.dy); // Update final delta = end - extractedPathLength; _remainingDashGapLength = _updateRemainingLength( delta: delta, end: end, availableEnd: availableEnd, initialLength: dashGapLength, ); extractedPathLength = end; _previousWasDash = false; } double _calculateLength(ui.PathMetric metric, double addedLength) { return math.min(extractedPathLength + addedLength, metric.length); } double _updateRemainingLength({ required double delta, required double end, required double availableEnd, required double initialLength, }) { return (delta > 0 && availableEnd == end) ? delta : initialLength; } }