paint method
Paints the animated connection effect on the canvas.
Parameters:
canvas: The canvas to draw onpath: The connection path (pre-computed and cached)basePaint: The base paint object with color, stroke width, etc.animationValue: The current animation value (0.0 to 1.0, repeating)
Implementations should use animationValue to create continuous
animations. The value continuously cycles from 0.0 to 1.0.
Implementation
@override
void paint(Canvas canvas, Path path, Paint basePaint, double animationValue) {
final pathMetrics = path.computeMetrics();
for (final metric in pathMetrics) {
final pathLength = metric.length;
// Interpret gradientLength as percentage if < 1, absolute pixels if >= 1
final gradientSpan = gradientLength < 1
? gradientLength * pathLength
: gradientLength;
// Calculate the animated position (moves forward along path, extends beyond end)
// Scale to go from -gradientSpan to pathLength + gradientSpan so it can fully enter and exit
final totalRange = pathLength + (2 * gradientSpan);
// Apply modulo on pixel distance for seamless looping at any speed
final pixelDistance = (animationValue * speed * totalRange) % totalRange;
final gradientCenter = pixelDistance - gradientSpan;
// Calculate start and end of the gradient segment
final gradientStart = gradientCenter - (gradientSpan / 2);
final gradientEnd = gradientCenter + (gradientSpan / 2);
// Only draw if gradient is visible on the path
if (gradientEnd < 0 || gradientStart > pathLength) {
// Gradient is completely off the path, draw base connection with configured opacity
if (connectionOpacity > 0) {
final basePaintWithOpacity = Paint()
..color = basePaint.color.withValues(alpha: connectionOpacity)
..strokeWidth = basePaint.strokeWidth
..style = PaintingStyle.stroke
..strokeCap = basePaint.strokeCap
..strokeJoin = basePaint.strokeJoin;
canvas.drawPath(path, basePaintWithOpacity);
}
continue;
}
// Clamp to visible portion
final visibleStart = gradientStart.clamp(0.0, pathLength);
final visibleEnd = gradientEnd.clamp(0.0, pathLength);
// Extract the visible gradient segment
final gradientPath = metric.extractPath(visibleStart, visibleEnd);
// Get tangent positions for shader direction
final startTangent = metric.getTangentForOffset(visibleStart);
final endTangent = metric.getTangentForOffset(visibleEnd);
if (startTangent == null || endTangent == null) continue;
// Create gradient colors
final gradientColors =
colors ??
[
basePaint.color.withValues(alpha: 0.0),
basePaint.color,
basePaint.color.withValues(alpha: 0.0),
];
// Generate color stops evenly distributed based on number of colors
final colorStops = List.generate(
gradientColors.length,
(i) => i / (gradientColors.length - 1),
);
// Draw the rest of the path first (underneath) with configured opacity
if (connectionOpacity > 0) {
final basePaintWithOpacity = Paint()
..color = basePaint.color.withValues(alpha: connectionOpacity)
..strokeWidth = basePaint.strokeWidth
..style = PaintingStyle.stroke
..strokeCap = basePaint.strokeCap
..strokeJoin = basePaint.strokeJoin;
if (visibleStart > 0) {
final beforePath = metric.extractPath(0, visibleStart);
canvas.drawPath(beforePath, basePaintWithOpacity);
}
if (visibleEnd < pathLength) {
final afterPath = metric.extractPath(visibleEnd, pathLength);
canvas.drawPath(afterPath, basePaintWithOpacity);
}
}
// Create linear gradient shader along the path segment
final shader = Gradient.linear(
startTangent.position,
endTangent.position,
gradientColors,
colorStops,
);
// Draw the gradient segment on top
final gradientPaint = Paint()
..shader = shader
..strokeWidth = basePaint.strokeWidth
..style = PaintingStyle.stroke
..strokeCap = basePaint.strokeCap
..strokeJoin = basePaint.strokeJoin;
canvas.drawPath(gradientPath, gradientPaint);
}
}