A live painter demonstration reveals clipping and stroke-width effects immediately.
Reach for CustomPaint after layout widgets
CustomPaint is appropriate for charts, signatures, decorative paths, gauges, and other bespoke two-dimensional graphics. The surrounding widget still controls size and constraints; the painter only draws inside the provided Size.
Using Canvas for ordinary text and buttons discards accessibility and layout behaviour already supplied by widgets.
Implement paint and shouldRepaint
CustomPainter.paint receives a Canvas and Size, while shouldRepaint tells Flutter whether a new delegate changes pixels. Keep painter inputs immutable and compare every visual field in shouldRepaint.
Always returning true repaints unnecessarily, while always false leaves changed data invisible.
class ProgressRingPainter extends CustomPainter {
const ProgressRingPainter(this.progress);
final double progress;
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 12
..strokeCap = StrokeCap.round
..shader = const LinearGradient(colors: [Colors.blue, Colors.cyan])
.createShader(Offset.zero & size);
canvas.drawArc(Offset.zero & size, -1.5708, 6.2832 * progress.clamp(0, 1), false, paint);
}
@override
bool shouldRepaint(ProgressRingPainter oldDelegate) => oldDelegate.progress != progress;
}
Canvas coordinate sketches on Instagram connect logical size, transforms, strokes, and device-pixel ratios.
Draw lines circles rectangles and RRects
Canvas drawLine, drawCircle, drawRect, and drawRRect consume logical coordinates and a configured Paint. Derive positions from Size instead of hard-coded screen dimensions so the graphic scales.
Strokes extend around their path and may be clipped when drawn exactly on the boundary.

Build production-ready CustomPaint and Canvas features
The Complete Flutter Guide turns custompaint and canvas into maintainable app architecture, polished UI, and testable production code.
Enrol nowConstruct curves with Path
Path.moveTo starts a contour, lineTo adds segments, and quadraticBezierTo creates a curve through a control point. Close filled shapes explicitly and use drawPath with a fill or stroke Paint.
Mixing relative and absolute path methods without a plan produces displaced segments.
class MountainPainter extends CustomPainter {
const MountainPainter({required this.progress, super.repaint});
final double progress;
@override
void paint(Canvas canvas, Size size) {
final path = Path()
..moveTo(0, size.height)
..lineTo(size.width * .25, size.height * .55)
..quadraticBezierTo(size.width * .45, size.height * .2,
size.width * .65, size.height * .5)
..lineTo(size.width, size.height * .25)
..lineTo(size.width, size.height)
..close();
final paint = Paint()
..style = PaintingStyle.fill
..shader = const LinearGradient(colors: [Colors.indigo, Colors.cyan])
.createShader(Offset.zero & size);
canvas.save();
canvas.clipRect(Rect.fromLTWH(0, 0, size.width * progress, size.height));
canvas.drawPath(path, paint);
canvas.restore();
}
@override
bool shouldRepaint(MountainPainter old) => old.progress != progress;
}
Style strokes fills and shaders
Paint.style selects fill or stroke, strokeWidth and strokeCap shape outlines, and shader can hold a gradient created for the drawing bounds. Create the shader from the same Rect that the painted geometry occupies.
Allocating many Paint and Path objects inside repeated tight loops increases garbage collection pressure.
late final AnimationController controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
)..repeat(reverse: true);
CustomPaint(
size: const Size(320, 180),
painter: MountainPainter(progress: controller.value, repaint: controller),
)
A CustomPaint article on LinkedIn examines repaint ownership, hit testing, semantics, and profiling.

