Skip to content

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 🧐

  1. What is a Repaint Boundary?: Flutter is very smart; it tries to only repaint the pixels that actually change. It divides the UI into "layers" called Repaint Boundaries. When a widget changes, Flutter only needs to repaint the "layer" containing that widget.
  2. The Core Issue: By default, your entire screen might be in the same Repaint Boundary. When setState() is called 60 times per second by the AnimationController, Flutter has to check and potentially repaint that entire large boundary, including the complex static chart widgets.
  3. 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 ✨

  1. Isolated Repainting: The RepaintBoundary has created a separate "layer" for the clock. Now, when setState() is called, Flutter only needs to repaint that tiny layer.
  2. 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 RepaintBoundary to 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.