Mastering Custom Painters and Canvas in Flutter
Learn how to unlock Flutter's low-level rendering capabilities using
CustomPainter,Canvas, and the Skia graphics engine to build charts, games, visualizations, animations, and highly customized user interfaces.
Table of Contentsโ
- Introduction
- Why Custom Painting Matters
- Understanding Flutter Rendering
- The Role of Canvas
- Introduction to CustomPainter
- Drawing Shapes
- Working with Paint
- Paths and Complex Shapes
- Gradients and Shaders
- Transformations
- Text Rendering
- Images
- Animations
- Hit Testing
- Performance Optimization
- Real-World Use Cases
- Best Practices
- Common Mistakes
- Production Checklist
- Conclusion
Introduction
Flutter provides an extensive collection of widgets, but not every interface can be built using rows, columns, containers, and buttons.
Some applications require:
- Charts
- Drawing tools
- Games
- Visualizations
- Custom progress indicators
- Maps
- Diagrams
- Particle effects
- Dynamic backgrounds
- Interactive graphics
This is where CustomPainter becomes one of Flutter's most powerful APIs.
Rather than composing widgets, CustomPainter gives developers direct access to Flutter's rendering engine through the Canvas API.
Why Custom Painting Matters
Most Flutter applications rely on widgets.
Widget rendering:
Widgets
โ
Elements
โ
Render Objects
โ
Painting
โ
GPU
Custom painters work closer to the rendering layer:
Widgets
โ
CustomPainter
โ
Canvas
โ
Skia
โ
GPU
Benefits include:
- Fine-grained control
- Better performance for complex graphics
- Advanced animations
- Custom visual effects
- Fewer widget trees
- Lower layout overhead
Understanding Flutter Rendering
Flutter uses the Skia graphics engine.
Everything visible on the screen is eventually painted:
- Text
- Images
- Buttons
- Icons
- Shapes
Even this:
Container(
color: Colors.blue,
)
Ultimately becomes drawing commands.
CustomPainter allows developers to write those commands directly.
The Role of Canvas
Canvas is a drawing surface.
Think of it as a blank sheet where you can draw:
- Lines
- Circles
- Paths
- Text
- Images
- Shadows
- Gradients
Example:
canvas.drawCircle(
Offset(100, 100),
50,
paint,
);
Introduction to CustomPainter
A painter extends CustomPainter.
Example:
class CirclePainter extends CustomPainter {
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.blue;
canvas.drawCircle(
size.center(Offset.zero),
50,
paint,
);
}
bool shouldRepaint(
covariant CirclePainter oldDelegate,
) {
return false;
}
}
Using it:
CustomPaint(
size: const Size(200, 200),
painter: CirclePainter(),
)
Understanding Paint
Paint controls how graphics appear.
final paint = Paint()
..color = Colors.red
..strokeWidth = 4
..style = PaintingStyle.stroke;
Properties:
| Property | Purpose |
|---|---|
| color | Drawing color |
| style | Fill or stroke |
| strokeWidth | Border size |
| shader | Gradients |
| maskFilter | Blur |
| blendMode | Composition |
| strokeCap | Rounded ends |
Drawing Basic Shapes
Rectangleโ
canvas.drawRect(
Rect.fromLTWH(20, 20, 100, 80),
paint,
);
Rounded Rectangleโ
canvas.drawRRect(
RRect.fromRectAndRadius(
rect,
const Radius.circular(20),
),
paint,
);
Circleโ
canvas.drawCircle(
center,
radius,
paint,
);
Lineโ
canvas.drawLine(
start,
end,
paint,
);
Working with Paths
Paths allow complex shapes.
Example:
final path = Path()
..moveTo(50, 0)
..lineTo(100, 100)
..lineTo(0, 100)
..close();
canvas.drawPath(path, paint);
Used for:
- Curves
- Charts
- Wave effects
- Maps
- Logos
Bezier Curves
Flutter supports quadratic curves.
path.quadraticBezierTo(
50,
0,
100,
100,
);
Cubic curves:
path.cubicTo(
20,
0,
80,
100,
100,
50,
);
Essential for smooth graphics.
Gradients
Linear gradient:
paint.shader = LinearGradient(
colors: [
Colors.blue,
Colors.purple,
],
).createShader(rect);
Radial gradient:
paint.shader = RadialGradient(
colors: [
Colors.orange,
Colors.red,
],
).createShader(rect);
Gradients add depth and modern visual design.
Shadows
canvas.drawShadow(
path,
Colors.black,
10,
true,
);
Useful for:
- Floating cards
- Icons
- Neumorphism
- Dynamic effects
Transformations
Canvas transformations affect subsequent drawing operations.
Translate:
canvas.translate(50, 50);
Rotate:
canvas.rotate(0.5);
Scale:
canvas.scale(1.5);
Save state:
canvas.save();
Restore:
canvas.restore();
Always restore transformations.
Text Rendering
Text can be drawn manually.
final textPainter = TextPainter(
text: TextSpan(
text: 'Flutter',
style: TextStyle(
color: Colors.black,
fontSize: 20,
),
),
textDirection: TextDirection.ltr,
);
textPainter.layout();
textPainter.paint(
canvas,
Offset.zero,
);
Useful for:
- Charts
- Labels
- Infographics
Drawing Images
Load image:
canvas.drawImage(
image,
Offset.zero,
paint,
);
Or:
canvas.drawImageRect(
image,
src,
dst,
paint,
);
Common uses:
- Games
- Editors
- Visualizations
Animating Custom Painters
Painters become extremely powerful when combined with animations.
Example:
class WavePainter extends CustomPainter {
final double progress;
WavePainter(this.progress);
void paint(
Canvas canvas,
Size size,
) {
// animation logic
}
bool shouldRepaint(
WavePainter oldDelegate,
) {
return progress != oldDelegate.progress;
}
}
Animation controller:
AnimationController(
vsync: this,
duration: Duration(seconds: 2),
);
Hit Testing
Painters can support interaction.
bool hitTest(Offset position) {
return true;
}
Useful for:
- Drawing apps
- Games
- Charts
- Interactive diagrams
Performance Optimization
Custom painting is powerful but requires care.
Use RepaintBoundaryโ
RepaintBoundary(
child: CustomPaint(
painter: ChartPainter(),
),
)
Reduces unnecessary repaints.
Minimize Object Creationโ
Avoid:
Paint()
Path()
Rect()
Inside every frame.
Cache reusable objects.
Avoid Expensive Operationsโ
Be careful with:
- Blur
- Large shadows
- Clipping
- Complex paths
Profile using Flutter DevTools.
Optimize shouldRepaintโ
Bad:
return true;
Better:
return progress != old.progress;
Real-World Use Cases
Custom painters are ideal for:
- Financial charts
- Music visualizers
- Signature apps
- Whiteboards
- Games
- Particle systems
- Interactive maps
- Infographics
- Custom loading indicators
- Animated backgrounds
Many advanced Flutter packages internally rely on CustomPainter.
Common Mistakes
Repainting Too Oftenโ
Avoid unnecessary repaints.
Creating Objects Per Frameโ
Cache:
- Paint
- Path
- TextPainter
When possible.
Forgetting save() and restore()โ
Transformations accumulate.
Always restore canvas state.
Overusing Widgetsโ
Sometimes one painter is faster than hundreds of widgets.
Choose appropriately.
Best Practices
- Keep painters focused.
- Separate drawing logic.
- Reuse paint objects.
- Profile rendering performance.
- Use immutable painter data.
- Prefer composition.
- Cache expensive calculations.
- Document drawing algorithms.
- Test on low-end devices.
- Use
RepaintBoundary.
Production Checklist
Renderingโ
- โ
Optimize
shouldRepaint - โ
Use
RepaintBoundary - โ Cache objects
Graphicsโ
- โ Minimize blur effects
- โ Optimize paths
- โ Reuse shaders
Animationโ
- โ Use efficient repaint logic
- โ Avoid allocations
- โ Profile frame times
Code Qualityโ
- โ Separate painter classes
- โ Add documentation
- โ Write tests
Example: Animated Wave Painter
class WavePainter extends CustomPainter {
final double animation;
WavePainter(this.animation);
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.blue;
final path = Path();
path.moveTo(0, size.height / 2);
for (double x = 0; x < size.width; x++) {
path.lineTo(
x,
size.height / 2 +
sin(
(x / 40) + animation,
) *
20,
);
}
canvas.drawPath(path, paint);
}
bool shouldRepaint(
WavePainter oldDelegate,
) {
return animation != oldDelegate.animation;
}
}
This approach can create:
- Water effects
- Audio visualizers
- Animated backgrounds
- Scientific graphs
Conclusion
CustomPainter opens an entirely different level of Flutter development. Instead of composing existing widgets, developers gain direct access to Flutter's rendering pipeline and can create highly customized visuals with exceptional performance.
By mastering Canvas, Paint, Path, gradients, transformations, and animation techniques, you can build interfaces that go far beyond traditional mobile UI patterns.
Whether you're creating charts, games, drawing tools, visual effects, or data visualizations, custom painting provides the flexibility and efficiency required for production-grade applications.
The key is balance: use widgets where they are sufficient, and use custom painting when you need precision, performance, and complete control over the rendering process.