Flutter List Animations with AnimatedList
Creating dynamic and engaging user interfaces is crucial for modern mobile applications. Flutter provides powerful tools to achieve this, and animating list changes is a common requirement. While a standard ListView can display data, it lacks built-in animations for item additions or removals. This is where Flutter's AnimatedList widget shines, offering a seamless and visually appealing experience when items are inserted, removed, or reordered within a list.
Understanding AnimatedList
The AnimatedList widget is a stateful widget designed to animate the insertion and removal of items. It works by providing a global key to its state, which allows you to programmatically trigger animations for list modifications. Instead of rebuilding the entire list, AnimatedList focuses on animating only the changed items, leading to smoother transitions and better performance.
Key Components of AnimatedList
itemBuilder: A required callback that builds the widget for each item in the list. It provides theBuildContext, theindexof the item, and anAnimation<double>that can be used to animate the item as it enters or exits the list.initialItemCount: The number of items that should be present in the list when it's first built.key: AGlobalKey<AnimatedListState>is essential. This key provides access to theAnimatedListState, which exposes methods likeinsertItemandremoveItemto control the list animations.
Setting Up AnimatedList
To use AnimatedList, you typically need a StatefulWidget to manage the list's state and a GlobalKey to interact with the AnimatedListState. Let's look at a basic setup:
import 'package:flutter/material.dart';
class AnimatedListExample extends StatefulWidget {
@override
_AnimatedListExampleState createState() => _AnimatedListExampleState();
}
class _AnimatedListExampleState extends State {
final GlobalKey _listKey = GlobalKey();
List _data = ['Item 0', 'Item 1', 'Item 2']; // Our list data
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('AnimatedList Demo'),
actions: [
IconButton(
icon: Icon(Icons.add),
onPressed: () => _insertItem(),
),
],
),
body: AnimatedList(
key: _listKey,
initialItemCount: _data.length,
itemBuilder: (context, index, animation) {
return _buildItem(_data[index], animation, index);
},
),
);
}
// Helper method to build each item
Widget _buildItem(String item, Animation animation, int index) {
return SizeTransition(
sizeFactor: animation,
child: Card(
margin: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
child: ListTile(
title: Text(item),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () => _removeItem(index),
),
),
),
);
}
void _insertItem() {
// We'll implement this next
}
void _removeItem(int index) {
// We'll implement this next
}
}
In this initial setup, we define a _data list to hold our actual items. The AnimatedList uses a GlobalKey and initialItemCount. The itemBuilder then calls _buildItem, passing the item, the animation, and its index. We use a SizeTransition here as a simple animation example, but you can use any transition widget like SlideTransition, FadeTransition, or combine them.
Adding Items to AnimatedList
To add an item, you first update your underlying data list and then call _listKey.currentState!.insertItem(). The insertItem method takes the index where the new item should be inserted and an optional duration. This triggers the animation for the new item entering the list.
// ... inside _AnimatedListExampleState
void _insertItem() {
final int newIndex = _data.length; // Add to the end
_data.add('New Item ${newIndex}'); // Update data source
_listKey.currentState!.insertItem(
newIndex,
duration: const Duration(milliseconds: 500),
);
}
// ... rest of the code
When insertItem is called, the itemBuilder for the new item (and potentially subsequent items if they shift) is called with the animation, allowing the new item to smoothly slide into place.
Removing Items from AnimatedList
Removing items is slightly more involved because you need to animate the item *leaving* the list. The removeItem method requires a builder argument that is responsible for building the widget of the item being removed. This builder receives the same animation as itemBuilder, allowing you to animate the item's exit.
// ... inside _AnimatedListExampleState
void _removeItem(int index) {
if (_data.isEmpty) return; // Prevent errors if list is empty or index invalid
final String removedItem = _data[index]; // Get the item before removing
// Remove from the data source immediately
_data.removeAt(index);
// Call removeItem on the AnimatedListState
_listKey.currentState!.removeItem(
index,
(context, animation) {
// This builder is for the item that is being removed
// It's crucial to return the widget as it was before removal
return _buildItem(removedItem, animation, index);
},
duration: const Duration(milliseconds: 500),
);
}
// ... rest of the code
Notice that inside the removeItem's builder, we reuse our existing _buildItem method. This ensures that the item being removed looks identical to its state before removal, making the animation visually consistent. The animation provided to this builder will run in reverse or from 1.0 down to 0.0, allowing you to animate the item shrinking, fading out, or sliding away.
Putting It All Together (Full Example)
Here's the complete code for a basic AnimatedList demonstrating add and remove operations:
import 'package:flutter/material.dart';
class AnimatedListFullExample extends StatefulWidget {
@override
_AnimatedListFullExampleState createState() => _AnimatedListFullExampleState();
}
class _AnimatedListFullExampleState extends State {
final GlobalKey _listKey = GlobalKey();
List _data = ['Item 0', 'Item 1', 'Item 2'];
int _nextItem = 3; // To keep track of unique item names
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('AnimatedList Full Demo'),
actions: [
IconButton(
icon: Icon(Icons.add),
onPressed: () => _insertItem(),
),
],
),
body: AnimatedList(
key: _listKey,
initialItemCount: _data.length,
itemBuilder: (context, index, animation) {
return _buildItem(_data[index], animation, index);
},
),
);
}
Widget _buildItem(String item, Animation animation, int index) {
return SizeTransition( // Can also use SlideTransition, FadeTransition etc.
sizeFactor: animation,
axisAlignment: -1.0, // Align top for size transition
child: Card(
margin: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
color: Colors.blueGrey[50],
child: ListTile(
title: Text(item, style: TextStyle(fontSize: 18)),
trailing: IconButton(
icon: Icon(Icons.delete, color: Colors.redAccent),
onPressed: () => _removeItem(index),
),
),
),
);
}
void _insertItem() {
final int newIndex = _data.length;
_data.add('Item ${_nextItem++}');
_listKey.currentState!.insertItem(
newIndex,
duration: const Duration(milliseconds: 500),
);
}
void _removeItem(int index) {
if (_data.isEmpty) return; // Prevent removing from empty list
final String removedItem = _data[index];
_data.removeAt(index); // Remove from underlying data first
_listKey.currentState!.removeItem(
index,
(context, animation) {
// This builder is for the item being removed.
// It must return the widget as it was before removal.
return _buildItem(removedItem, animation, index);
},
duration: const Duration(milliseconds: 500),
);
}
}
Customizing Animations
The animation object provided to both itemBuilder and the removeItem builder is an Animation<double> typically ranging from 0.0 to 1.0 (for insertion) or 1.0 to 0.0 (for removal). You can wrap your item's widget in various transition widgets:
SizeTransition: Animates the size of the child widget.SlideTransition: Animates the position of the child widget.FadeTransition: Animates the opacity of the child widget.ScaleTransition: Animates the scale of the child widget.
You can also combine these or create custom AnimatedBuilder widgets for more complex effects. The duration parameter in insertItem and removeItem controls how long these animations take.
Conclusion
AnimatedList is a powerful and flexible widget for adding sophisticated list animations to your Flutter applications. By managing your data source and using the GlobalKey to interact with the AnimatedListState, you can create smooth and engaging user experiences for item insertions and removals. Remember to provide a consistent widget for the item being removed in the removeItem builder to ensure a flawless exit animation.