Custom Painters & CustomPaint
7 questions foundWhat is the CustomPaint widget in Flutter, and when would you need to use it instead of relying on Flutter's existing built in widgets?
Beginner The CustomPaint widget lets you draw completely custom graphics directly onto the screen using low level drawing commands, such as lines, shapes, arcs, and text, giving you full pixel level control over exactly what gets rendered, and you would reach for it whenever a design requires a visual element that cannot reasonably be built by simply composing Flutter's existing built in widgets together, such as a custom chart, a unique progress indicator shape, or a specialized data visualization not covered by any standard widget.
CustomPaint(
size: Size(200, 200),
painter: MyCirclePainter(),
)
Real-world example A fitness app draws a custom circular progress ring showing a user's daily step count progress using CustomPaint, achieving a precise visual design that would have been very difficult to recreate using only standard built in widgets.
Common follow-ups: What is the difference between the painter and foregroundPainter properties of CustomPaint?;How does CustomPaint compare to simply using an Image or an SVG asset for a similar visual effect?
Flutter Widgets Fundamentals (Stateless & Stateful);Flutter Animations Basics
What is a CustomPainter class, and how does its paint method let you actually issue the specific drawing instructions rendered by a CustomPaint widget?
Beginner A CustomPainter is an abstract class that you extend to define your own actual drawing logic, and its required paint method receives a Canvas object representing the drawing surface along with the available Size to draw within, letting you call various drawing methods on that canvas, such as drawLine, drawCircle, or drawPath, to actually render your desired custom graphics, with the CustomPaint widget itself responsible for calling this paint method at the appropriate time whenever the widget needs to be rendered or redrawn.
class MyCirclePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()..color = Colors.blue;
canvas.drawCircle(size.center(Offset.zero), 50, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
Real-world example A weather app implements a custom painter that draws a simple animated sun icon using basic circle and line drawing commands directly on the canvas, rather than relying on a static bundled image asset.
Common follow-ups: What is the shouldRepaint method used for, and why is it important for performance?;What other drawing methods besides drawCircle does the Canvas class provide?
Flutter Performance Optimization;Flutter Animations Basics
Why is properly implementing the shouldRepaint method on a CustomPainter important for performance, and what happens if it always incorrectly returns true?
Intermediate The shouldRepaint method tells Flutter whether the custom painter actually needs to redraw itself again whenever its associated widget rebuilds, and correctly implementing it by comparing the old painter's relevant properties against the new painter's properties, only returning true when something that actually affects the visual output has genuinely changed, avoids unnecessary and potentially expensive repainting operations, whereas always returning true unconditionally means the potentially expensive paint method runs on every single rebuild regardless of whether anything visually relevant actually changed, which can noticeably hurt performance for a complex custom drawing.
@override
bool shouldRepaint(covariant MyChartPainter oldDelegate) {
return oldDelegate.dataPoints != dataPoints;
}
Real-world example A custom chart painter compares its old and new data points within shouldRepaint, correctly avoiding an unnecessary and computationally expensive redraw whenever a parent widget rebuilds for a completely unrelated reason that does not actually affect the chart's underlying data.
Common follow-ups: How expensive is it typically to actually compare complex objects within shouldRepaint?;What happens visually if shouldRepaint incorrectly returns false when it actually should return true?
Flutter Performance Optimization;Custom Widgets & Reusable Components
How do you draw more complex custom shapes using the Path class within a CustomPainter, rather than being limited to Flutter's simpler built in shapes like circles and rectangles?
Intermediate The Path class lets you construct an arbitrarily complex custom shape by describing a sequence of individual drawing movements, such as moving to a specific starting point, drawing a straight line to another point, or drawing a smooth curved line using a quadratic or cubic bezier curve, and once a Path object has been fully constructed describing your desired shape's outline, you can pass it to the canvas's drawPath method to actually render that complete custom shape, which enables drawing complex custom graphics like a custom wave shape or an irregular polygon that would not be possible using Flutter's simpler built in shape drawing methods alone.
final path = Path()
..moveTo(0, 50)
..quadraticBezierTo(100, 0, 200, 50)
..lineTo(200, 100)
..close();
canvas.drawPath(path, paint);
Real-world example A weather app draws a smooth, custom wavy line representing an animated water level using the Path class's quadraticBezierTo method, achieving a naturally curved visual effect that would be impossible using only straight lines.
Common follow-ups: What is the difference between a quadratic and a cubic bezier curve?;How do you fill a custom path shape with a color versus only drawing its outline?
Flutter Animations Basics;Implicit vs Explicit Animations
How can you combine a CustomPainter with an AnimationController to create a smoothly animated custom drawing that changes over time?
Intermediate Combining a CustomPainter with an AnimationController lets you create a genuinely animated custom drawing by having the painter's drawing logic reference the animation controller's current value, which changes smoothly over time, and repainting the canvas on every single animation frame update, meaning something like a custom loading spinner, an animated chart, or a progress indicator can be drawn with exactly the specific visual style your design calls for, while still being smoothly animated over time just like Flutter's own standard built in animated widgets.
class SpinnerPainter extends CustomPainter {
final double animationValue;
SpinnerPainter(this.animationValue);
@override
void paint(Canvas canvas, Size size) {
canvas.drawArc(Offset.zero & size, 0, animationValue * 2 * 3.14159, false, paint);
}
@override
bool shouldRepaint(covariant SpinnerPainter oldDelegate) => oldDelegate.animationValue != animationValue;
}
Real-world example A custom branded loading indicator uses an AnimationController driving a CustomPainter that draws a continuously rotating arc, achieving a completely unique visual style specifically matching the app's own distinct brand identity.
Common follow-ups: How do you properly dispose of an AnimationController to avoid a memory leak?;What is the difference between using AnimatedBuilder together with CustomPaint versus rebuilding an entire StatefulWidget on every frame?
Flutter Animations Basics;Implicit vs Explicit Animations
How can clipping and layering multiple CustomPaint widgets together help achieve more visually complex custom graphics, such as a custom chart with several distinct visual layers?
Advanced Complex custom graphics, such as a chart containing background grid lines, data points, and an overlaid highlighted selection area, are often best implemented by layering several separate CustomPaint widgets stacked on top of each other using a Stack widget, with each individual painter responsible for just one specific visual layer, and clipping, meaning constraining drawing operations to within a specific defined boundary shape, helps ensure each individual layer only actually draws within its intended designated area, keeping each painter's own individual drawing logic considerably simpler and easier to reason about compared to trying to handle every single visual layer within one single, large, complex painter.
Stack(
children: [
CustomPaint(painter: GridPainter()),
CustomPaint(painter: DataPointsPainter(data)),
],
)
Real-world example A financial charting app layers a separate grid line painter beneath a separate data points painter using a Stack widget, keeping each individual painter's own drawing logic focused and considerably simpler than attempting to handle the entire complex chart within just one single combined painter.
Common follow-ups: What are the performance implications of using several separate CustomPaint widgets layered together compared to just one combined painter?;How does canvas clipping actually work, and what specific methods are available for it?
Flutter Performance Optimization;Custom Widgets & Reusable Components
What performance considerations become important when using CustomPaint for a complex, frequently redrawn custom drawing, such as a real time data visualization updating many times per second?
Advanced A complex custom drawing that needs to redraw itself very frequently, such many times per second to reflect real time incoming data, requires careful attention to performance, including minimizing the actual computational work performed within the paint method itself since it may run on every single animation frame, avoiding creating brand new Paint or Path objects on every single call to paint when they could instead be created once and simply reused, and being especially precise and efficient about the shouldRepaint logic to guarantee a redraw is genuinely triggered only when the underlying data has actually meaningfully changed, since a poorly optimized paint method running many times per second can noticeably impact an app's overall frame rate and responsiveness.
class LivePainter extends CustomPainter {
final Paint _cachedPaint = Paint()..color = Colors.blue;
@override
void paint(Canvas canvas, Size size) {
canvas.drawCircle(Offset.zero, 10, _cachedPaint);
}
}
Real-world example A stock trading app reuses a single cached Paint object across every single frame of its rapidly updating real time price chart rather than creating a brand new one on every single call to paint, meaningfully improving the chart's overall frame rate during periods of very frequent price updates.
Common follow-ups: How do you actually measure and profile the performance of a specific CustomPainter using Flutter DevTools?;What is repaint boundary, and how can it help isolate expensive custom painting from the rest of the widget tree?
Flutter Performance Optimization;Flutter DevTools & Debugging