AdvancedAdvanced Flutter

What is CustomPainter used for?

Short Answer

CustomPainter lets you draw directly onto a canvas with low-level drawing primitives (paths, arcs, text, gradients) for visuals that no combination of standard widgets can produce — charts, custom shapes, signature pads.

You subclass `CustomPainter`, implement `paint(Canvas canvas, Size size)` with direct drawing calls (`canvas.drawPath`, `canvas.drawCircle`, `canvas.drawLine`, etc.), and implement `shouldRepaint(oldDelegate)` to tell Flutter whether it needs to repaint when something changes — returning `false` when nothing relevant changed avoids wasted repaints. You attach it to the tree via a `CustomPaint` widget, optionally giving it a `size` and a `child` to paint over/under.

This is the right tool when you need pixel-level control that widgets can't express — a custom progress ring, a chart, a hand-drawn signature capture, or a complex clipped/gradient shape. For most "slightly custom" visuals, reach for `ClipPath`, `ShaderMask`, or `DecoratedBox` first; CustomPainter is for when those genuinely aren't enough.

Code Example

class RingPainter extends CustomPainter {
  RingPainter(this.progress);
  final double progress; // 0.0 - 1.0

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.blue
      ..style = PaintingStyle.stroke
      ..strokeWidth = 8;
    final rect = Rect.fromLTWH(0, 0, size.width, size.height);
    canvas.drawArc(rect, -1.57, progress * 6.28, false, paint);
  }

  @override
  bool shouldRepaint(RingPainter old) => old.progress != progress;
}

Common Mistakes

  • ×Always returning `true` from `shouldRepaint`, forcing a repaint on every frame regardless of whether anything changed.
  • ×Doing expensive computation inside `paint()` instead of precomputing it before passing data to the painter.