🎨 Flutter Picasso: Painting Widgets from Scratch with CustomPaint & GestureDetector

"Once upon a runtime, in the land of Widgets and Pixels, a developer had a dream — to draw outside the box... literally."


✨ The Awakening of the Flutter Picasso

Meet Aryan — a curious developer with a spark in his eyes and too many Container() widgets in his codebase.

One fine Monday morning (because bugs usually sleep on Mondays), Aryan stared at his screen and sighed, “I’m tired of square widgets. I want to draw stuff. Real stuff. Like circles, triangles... maybe a unicorn.”

His inner artist, long suppressed under layers of ListView.builder(), whispered: “Use the brush, Aryan. Use the CustomPaint.”


🎯 What is CustomPaint?

Think of CustomPaint as your blank canvas in Flutter. It doesn’t assume anything. It doesn’t judge. It just says:

“Here’s a canvas, boss. Go nuts!”

It gives you low-level access to the drawing board — whether it’s lines, shapes, or even doodling a stickman eating pizza (hey, it’s your canvas).


🖌️ Let’s Paint: Building a Custom Circle Drawer

Imagine we’re creating a custom widget where the user can tap to draw circles on the screen. Yes. Like a toddler with crayons — only in code.


Step 1: The Canvas Whisperer (CustomPainter)

CirclePainter extends CustomPainter {
  final List<Offset> points;

  CirclePainter(this.points);

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.blue
      ..style = PaintingStyle.fill;

    for (final point in points) {
      canvas.drawCircle(point, 20.0, paint);
    }
  }

  @override
  bool shouldRepaint(CirclePainter oldDelegate) {
    return oldDelegate.points != points;
  }
}

Step 2: The Masterpiece Frame (CustomPaint + GestureDetector)

CircleCanvas extends StatefulWidget {
  @override
  _CircleCanvasState createState() => _CircleCanvasState();
}

_CircleCanvasState extends State<CircleCanvas> {
  final List<Offset> _points = [];

  void _addPoint(TapDownDetails details) {
    setState(() {
      _points.add(details.localPosition);
    });
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTapDown: _addPoint,
      child: CustomPaint(
        painter: CirclePainter(_points),
        child: Container(
          color: Colors.white,
          height: double.infinity,
          width: double.infinity,
        ),
      ),
    );
  }
}

🚀 PART 2: Level Up – Drawing Like a Pro (With Signature Pad & Animations!)

Aryan felt powerful.

Tapping to draw circles? Done.
But his inner artist wasn’t satisfied.

“What if I could draw freehand?”
“What if I could animate it?”
“What if... I could build a signature pad and launch my own DocuSign clone?”

Enter: Pan gestures and animation magic.


✍️ Freehand Drawing Like a Smooth Criminal

To draw freely as the user drags their finger — you switch from onTapDown to onPanUpdate.

Step 1: Update Your Painter

PathPainter extends CustomPainter {
  final List<Offset> points;

  PathPainter(this.points);

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.black
      ..strokeWidth = 3.0
      ..strokeCap = StrokeCap.round;

    for (int i = 0; i < points.length - 1; i++) {
      if (points[i] != null && points[i + 1] != null) {
        canvas.drawLine(points[i], points[i + 1], paint);
      }
    }
  }

  @override
  bool shouldRepaint(PathPainter oldDelegate) => true;
}

Step 2: Handle Drag with GestureDetector

FreeDrawCanvas extends StatefulWidget {
  @override
  _FreeDrawCanvasState createState() => _FreeDrawCanvasState();
}

_FreeDrawCanvasState extends State<FreeDrawCanvas> {
  List<Offset> _points = [];

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onPanUpdate: (details) {
        setState(() {
          RenderBox renderBox = context.findRenderObject() as RenderBox;
          _points.add(renderBox.globalToLocal(details.globalPosition));
        });
      },
      onPanEnd: (details) {
        _points.add(null); // Marks end of stroke
      },
      child: CustomPaint(
        painter: PathPainter(_points),
        child: Container(
          color: Colors.yellow[100],
          height: double.infinity,
          width: double.infinity,
        ),
      ),
    );
  }
}

Now you’ve got a live sketchpad — freehand, fluid, and fun. Great for:

  • Sketching apps

  • Signature pads

  • Drawing games

  • Whiteboard apps


🌈 Bonus: Animating Your Paint

Want your drawing to grow like it’s being sketched in real time? Try mixing in an AnimationController and slowly revealing the points one by one.

For example, build an animation that reveals paths or changes colors dynamically. Animation with CustomPainter is a deep rabbit hole — but a rewarding one.


🧠 Final Words from the Artist’s Studio

Whether you want to:

  • Draw circles on tap

  • Sketch freely with finger gestures

  • Build signature pads

  • Animate brush strokes

...CustomPaint + GestureDetector is your dynamic duo.

They're like Batman and Alfred:

  • One gives you power to control the pixels.

  • The other notifies you of every interaction.


📦 TL;DR – You Are Now the Flutter Picasso 🎨

  • CustomPainter: The brush.

  • CustomPaint: The canvas.

  • GestureDetector: The hand holding the brush.

  • You? The artist. The architect. The creator of chaos with style.

So go ahead — draw that unicorn, sign that virtual document, or paint your dreams in code. Flutter’s canvas is yours.

0
Subscribe to my newsletter

Read articles from Anmol Singh Tuteja directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Anmol Singh Tuteja
Anmol Singh Tuteja