The Complete Flutter Guide: Build Android, iOS and Web apps
Go from scratch to building industry-standard apps with Riverpod, Firebase, animations, REST APIs, and more.
Enrol nowAnimate through the repaint Listenable
Pass an Animation to CustomPainter.repaint so ticks request painting without rebuilding the parent widget tree. Read animation.value in paint and keep shouldRepaint for delegate changes rather than timeline ticks.
Calling setState for every frame rebuilds layout even though only pixels changed.
Control coordinates, repainting, and hit behaviour
A painter receives the logical size assigned by its parent, so establish a clear coordinate system before drawing. Derive points from that size when the graphic should scale, or apply a canvas transform around a documented design space. Pair every save with restore when translating, rotating, or clipping. Unbalanced canvas state can alter later drawing commands and produce artefacts far from the original mistake.
Allocate stable paints, paths, and expensive measurements outside the hottest portion of paint where practical. The framework may call the method frequently during animation, and object churn can become visible as missed frames. shouldRepaint should compare every input that changes the pixels rather than always returning true or false. Immutable painter fields and meaningful equality make that decision easier to audit.
Pixel density and stroke placement affect sharpness. Flutter’s coordinates are logical pixels, while rasterisation targets physical pixels; thin horizontal or vertical lines may need positions chosen for the effective stroke width. Test several device-pixel ratios rather than nudging values until one emulator looks crisp. Paths also have fill rules and winding direction, which matter when shapes contain holes or overlap.
Custom pixels do not automatically provide interaction or accessibility. Put gesture detection around a deliberately sized target, translate pointer coordinates into the painter’s space, and implement hit testing when distinct regions have different actions. Supply semantic labels and values through surrounding widgets or custom semantics so the chart or control is not invisible to assistive technology. Verification should cover zero and extreme sizes, text direction, high contrast, large data sets, animation, and a golden image at representative ratios. Profile the actual painter before reaching for caching or layers; canvas saveLayer, broad shadows, and repeated path operations are common sources of unnecessary GPU work.
Text drawn directly on a canvas needs measurement, direction, scaling, and semantic duplication that ordinary widgets already solve. Prefer positioning a real text widget over the painting when it must remain selectable or accessible. If canvas text is essential, use a text painter with the current locale and text scale, then verify truncation and baselines across scripts. Never assume an English label’s width represents translated production copy.
A custom chart needs a transform in both directions. Data values map into the plot rectangle for painting, while pointer coordinates must map back into domain values for selection and tooltips. Keep margins, axis ranges, zoom, and text direction in one immutable geometry object so paint and hit testing cannot drift apart. Clamp degenerate ranges, skip non-finite values, and decide how missing samples break or bridge a path before the canvas receives any coordinates.
Accessibility should expose a chart summary and navigable data points outside the pixels. A focused point can use ordinary widgets or custom semantics while the painter renders the visual highlight from the same selected ID. For export, create the painter at the target logical size and pixel ratio rather than stretching a captured preview; gradients, strokes, and labels depend on those bounds. Tests should cover empty data, one point, extreme values, right-to-left labels, zoom transforms, and a pointer exactly on each edge. Profiling can then distinguish expensive path construction from unavoidable rasterisation.
Common mistakes
- Reach for CustomPaint after layout widgets: In CustomPaint drawing, using Canvas for ordinary text and buttons discards accessibility and layout behaviour already supplied by widgets; inspect this CustomPaint drawing cause before changing another CustomPaint drawing widget.
- Implement paint and shouldRepaint: In CustomPaint drawing, always returning true repaints unnecessarily, while always false leaves changed data invisible; inspect this CustomPaint drawing cause before changing another CustomPaint drawing widget.
- Draw lines circles rectangles and RRects: In CustomPaint drawing, strokes extend around their path and may be clipped when drawn exactly on the boundary; inspect this CustomPaint drawing cause before changing another CustomPaint drawing widget.
- Construct curves with Path: In CustomPaint drawing, mixing relative and absolute path methods without a plan produces displaced segments; inspect this CustomPaint drawing cause before changing another CustomPaint drawing widget.
- Style strokes fills and shaders: In CustomPaint drawing, allocating many Paint and Path objects inside repeated tight loops increases garbage collection pressure; inspect this CustomPaint drawing cause before changing another CustomPaint drawing widget.
- Animate through the repaint Listenable: In CustomPaint drawing, calling setState for every frame rebuilds layout even though only pixels changed; inspect this CustomPaint drawing cause before changing another CustomPaint drawing widget.
Frequently asked questions
How does reach for CustomPaint after layout widgets work in CustomPaint drawing?
For CustomPaint drawing, the starting rule is that customPaint is appropriate for charts, signatures, decorative paths, gauges, and other bespoke two-dimensional graphics. Apply this CustomPaint drawing rule first because reach for CustomPaint after layout widgets determines whether the CustomPaint drawing pattern fits.
Why does draw lines circles rectangles and RRects matter for CustomPaint drawing?
In CustomPaint drawing, derive positions from Size instead of hard-coded screen dimensions so the graphic scales. Keeping draw lines circles rectangles and RRects at the CustomPaint drawing call site exposes the CustomPaint drawing return value directly.
What failure should I test first in CustomPaint drawing?
First reproduce the CustomPaint drawing case where allocating many Paint and Path objects inside repeated tight loops increases garbage collection pressure. The improved painter reuses stable drawing objects where practical and applies strokes, fills, and shaders without avoidable allocation inside its tight loop.
How can I verify CustomPaint drawing before release?
Exercise animate through the repaint Listenable with real CustomPaint drawing inputs on every shipped platform. Inspect the final CustomPaint drawing callback or output; a successful CustomPaint drawing button tap alone is not proof.
Further reads
Connect CustomPaint drawing to the surrounding Flutter stack through these published tutorials:
- Flutter Development Guide 2026
- Flutter AnimationController and Tween Explained
- Flutter Gradients: LinearGradient, RadialGradient, and SweepGradient
- Flutter Animations: The Complete Guide With Code Examples
- Flutter Performance in 2026: Impeller, DevTools, and Rebuild Reduction
Sources: Flutter framework and Dart API documentation; CustomPaint drawing examples verified against current stable Flutter.