- Removing the list widget without any transition effect, will confuse the user with what just happened, from where the list item was inserted or deleted, etc.
- To animate the sliver list we can use SliverAnimatedList. This widget's SliverAnimatedListState can be used to dynamically insert or remove items.
- Example - Taken from doc.
- First, let's create a SliverAppBar that has two buttons
add and remove for inserting and removing the list item.
CustomScrollView(
slivers: <Widget>[
SliverAppBar(
title: const Text(
'SliverAnimatedList',
style: TextStyle(fontSize: 30),
),
expandedHeight: 60,
centerTitle: true,
backgroundColor: Colors.amber[900],
leading: IconButton(
icon: const Icon(Icons.add_circle),
onPressed: _insert,
tooltip: 'Insert a new item.',
iconSize: 32,
),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.remove_circle),
onPressed: _remove,
tooltip: 'Remove the selected item.',
iconSize: 32,
),
],
),
],
)

Now let's add SliverAnimatedList below the SliverAppBar
SliverAnimatedList(
key: _listKey,
initialItemCount: _list.length,
itemBuilder: _buildItem,
),
key
- Here the
_listKey is a GlobalKey of type SliverAnimatedListState. This key is helpful for inserting and deleting the list item.
- To insert the item into the list we can use
SliverAnimatedListState.insertItem(), and to remove the item use SliverAnimatedListState.removeItem()
final GlobalKey<SliverAnimatedListState> _listKey =
GlobalKey<SliverAnimatedListState>();
initialItemCount
- The
initialItemCount defines how many numbers of items the list will start with. So let's initialize it in initState
@override
void initState() {
super.initState();
_nextItem = 3;
}
itemBuilder
- This builds the item on the screen as needed lazily.
- List items are only built when they're scrolled into view.
- This function has 3 parameters :
- The
context, which is the context of the sliver.
- The
index parameter indicates the item's position in the list. The value of the index parameter will be between 0 and initialItemCount plus the total number of items that have been inserted with SliverAnimatedListState.insertItem and less the total number of items that have been removed with SliverAnimatedListState.removeItem.
- The
animation gives the animation value of type double between 0 and 1.
- Let's implement the
_buildItem function which is passed inside the itemBuilder :
Widget _buildItem(
BuildContext context, int index, Animation<double> animation) {
return CardItem(
animation: animation,
item: _list[index],
selected: _selectedItem == _list[index],
onTap: () {
setState(() {
_selectedItem = _selectedItem == _list[index] ? null : _list[index];
});
},
);
}
- Let's also define the
_list and _selectedItem
late List<int> _list;
int? _selectedItem;
late int _nextItem;
@override
void initState() {
super.initState();
_list = <int>[0, 1, 2];
_nextItem = 3;
}
- Let's see what the
CardItem widget is: It is a simple widget containing a Text in the center displaying the index of that item in the list.
- The card turns gray when
selected is true. This widget's opacity
is based on the animation parameter.
- It varies as the
animation value transitions from 0.0 to 1.0.
class CardItem extends StatelessWidget {
const CardItem({
Key? key,
this.onTap,
this.selected = false,
required this.animation,
required this.item,
}) : assert(item >= 0),
super(key: key);
final Animation<double> animation;
final VoidCallback? onTap;
final int item;
final bool selected;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(
left: 2.0,
right: 2.0,
top: 2.0,
bottom: 0.0,
),
child: FadeTransition(
opacity: animation,
child: GestureDetector(
onTap: onTap,
child: SizedBox(
height: 80.0,
child: Card(
color: selected
? Colors.black12
: Colors.primaries[item % Colors.primaries.length],
child: Center(
child: Text(
'Item $item',
style: Theme.of(context).textTheme.headline4,
),
),
),
),
),
),
);
}
}
- The UI will look something like this -

- Now, Let's implement the
_insert and _remove functions for Inserting the "next item" into the list. And Removing the selected item from the list
void _insert() {
final int index =
_selectedItem == null ? _list.length : _list.indexOf(_selectedItem!);
_list.insert(index, _nextItem++);
_listKey.currentState!.insertItem(index);
}
void _remove() {
final int index =
_selectedItem == null ? _list.length : _list.indexOf(_selectedItem!);
_list.removeAt(index);
_listKey.currentState!.removeItem(
index,
(context, animation) => SizeTransition(
sizeFactor: animation,
child: Card(
child: Center(
child: Text(
'Item $index',
style: Theme.of(context).textTheme.headline4,
),
),
),
),
);
setState(() {
_selectedItem = null;
});
}
- FINAL Output
