🎨 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.
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
