Chapter 6: The Over-painting Problem - Isolating Animations with RepaintBoundary
Of course. After optimizing heavy tasks, we'll tackle a more subtle issue where a small part of the UI is unnecessarily "repainting," affecting other parts: the Repaint Boundary.
The Scenario 📝
- System: A dashboard screen that has a running analog clock widget (an animation) and many other static widgets like charts and data tables.
- Problem: Although only the clock is moving, the entire screen feels a bit "laggy." When inspected with Flutter DevTools, the entire screen is being "repainted" 60 times per second.
The Problematic Code (Repaint Bleeding)
The clock's animation is controlled by an AnimationController, which calls setState() in its listener to update the UI.
dart
class DashboardScreen extends StatefulWidget {
@override
_DashboardScreenState createState() => _DashboardScreenState();
}
class _DashboardScreenState extends State<DashboardScreen> with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
)..repeat();
// Call setState() every time the animation ticks
_controller.addListener(() => setState(() {}));
}
// ... dispose controller ...
@override
Widget build(BuildContext context) {
// This build() runs 60 times per second!
return Scaffold(
appBar: AppBar(title: const Text("Dashboard")),
body: Column(
children: [
// MISTAKE: The clock shares a repaint boundary with other widgets
AnalogClock(controller: _controller), // This widget is animating
const VeryComplexStaticChart(), // This static widget gets repainted 60 times/sec!
const AnotherStaticWidget(), // This one too!
],
),
);
}
}The Bottleneck Analysis 🧐
- What is a Repaint Boundary?: Flutter is very smart; it tries to only
repaintthe pixels that actually change. It divides the UI into "layers" calledRepaint Boundaries. When a widget changes, Flutter only needs to repaint the "layer" containing that widget. - The Core Issue: By default, your entire screen might be in the same
Repaint Boundary. WhensetState()is called 60 times per second by theAnimationController, Flutter has to check and potentiallyrepaintthat entire large boundary, including the complex static chart widgets. - The Consequence: The CPU and GPU are overworked, repainting things that haven't changed, which causes jank and drains the battery.
The Solution: Use RepaintBoundary to Create a New "Layer" ✅
The Logic: We can proactively tell Flutter: "Create a separate canvas for this widget. Any changes inside it will only affect this canvas, so don't bother the other parts."
Optimized Code:
dart@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text("Dashboard")), body: Column( children: [ // OPTIMIZED: Wrap the animating widget in a RepaintBoundary RepaintBoundary( child: AnalogClock(controller: _controller), ), const VeryComplexStaticChart(), // This widget is now completely safe const AnotherStaticWidget(), // and will never be repainted again. ], ), ); }
Analysis of the Result ✨
- Isolated Repainting: The
RepaintBoundaryhas created a separate "layer" for the clock. Now, whensetState()is called, Flutter only needs to repaint that tiny layer. - Performance Restored: The other complex static widgets are no longer affected. The CPU and GPU are freed up, helping the entire application run smoother.
Verification Tool: Use Flutter DevTools and enable "Show Repaint Rainbow".
- Before Optimization: You would see the entire screen flashing with colors.
- After Optimization: You will only see a colored frame flashing around the clock.
Conclusion:
- The Golden Rule: "Use
RepaintBoundaryto isolate complex animations or frequently changing parts of the UI from the static parts of the screen." - This is a powerful optimization tool for precisely controlling what gets redrawn on the screen, which is especially important on screens with both dynamic and static content.