Table of Contents
The problem
List view in general is very essential in any front end framework. It contains the list of data called items to be displayed to the user. There is a high chance that item in a list may be added, removed, and changed. Now the problem is whenever any item added or removed in between the list while the user is interacting with it, This sudden change sometimes can create confusion to the user.
The solution
The simple solution is to provide a visual experience of items being added or removed by animating it. ?
So how do we do that in Flutter?
? Presenting the AnimatedList Widget
An AnimatedList is a List that animates the item when inserted or removed.
Let’s try to build something like this.
Animated Widget Code looks like this
AnimatedList(
itemBuilder: (context, index, animation) {
return slideIt(context, index, animation);
},
)
Properties:
◉ itemBuilder: This is the required parameter and is used to build items in a list. You can simply return a widget of your choice to build any item and they will be only built when they are scrolled into the view. Since we are dealing with animation, we get an animation object that will be used by the item to animate.
◉ controller: This is used to control the scroll position of the list.
◉ key: This is required in case we need to access the AnimatedList from outside and not from within the item itself.
◉ initialItemCount: It is used to load initial items when the list is loaded. These initial items won’t be animated. It defaults to 0.
◉ scrollDirection: It decides the scrolling behaviors of items.
// Items can be scrolled only bottom to top and vica versa. scrollDirection: Axis.vertical,// Items can be scrolled only left to right and vica versa. scrollDirection: Axis.horizontal,
◉ reverse: This is used to decide whether the list will be scrolled in the reading direction. It defaults to false. In the App by default, the reading direction is left-to-right so the list will be scrolled left to right. If we set this flag to true, the list will scroll in a right-to-left direction (opposite).
◉ primary: In iOS when we click on the status bar the list will scroll to top. It defaults to true when the scroll direction is vertical and the controller is null.
◉ physics: It decides how the scroll should behave on user input.
◉ shrinkWrap: It is used to decide whether the size (extent) of the list should take full available space or match it to the size of items in the list.
◉ padding: This simply adds padding around the items on the list.
Let’s make it work step by step
Step ➊: Prepare variables
/// Will used to access the Animated list final GlobalKey<AnimatedListState> listKey = GlobalKey<AnimatedListState>();/// This holds the items List<int> _items = [];/// This holds the item count int counter = 0;
Step ➋: Deploy the AnimatedList.
AnimatedList(
key: listKey,
initialItemCount: _items.length,
itemBuilder: (context, index, animation) {
return slideIt(context, index, animation); // Refer step 3
},
)
Step ➌: Write a widget to display as Items in a list.
Widget slideIt(BuildContext context, int index, animation) {
int item = _items[index];
TextStyle textStyle = Theme.of(context).textTheme.headline4;
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(-1, 0),
end: Offset(0, 0),
).animate(animation),
child: SizedBox( // Actual widget to display
height: 128.0,
child: Card(
color: Colors.primaries[item % Colors.primaries.length],
child: Center(
child: Text('Item $item', style: textStyle),
),
),
),
);
}
Note: ✍
The main item widget i.e SizedBox is wrapped inside SlideTransition. SlideTransition needs the object of Animation<Offset> and we are getting the object of Animation<double> so we use .animate method to convert it.
Step ➍: Insert the Item.
listKey.currentState.insertItem(0,
duration: const Duration(milliseconds: 500));
_items = []
..add(counter++)
..addAll(_items);
For this example, we are adding any new item to the first index.
Step ➎: Remove the Item.
listKey.currentState.removeItem(
0, (_, animation) => slideIt(context, 0, animation),
duration: const Duration(milliseconds: 500));
_items.removeAt(0);
Also removing it from the first index only.
Note: ✍
- Whenever we insert the item in the animated list we should also update the actual list.
- While removing the item in step 5 we also have to pass the same widget which is used to create items because when AnimatedList removes the item, Its index is no longer available but still, it needs the UI of that item to animate like this (_, animation) => slideIt(context, 0, animation).
Here is the full code ?
How do we add curves to animation?
CurvedAnimation is here to the rescue.
CurvedAnimation is useful when you want to apply a non-linear Curve to an animation object, especially if you want different curves when the animation is going forward vs when it is going backward.
SlideTransition(
position: Tween<Offset>(
begin: const Offset(-1, 0),
end: Offset(0, 0),
).animate(CurvedAnimation(
parent: animation,
curve: Curves.bounceIn,
reverseCurve: Curves.bounceOut)),
child: SizedBox(
height: 128.0,
child: Card(
color: Colors.primaries[item % Colors.primaries.length],
child: Center(
child: Text('Item $item', style: textStyle),
),
),
),
);
Instead of just passing animation, we pass CurvedAnimation with the required curve. And the result looks like this.
You see items bouncing on entry and exit. Right?
? Bonus
You can check all the curves here
Different types of animation
So far we have seen the item is just entering from left to right and taking exit in the reverse direction. This is because we have used the SlideTransition widget.
You can create any animation for the item that you can think of. The widget you choose will just have to accept the object of Animation<double>
Resize the item ↕
Widget sizeIt(BuildContext context, int index, animation) {
int item = _items[index];
TextStyle textStyle = Theme.of(context).textTheme.headline4;
return SizeTransition(
axis: Axis.vertical,
sizeFactor: animation,
child: SizedBox(
height: 128.0,
child: Card(
color: Colors.primaries[item % Colors.primaries.length],
child: Center(
child: Text('Item $item', style: textStyle),
),
),
),
);
}
The sizeFactor property accepts the Animation<double> which we can easily pass what we have got from the itemBuilder.
Rotate the item ⟳
Widget rotateIt(BuildContext context, int index, animation) {
int item = _items[index];
TextStyle textStyle = Theme.of(context).textTheme.headline4;
return RotationTransition(
turns: animation,
child: SizedBox(
height: 128.0,
child: Card(
color: Colors.primaries[item % Colors.primaries.length],
child: Center(
child: Text('Item $item', style: textStyle),
),
),
),
);
}
Slide, Resize, and Rotate the item together. →↕⟳
SlideTransition(
position: Tween<Offset>(
begin: const Offset(-1, 0),
end: Offset(0, 0),
).animate(animation),
child: RotationTransition(
turns: animation,
child: SizeTransition(
axis: Axis.vertical,
sizeFactor: animation,
child: SizedBox(
height: 128.0,
child: Card(
color: Colors.primaries[item % Colors.primaries.length],
child: Center(
child: Text('Item $item', style: textStyle),
),
),
),
),
),
);
How to animate the items rendered initially.
Right now AnimatedList is able to animate items that are added or removed after the initial list is displayed. To animate the initial items that are already present in the list, You can manually add items in a list after a delay.
Future<void> _loadItems() async {
for (int item in _fetchedItems) {
// 1) Wait for one second
await Future.delayed(Duration(milliseconds: 1000));
// 2) Adding data to actual variable that holds the item.
_items.add(item);
// 3) Telling animated list to start animation
listKey.currentState.insertItem(_items.length - 1);
}
}
That’s it. I hope you have learned something new from this article.
Thanks for reading.