Custom painters let you draw shapes, lines, and designs directly on the screen. This helps you create unique visuals that normal widgets can't do.
Custom painters (CustomPaint) in Flutter
Introduction
Syntax
Flutter
class MyPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { // Drawing code here } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return false; } } // Use in widget tree: CustomPaint( painter: MyPainter(), child: Container(width: 100, height: 100), )
The paint method is where you draw on the canvas.
The shouldRepaint method tells Flutter when to redraw.
Examples
Flutter
class CirclePainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { final paint = Paint()..color = Colors.blue; canvas.drawCircle(size.center(Offset.zero), 40, paint); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => false; } CustomPaint( painter: CirclePainter(), child: SizedBox(width: 100, height: 100), )
Flutter
class LinePainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = Colors.red ..strokeWidth = 4; canvas.drawLine(Offset(0, 0), Offset(size.width, size.height), paint); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => false; } CustomPaint( painter: LinePainter(), child: SizedBox(width: 100, height: 100), )
Sample App
This app draws a simple yellow smiley face with black eyes and a smile using CustomPaint.
Flutter
import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text('CustomPaint Example')), body: Center( child: CustomPaint( size: const Size(150, 150), painter: SmileyPainter(), ), ), ), ); } } class SmileyPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { final paint = Paint()..color = Colors.yellow; canvas.drawCircle(size.center(Offset.zero), size.width / 2, paint); final eyePaint = Paint()..color = Colors.black; canvas.drawCircle(Offset(size.width * 0.35, size.height * 0.35), 10, eyePaint); canvas.drawCircle(Offset(size.width * 0.65, size.height * 0.35), 10, eyePaint); final smilePaint = Paint() ..color = Colors.black ..style = PaintingStyle.stroke ..strokeWidth = 5; final smilePath = Path(); smilePath.moveTo(size.width * 0.3, size.height * 0.65); smilePath.quadraticBezierTo(size.width * 0.5, size.height * 0.85, size.width * 0.7, size.height * 0.65); canvas.drawPath(smilePath, smilePaint); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => false; }
Important Notes
Always set shouldRepaint to false if your drawing does not change to improve performance.
Use canvas methods like drawCircle, drawRect, and drawPath to create shapes.
CustomPaint size can be set by its size property or by its child widget size.
Summary
Custom painters let you draw directly on the screen for unique visuals.
Implement paint to draw and shouldRepaint to control redrawing.
Use CustomPaint widget to add your painter to the widget tree.
Practice
1. What is the main purpose of a
CustomPainter in Flutter?easy
Solution
Step 1: Understand CustomPainter role
A CustomPainter is used to draw graphics directly on the canvas in Flutter apps.Step 2: Compare with other options
Options A, B, and C relate to standard UI, navigation, and input events, not drawing.Final Answer:
To draw custom graphics directly on the screen -> Option AQuick Check:
CustomPainter = Draw graphics [OK]
Hint: CustomPainter is for drawing, not UI controls or navigation [OK]
Common Mistakes:
- Confusing CustomPainter with widget layout
- Thinking it handles user input
- Assuming it manages navigation
2. Which method must you override when creating a
CustomPainter subclass to draw on the canvas?easy
Solution
Step 1: Identify CustomPainter methods
Thepaintmethod is where drawing commands are written.Step 2: Exclude unrelated methods
build,createState, andinitStatebelong to widgets, not painters.Final Answer:
paint -> Option AQuick Check:
Draw in paint() method [OK]
Hint: paint() draws; build() is for widgets [OK]
Common Mistakes:
- Using build() instead of paint()
- Confusing widget lifecycle methods with painter methods
- Forgetting to override paint()
3. What will be the visible result of this Flutter code snippet?
class MyPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()..color = Colors.red;
canvas.drawCircle(Offset(size.width / 2, size.height / 2), 50, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
Widget build(BuildContext context) {
return CustomPaint(
size: Size(200, 200),
painter: MyPainter(),
);
}medium
Solution
Step 1: Analyze paint method
The paint method draws a red circle at the center (width/2, height/2) with radius 50.Step 2: Understand shouldRepaint effect
shouldRepaint returning false means no redraw needed, but initial paint still happens.Final Answer:
A red circle centered in a 200x200 area -> Option BQuick Check:
Draw circle center = red circle visible [OK]
Hint: shouldRepaint false still draws initially [OK]
Common Mistakes:
- Thinking shouldRepaint false means no drawing
- Assuming circle is at top-left
- Confusing circle with square shape
4. Identify the error in this CustomPainter subclass:
class MyPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()..color = Colors.green;
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height), paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true
}
}medium
Solution
Step 1: Check syntax in shouldRepaint
The return statement lacks a semicolon aftertrue, causing a syntax error.Step 2: Verify other parts
paint method correctly draws a rectangle; canvas supports rectangles; shouldRepaint can return true.Final Answer:
Missing semicolon after return true -> Option CQuick Check:
Syntax error = missing semicolon [OK]
Hint: Check semicolons after return statements [OK]
Common Mistakes:
- Forgetting semicolon after return
- Thinking paint must return a value
- Assuming canvas can't draw rectangles
5. You want to create a CustomPainter that draws a circle only when the color changes. Which is the best way to implement
shouldRepaint to optimize performance?
class CirclePainter extends CustomPainter {
final Color color;
CirclePainter(this.color);
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()..color = color;
canvas.drawCircle(Offset(size.width/2, size.height/2), 40, paint);
}
@override
bool shouldRepaint(covariant CirclePainter oldDelegate) {
// What to write here?
}
}hard
Solution
Step 1: Understand shouldRepaint purpose
shouldRepaint returns true if the painter needs to redraw due to changes.Step 2: Compare colors to decide repaint
Repaint only if the new color differs from the old one, so compare oldDelegate.color and color.Step 3: Evaluate options
return oldDelegate.color != color; returns true when colors differ, optimizing redraw. return true; always repaints (less efficient). return false; never repaints (ignores changes). return oldDelegate.color == color; repaints when colors are same (wrong logic).Final Answer:
return oldDelegate.color != color; -> Option DQuick Check:
Repaint if color changed = oldDelegate.color != color [OK]
Hint: Repaint only if properties differ for efficiency [OK]
Common Mistakes:
- Always returning true wastes performance
- Always returning false ignores changes
- Using equality check wrongly in shouldRepaint
Recommended
Ready to prove it?
Pass the test and this pattern moves unknown
10 questions ~5 mineasy hard
