Skip to main content

Mastering Custom Painters and Canvas in Flutter

ยท 6 min read
Flutter Family
Flutter Family Core Team

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:

PropertyPurpose
colorDrawing color
styleFill or stroke
strokeWidthBorder size
shaderGradients
maskFilterBlur
blendModeComposition
strokeCapRounded 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.