Using AnimatedBuilder and ValueListenableBuilder for Efficient Animations in Flutter

Flutter provides a rich set of tools for creating beautiful and engaging animations. However, crafting performant animations can sometimes be challenging, especially when dealing with complex UI updates. Two widgets, AnimatedBuilder and ValueListenableBuilder, can significantly enhance the efficiency of your animations by rebuilding only the necessary parts of the widget tree.

What are AnimatedBuilder and ValueListenableBuilder?

AnimatedBuilder and ValueListenableBuilder are Flutter widgets designed to optimize animation performance by limiting widget rebuilds. They are particularly useful when only a small part of your UI needs to be updated based on an animation or a changing value.

  • AnimatedBuilder: Rebuilds a widget tree based on an Animation object. It avoids unnecessary rebuilds of parent widgets, focusing only on the animated parts.
  • ValueListenableBuilder: Rebuilds a widget tree when a ValueListenable object changes its value. Similar to AnimatedBuilder, it optimizes performance by isolating the rebuild to specific widgets.

Why Use AnimatedBuilder and ValueListenableBuilder?

  • Performance Optimization: Reduces the number of widget rebuilds, leading to smoother animations, especially on less powerful devices.
  • Code Readability: Makes your code more organized and easier to understand by clearly separating animation logic from the rest of the UI.
  • Resource Efficiency: Helps in managing and efficiently updating specific parts of the UI that are affected by changes in animated values or listenable values.

How to Implement Animations with AnimatedBuilder in Flutter

To demonstrate how to use AnimatedBuilder effectively, let’s create a simple animation where a container’s size changes.

Step 1: Set Up an AnimationController

Create an AnimationController to manage the animation’s timing and value. Make sure to dispose of the controller when the widget is no longer needed.


import 'package:flutter/material.dart';

class AnimatedBuilderExample extends StatefulWidget {
  @override
  _AnimatedBuilderExampleState createState() => _AnimatedBuilderExampleState();
}

class _AnimatedBuilderExampleState extends State<AnimatedBuilderExample> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    )..repeat(reverse: true);

    _animation = Tween<double>(begin: 100.0, end: 200.0).animate(_controller);
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('AnimatedBuilder Example'),
      ),
      body: Center(
        child: AnimatedBuilder(
          animation: _animation,
          builder: (context, child) {
            return Container(
              width: _animation.value,
              height: _animation.value,
              color: Colors.blue,
            );
          },
        ),
      ),
    );
  }
}

Explanation:

  • We initialize an AnimationController and an Animation in the initState method. The AnimationController is set to repeat the animation back and forth.
  • The Tween specifies the range of values for the animation (from 100.0 to 200.0 in this case).
  • The AnimatedBuilder takes the _animation and a builder function as arguments. The builder function is called every time the animation value changes.
  • The Container’s width and height are set to the current value of the animation.

How to Implement Animations with ValueListenableBuilder in Flutter

ValueListenableBuilder is used when you want to rebuild a part of the UI based on a ValueListenable object’s value changes.

Step 1: Set Up a ValueNotifier

Create a ValueNotifier to hold the value that will drive the animation. ValueNotifier is a simple class that extends ValueListenable.


import 'package:flutter/material.dart';

class ValueListenableBuilderExample extends StatefulWidget {
  @override
  _ValueListenableBuilderExampleState createState() => _ValueListenableBuilderExampleState();
}

class _ValueListenableBuilderExampleState extends State<ValueListenableBuilderExample> {
  final ValueNotifier<double> _valueNotifier = ValueNotifier<double>(100.0);

  @override
  void initState() {
    super.initState();
    // Simulate value changes every 2 seconds
    Future.periodic(Duration(seconds: 2), (timer) {
      _valueNotifier.value = (_valueNotifier.value == 100.0) ? 200.0 : 100.0;
    });
  }

  @override
  void dispose() {
    _valueNotifier.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('ValueListenableBuilder Example'),
      ),
      body: Center(
        child: ValueListenableBuilder<double>(
          valueListenable: _valueNotifier,
          builder: (context, value, child) {
            return Container(
              width: value,
              height: value,
              color: Colors.green,
            );
          },
        ),
      ),
    );
  }
}

Explanation:

  • We create a ValueNotifier<double> named _valueNotifier with an initial value of 100.0.
  • In the initState method, we simulate the value change every 2 seconds using Future.periodic, toggling between 100.0 and 200.0.
  • The ValueListenableBuilder takes _valueNotifier as the valueListenable and a builder function. The builder function is called whenever the value of _valueNotifier changes.
  • The Container’s width and height are set to the current value of _valueNotifier.

Practical Examples and Use Cases

1. Animated Opacity

Using AnimatedBuilder to animate the opacity of a widget:


import 'package:flutter/material.dart';

class AnimatedOpacityExample extends StatefulWidget {
  @override
  _AnimatedOpacityExampleState createState() => _AnimatedOpacityExampleState();
}

class _AnimatedOpacityExampleState extends State<AnimatedOpacityExample> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    )..repeat(reverse: true);

    _animation = Tween<double>(begin: 0.2, end: 1.0).animate(_controller);
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Animated Opacity Example'),
      ),
      body: Center(
        child: AnimatedBuilder(
          animation: _animation,
          builder: (context, child) {
            return Opacity(
              opacity: _animation.value,
              child: Container(
                width: 200,
                height: 200,
                color: Colors.red,
              ),
            );
          },
        ),
      ),
    );
  }
}

2. Animating Colors

Using ValueListenableBuilder to change the color of a container:


import 'package:flutter/material.dart';
import 'dart:math';

class AnimatedColorExample extends StatefulWidget {
  @override
  _AnimatedColorExampleState createState() => _AnimatedColorExampleState();
}

class _AnimatedColorExampleState extends State<AnimatedColorExample> {
  final ValueNotifier<Color> _colorNotifier = ValueNotifier<Color>(Colors.blue);

  @override
  void initState() {
    super.initState();
    // Simulate color changes every 2 seconds
    Future.periodic(Duration(seconds: 2), (timer) {
      _colorNotifier.value = Color.fromRGBO(
        Random().nextInt(256),
        Random().nextInt(256),
        Random().nextInt(256),
        1,
      );
    });
  }

  @override
  void dispose() {
    _colorNotifier.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Animated Color Example'),
      ),
      body: Center(
        child: ValueListenableBuilder<Color>(
          valueListenable: _colorNotifier,
          builder: (context, value, child) {
            return Container(
              width: 200,
              height: 200,
              color: value,
            );
          },
        ),
      ),
    );
  }
}

Best Practices

  • Dispose Controllers: Always dispose of AnimationController and ValueNotifier instances in the dispose method to prevent memory leaks.
  • Use Const Widgets: Use const widgets where possible inside the builder to prevent unnecessary rebuilds of the static parts of the UI.
  • Isolate Animated Regions: Ensure that AnimatedBuilder and ValueListenableBuilder wrap only the parts of the UI that need to be rebuilt.
  • Optimize Tween Values: Use appropriate Tween values to match the expected animation range, optimizing the smoothness and visual effect.

Conclusion

AnimatedBuilder and ValueListenableBuilder are invaluable tools for creating efficient animations in Flutter. By selectively rebuilding parts of the widget tree, they help optimize performance and improve code readability. Use these widgets to create complex and engaging animations while maintaining smooth and efficient UI updates.