This tutorial is about how to display AnimatedList
in Flutter with a simple example.
If your application need to display a ListView
and the items are dynamically inserted or removed, it would be better if you add animation during update, so the user is aware of the update. In Flutter, it can be done easily thanks to AnimatedList
class.
First, we're going to create a stateful widget along with its state and the empty layout.
In this example, the data is stored in a List<String>
. The value is generated randomly using english_words
package. We also need an instance of GlobalKey<AnimatedListState> for storing the state of AnimatedList.
The layout is very simple. It mainly consists of the list view. Three buttons are at the bottom: a button for adding new item (with random value), a button removing the last item and a button for removing all items.
class AnimatedListExample extends StatefulWidget {
@override
AnimatedListExampleState createState() {
return new AnimatedListExampleState();
}
}
class AnimatedListExampleState extends State<AnimatedListExample> {
final GlobalKey<AnimatedListState> _listKey = GlobalKey();
List<String> _data = [
WordPair.random().toString(),
WordPair.random().toString(),
WordPair.random().toString(),
WordPair.random().toString(),
WordPair.random().toString(),
];
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Animated List'),
backgroundColor: Colors.blueAccent,
),
persistentFooterButtons: <Widget>[
RaisedButton(
child: Text(
'Add an item',
style: TextStyle(fontSize: 20, color: Colors.white),
),
onPressed: () {
// _addAnItem();
},
),
RaisedButton(
child: Text(
'Remove last',
style: TextStyle(fontSize: 20, color: Colors.white),
),
onPressed: () {
// _removeLastItem();
},
),
RaisedButton(
child: Text(
'Remove all',
style: TextStyle(fontSize: 20, color: Colors.white),
),
onPressed: () {
// _removeAllItems();
},
),
],
body: new Container(),
);
}
Build Animation
To create the animation, we're going to create a function that returns a Widget
. The widget must contain any Transition such as:
SlideTransition
ScaleTransition
SizeTransition
RotationTransition
PositionedTransition
RelativePositionedTransition
DecoratedBoxTransition
AlignTransition
DefaultTextStyleTransition
FadeTransition
For example, if we use SizeTransition
, the widget that displays the list item must be the child of SizeTransition
.
Widget _buildItem(BuildContext context, String item, Animation animation) {
TextStyle textStyle = new TextStyle(fontSize: 20);
return Padding(
padding: const EdgeInsets.all(2.0),
child: SizeTransition(
sizeFactor: animation,
axis: Axis.vertical,
child: SizedBox(
height: 50.0,
child: Card(
child: Center(
child: Text(item, style: textStyle),
),
),
),
),
);
}
The _builtItem
function above will be called every time a new item is added or removed.
Create AnimatedList
We need to replace the empty body
with a new AnimatedList
. To build an AnimatedList
, we pass the _listKey
as key parameter and the number of items as initialItemCount
. As for itemBuilder
which will be called every time a new item is added, we pass a function that returns the _buildItem
function.
body: AnimatedList(
key: _listKey,
initialItemCount: _data.length,
itemBuilder: (context, index, animation) => _buildItem(context, _data[index], animation),
),
Insert Item
To insert an item, first insert it to _data
array first. After that, insert to the animated list using insertItem
. Every time an item is added, itemBuilder
will be called.
void _addAnItem() {
_data.insert(0, WordPair.random().toString());
_listKey.currentState?.insertItem(0);
}
Remove Item
There are two implemented functions of removing item: one for removing the last added item and the other is for removing all items. To show the animation, we also call _buildItem
. For removing the last added item, use removeItem method, then remove it from _data
. Beforehand, we need to save the data of the item that will be removed as it is necessary for displaying animation. If not, we will get the next item (or error if we delete the last item) instead of the deleted item inside removeItem, as the data has been deleted .
void _removeLastItem() {
String itemToRemove = _data[0];
_listKey.currentState?.removeItem(
0,
(BuildContext context, Animation animation) => _buildItem(context, itemToRemove, animation),
duration: const Duration(milliseconds: 250),
);
_data.removeAt(0);
}
void _removeAllItems() {
final int itemCount = _data.length;
for (var i = 0; i < itemCount; i++) {
String itemToRemove = _data[0];
_listKey.currentState?.removeItem(
0,
(BuildContext context, Animation animation) => _buildItem(context, itemToRemove, animation),
duration: const Duration(milliseconds: 250),
);
_data.removeAt(0);
}
}
If the list is empty, it has been handled by Flutter.
Full Code
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
class AnimatedListExample extends StatefulWidget {
@override
AnimatedListExampleState createState() {
return new AnimatedListExampleState();
}
}
class AnimatedListExampleState extends State<AnimatedListExample> {
final GlobalKey<AnimatedListState> _listKey = GlobalKey();
List<String> _data = [
WordPair.random().toString(),
WordPair.random().toString(),
WordPair.random().toString(),
WordPair.random().toString(),
WordPair.random().toString(),
];
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Animated List'),
backgroundColor: Colors.blueAccent,
),
persistentFooterButtons: <Widget>[
RaisedButton(
child: Text(
'Add an item',
style: TextStyle(fontSize: 20, color: Colors.white),
),
onPressed: () {
_addAnItem();
},
),
RaisedButton(
child: Text(
'Remove last',
style: TextStyle(fontSize: 20, color: Colors.white),
),
onPressed: () {
_removeLastItem();
},
),
RaisedButton(
child: Text(
'Remove all',
style: TextStyle(fontSize: 20, color: Colors.white),
),
onPressed: () {
_removeAllItems();
},
),
],
body: AnimatedList(
key: _listKey,
initialItemCount: _data.length,
itemBuilder: (context, index, animation) => _buildItem(context, _data[index], animation),
),
);
}
Widget _buildItem(BuildContext context, String item, Animation<double> animation) {
TextStyle textStyle = new TextStyle(fontSize: 20);
return Padding(
padding: const EdgeInsets.all(2.0),
child: SizeTransition(
sizeFactor: animation,
axis: Axis.vertical,
child: SizedBox(
height: 50.0,
child: Card(
child: Center(
child: Text(item, style: textStyle),
),
),
),
),
);
}
void _addAnItem() {
_data.insert(0, WordPair.random().toString());
_listKey.currentState?.insertItem(0);
}
void _removeLastItem() {
String itemToRemove = _data[0];
_listKey.currentState?.removeItem(
0,
(BuildContext context, Animation<double> animation) => _buildItem(context, itemToRemove, animation),
duration: const Duration(milliseconds: 250),
);
_data.removeAt(0);
}
void _removeAllItems() {
final int itemCount = _data.length;
for (var i = 0; i < itemCount; i++) {
String itemToRemove = _data[0];
_listKey.currentState?.removeItem(
0,
(BuildContext context, Animation<double> animation) => _buildItem(context, itemToRemove, animation),
duration: const Duration(milliseconds: 250),
);
_data.removeAt(0);
}
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'ListView Example',
home: AnimatedListExample(),
);
}
}
void main() => runApp(MyApp());