Flutter provides some really nice scrollable widgets out of the box such as ListView
, GridView
, CustomScroll
view etc, but what if you want to animate your content as they enter or leave the list? How would one achieve this?
Well the answer, Animated List.
AnimatedList
is a widget Flutter
provides out of the box for animating items as they are added or removed. This widget is similar in structure to ListView.builder
in the sense that both widgets have options for the initial item count as well as a builder for constructing the widgets. In fact, if you look at the build
method for AnimatedList
, it returns a ListView.builder
.
Listed below the constructor of AnimatedList
. It shows the different parameters which can be supplied to the list.
https://gist.github.com/b1912c4b3e757b080a5c6d3f7ee0268b
To use this list, you must first start by creating a GlobalKey
of type AnimatedListState
. This key serves as the medium through which you can interact with the list.
https://gist.github.com/16d4b7640aa1e93cab097db94e488655
Once you have created the key, you can now pass it to the AnimatedList
.
https://gist.github.com/6d3d1662d97e972797e60f6b0cd720bd
From the above snippet, you may also notice the builder. The builder of AnimatedList
is very similar to that of ListView.builder
except for one small difference, the extra Animation
parameter. This animation
runs every time the builder is called.
Before we could add and remove items, we need some data to play with. In this demo, we are going to keep it very simple and have a list which contains some dummy users. The User
is going to be a small model which has a person's first name, last name and a URL for their picture.
https://gist.github.com/c5de15b576eeb794d2ab82d1a5c52e23
With our model and list of test users created, let's move onto adding them to the AnimatedList
.
First, the basic skeleton of the app. We are going to have a StatefulWidget
which returns a Scaffold
. The Scaffold
is going to have an appBar
and a body
which contains our AnimatedList
https://gist.github.com/a1cb604177ad82c6a5c86f0c9442ac1f
For this demo, we are going to have a user fade in when they are added. In our AnimatedList
, we create a ListTile
and wrap it in a FadeTransition
. The animation
from the builder
is then passed to the FadeTransition
for its opacity.
With the basic structure built, let's move out attention to adding items to the list.
For this, we are going to create a new function addUser()
. This function is going to handle the adding of users to our listData
as well as inserting an item into the AnimatedList
. An important thing to note is we don't add an item to the AnimatedList
, we add them to our data. Instead, we tell the AnimatedList
to insert a new item at an index.
https://gist.github.com/d00775708675dad089b75002b2aa082c
The above code shows how to add a new user. With an AnimatedList
, you can insert at any index. In this case, we are adding at the end of the list so we use listData.length
.
To add a new user to our listData
we use the add
of List
.
With the list updated, we need to update our AnimatedList
so that we can see the animation and the new user on the screen. For this, we need to use the _listKey
for interacting with the list.
To insert the user into the list, call _listKey.currentState.insertItem()
and pass it both the index into which you want to insert the new user and the duration
of the animation.
Finally we set the addUser()
to the IconButton
onPressed
https://gist.github.com/68d0066f56c72464830f629114414da5
Now that we've covered how to add an item, it is time to wire our function to remove them.
An important thing to note about removing an item in AnimatedList
is the item is immediately removed from the list however it will remain visible on screen for the duration of time specified. Once removed, its index will no longer be passed to AnimatedList.builder
.
Before we can actually remove an item, let's clean up our current code. We will start by changing the way our item is built. Instead of building it directly, let's create a new function _buildItem()
. This function will be responsible for building our ListTile
.
https://gist.github.com/f980e59148957d7125d53f82bae96e8c
Notice our function has two parameters one of which is optional. The reason for this will become clear as we continue. Also take note of the ValueKey
around our ListTile
. Since our widgets are cached by Flutter, we need a way to uniquely idefify them.
At this point, running our code will cause a crash. This is because ValueKey
uses the ==
operator to compare widgets. Since we are using a custom type, we need to override the ==
operator and hashCode
for our UserModel
. If you are using InteliJ, you can use Alt+Ins
in UserModel
and select "generate == and hashCode". Select all three fields and hit ok.
https://gist.github.com/09deba2ecebb8be7b45304282668d17d
Now with our refactoring complete, let's move on to our delete function. From the code we refactored you may have noticed that we changed our onLongPressed
to use a function deleteUser
. Let us now create this function.
https://gist.github.com/41666b011ac1e1a85bf034f4898eb419
The above snippet shows the function for removing an item from this list. Fist, the index of the item we want to remove is passed to the function as a parameter. Next, we remove it from our listData
. At this point, the item is removed however it is still visible on screen because we did not remove it from our AnimatedList
. To do this, we use _listKey.currentState.removeItem()
.
The method removeItem
has three parameters, index
, builder
and an optional parameter duration
. When this method is called, it starts a reversed animation that is passed to builder
. This allows us to animate the item off-screen. For this example, we are going to both a FadeTransition
and SizeTransition
.
As a user is deleted, it is first going to fade then shrink as it is animated off-screen. As the above snippet shows, we are storing the result of listData.removeAt(index);
in a local variable user
. Next we are removing it from our AnimatedList
using _listKey.currentState.removeItem()
. We give the method the index which was passed into the function then create our builder. Here we create the build function consisting of BuildContext
and Animation<double>
. It returns a FadeTransition
which has a child
of SizeTransition
. For the opacity of our fade, we are using a CurvedAnimation
consisting of an Interval
and a parent of animation
. The same is done with SizeTransition
only this time the child
is our _buildItem()
.
You may have noticed that we pass our local user
variable over to _buildItem()
. This is done because the removed item needs to be the same as the one which was on our list. The final piece of our puzzle is duration
. This is the length of time we want our animation to run for. In this example, we are using 600 milliseconds.
Assuming you've followed along, your final code show look a little something like this:
https://gist.github.com/8b2151ffc0fcb6df1da81011e0ae1929
Running the above code on device yields the following results:
I hoped you enjoyed this article, if you have any questions or spot any errors or simply have a suggestion for an article, please leave a comment or reach out to me on Twitter, I am @Nash0x7E2. Also be sure to follow Flutter Community to keep up with everything as they relate to Flutter.
-- Nash