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 anAnimationobject. It avoids unnecessary rebuilds of parent widgets, focusing only on the animated parts.ValueListenableBuilder: Rebuilds a widget tree when aValueListenableobject changes its value. Similar toAnimatedBuilder, 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
AnimationControllerand anAnimationin theinitStatemethod. TheAnimationControlleris set to repeat the animation back and forth. - The
Tweenspecifies the range of values for the animation (from 100.0 to 200.0 in this case). - The
AnimatedBuildertakes the_animationand abuilderfunction as arguments. Thebuilderfunction 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_valueNotifierwith an initial value of 100.0. - In the
initStatemethod, we simulate the value change every 2 seconds usingFuture.periodic, toggling between 100.0 and 200.0. - The
ValueListenableBuildertakes_valueNotifieras thevalueListenableand abuilderfunction. Thebuilderfunction is called whenever the value of_valueNotifierchanges. - 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
AnimationControllerandValueNotifierinstances in thedisposemethod to prevent memory leaks. - Use Const Widgets: Use
constwidgets where possible inside thebuilderto prevent unnecessary rebuilds of the static parts of the UI. - Isolate Animated Regions: Ensure that
AnimatedBuilderandValueListenableBuilderwrap only the parts of the UI that need to be rebuilt. - Optimize Tween Values: Use appropriate
Tweenvalues 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.