Flutter - AnimatedList Example

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());