Using CustomPaint lets you draw graphics directly on the screen. This can be very fast if done right because it avoids many widget rebuilds. However, complex or frequent repaints can lower frame rates below 60fps, causing janky animations. Also, heavy painting uses more CPU and battery.
Custom painters (CustomPaint) in Flutter - Build, Publish & Deploy
To keep your app smooth at 60fps, only repaint when needed by using shouldRepaint wisely. Cache complex drawings in Picture objects or use RepaintBoundary widgets to isolate repaints. Avoid expensive operations inside the paint method. Keep your paint code simple and use efficient drawing commands.
Custom painters add minimal size to your app bundle because they are code-based drawings, not image assets. They do not significantly affect startup time. However, if you use many custom painters or complex shaders, it can increase code size slightly.
Flutter's CustomPaint works the same on iOS and Android because it uses Skia for rendering. However, performance may vary slightly due to device GPU differences. iOS devices often have ProMotion displays supporting 120fps, so optimizing for higher frame rates can improve user experience there.
- Ensure your custom painting does not cause excessive battery drain or overheating, as Apple and Google review app performance.
- Follow accessibility guidelines: provide alternatives if your custom painting conveys important information.
- Do not use custom painting to mimic system UI elements in a misleading way, which can cause rejection.
- Test on multiple devices to avoid crashes or slowdowns that can lead to store rejection.
Your app takes 5 seconds to load a screen with custom painting. What is likely wrong?
Answer: You might be doing heavy painting work synchronously on the main thread or repainting too often. Consider caching drawings, reducing paint complexity, or deferring expensive calculations.
Practice
CustomPainter in Flutter?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]
- Confusing CustomPainter with widget layout
- Thinking it handles user input
- Assuming it manages navigation
CustomPainter subclass to draw on the canvas?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]
- Using build() instead of paint()
- Confusing widget lifecycle methods with painter methods
- Forgetting to override paint()
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(),
);
}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]
- Thinking shouldRepaint false means no drawing
- Assuming circle is at top-left
- Confusing circle with square shape
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
}
}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]
- Forgetting semicolon after return
- Thinking paint must return a value
- Assuming canvas can't draw rectangles
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?
}
}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]
- Always returning true wastes performance
- Always returning false ignores changes
- Using equality check wrongly in shouldRepaint
Ready to prove it?
Pass the test and this pattern moves unknown
