paint method

  1. @override
void paint(
  1. Canvas canvas,
  2. Path path,
  3. Paint basePaint,
  4. double animationValue,
)
override

Paints the animated connection effect on the canvas.

Parameters:

  • canvas: The canvas to draw on
  • path: 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);
  }
}