Flutter (41):animated list

Time: Column:Mobile & Frontend views:249

AnimatedList and ListView have similar functionalities, but the key difference is that AnimatedList can perform animations when inserting or deleting items in the list. This feature enhances the user experience in scenarios where items are added or removed from the list.

AnimatedList is a StatefulWidget, and its corresponding state type is AnimatedListState. The methods for adding and removing elements are found in AnimatedListState:

void insertItem(int index, { Duration duration = _kDuration });

void removeItem(int index, AnimatedListRemovedItemBuilder builder, { Duration duration = _kDuration });

Let’s look at an example: we will create a list that allows users to add a new item by clicking a "+" button at the bottom, and delete an item by clicking the delete button next to each list item. The specified animations will be executed when items are added or removed, as shown in Figure.

Flutter (41):animated list

Initially, there are five items in the list. When the "+" button is clicked, a "6" is added, and a fade-in animation occurs during the addition. After clicking the delete button next to "4," a fade-out and shrink animation is executed.

Here’s the implementation code:

class AnimatedListRoute extends StatefulWidget {
  const AnimatedListRoute({Key? key}) : super(key: key);

  @override
  _AnimatedListRouteState createState() => _AnimatedListRouteState();
}

class _AnimatedListRouteState extends State<AnimatedListRoute> {
  var data = <String>[];
  int counter = 5;

  final globalKey = GlobalKey<AnimatedListState>();

  @override
  void initState() {
    for (var i = 0; i < counter; i++) {
      data.add('${i + 1}');
    }
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        AnimatedList(
          key: globalKey,
          initialItemCount: data.length,
          itemBuilder: (
            BuildContext context,
            int index,
            Animation<double> animation,
          ) {
            // Fade-in animation when adding list items
            return FadeTransition(
              opacity: animation,
              child: buildItem(context, index),
            );
          },
        ),
        buildAddBtn(),
      ],
    );
  }

  // Create a "+" button to insert an item into the list
  Widget buildAddBtn() {
    return Positioned(
      child: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () {
          // Add a new list item
          data.add('${++counter}');
          // Notify the list that a new item has been added
          globalKey.currentState!.insertItem(data.length - 1);
          print('Added $counter');
        },
      ),
      bottom: 30,
      left: 0,
      right: 0,
    );
  }

  // Build a list item
  Widget buildItem(context, index) {
    String char = data[index];
    return ListTile(
      // The number won't repeat, so it's used as the key
      key: ValueKey(char),
      title: Text(char),
      trailing: IconButton(
        icon: Icon(Icons.delete),
        // Delete on click
        onPressed: () => onDelete(context, index),
      ),
    );
  }

  void onDelete(context, index) {
    // To be implemented
  }
}

When deleting, we need to use the removeItem method from AnimatedListState to apply the deletion animation, with the specific logic in the onDelete method:

setState(() {
  globalKey.currentState!.removeItem(
    index,
    (context, animation) {
      // The deletion process executes a reverse animation; animation.value changes from 1 to 0
      var item = buildItem(context, index);
      print('Deleted ${data[index]}');
      data.removeAt(index);
      // The deletion animation is a composite animation: fade-out + shrink
      return FadeTransition(
        opacity: CurvedAnimation(
          parent: animation,
          // Make the opacity change faster
          curve: const Interval(0.5, 1.0),
        ),
        // Continuously reduce the height of the list item
        child: SizeTransition(
          sizeFactor: animation,
          axisAlignment: 0.0,
          child: item,
        ),
      );
    },
    duration: Duration(milliseconds: 200), // Animation duration is 200 ms
  );
});

The code is simple, but we need to note that our data is maintained separately in data. Calling the insertion and removal methods of AnimatedListState is essentially a notification: indicating where to execute the insertion or removal animation. The data remains driven (reactive rather than imperative).