Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

preface

Lists are the most commonly used components in mobile applications, and we often add or delete list elements. The easiest way is to update the list interface directly with setState when the list data changes. One drawback of this approach is that list elements can suddenly disappear (delete) or appear (add), and when the list elements are close, there is no way to know if the operation was successful. The experience is much better if you have a dynamic effect that shows the process of disappearing and appearing, such as the following effect, which shows a fading animation when removing elements and a fading effect when adding elements.

The AnimatedList is used here, and the sample code for this article comes mainly from the official documentation: the AnimatedList component. Note that tables are animated, and this will definitely affect performance. It is recommended to use only lists with small data volumes that require elements to be deleted or added.

AnimatedList introduction

AnimatedList is an alternative to ListView. The constructor is basically the same as ListView.

const AnimatedList({
  Key? key,
  required this.itemBuilder,
  this.initialItemCount = 0.this.scrollDirection = Axis.vertical,
  this.reverse = false.this.controller,
  this.primary,
  this.physics,
  this.shrinkWrap = false.this.padding,
  this.clipBehavior = Clip.hardEdge,
})
Copy the code

The difference is that the definition of itemBuilder is different. Compared to ListView, itemBuilder has an animation parameter:

typedef AnimatedListItemBuilder = Widget Function(
  BuildContext context, 
  int index, 
  Animation<double> animation
);
Copy the code

Animation is an animation

object, so you can use animation to build transition animations of elements. For example, our example here uses FadeTransition to build list elements to have a fade in effect.

class ListItem extends StatelessWidget {
  const ListItem({
    Key? key,
    required this.onRemove,
    required this.animation,
    required this.item,
  }) : super(key: key);

  final Animation<double> animation;
  final ValueChanged onRemove;
  final int item;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(2.0),
      child: FadeTransition(
        opacity: animation,
        child: Container(
          child: Row(children: [
            Expanded(
              child: Text(
                'Item $item',
                style: TextStyle(
                  color: Colors.blue,
                ),
              ),
            ),
            IconButton(
              onPressed: () {
                onRemove(this.item); }, icon: Icon(Icons.delete_forever_rounded, color: Colors.grey), ), ]), ), ), ); }}Copy the code

Insert and delete elements

With AnimatedList, we need to call the insertItem and removeItem methods of AnimatedListState to operate, rather than refresh the interface after directly manipulating the data. When inserting and deleting data, the list data should be modified first, and then the insertItem or removeItem methods of AnimatedListState should be called to refresh the list interface. For example, delete element code:

E removeAt(int index) {
  final E removedItem = _items.removeAt(index);

  if(removedItem ! =null) { _animatedList! .removeItem( index, (BuildContext context, Animation<double> animation) {
        returnremovedItemBuilder(removedItem, context, animation); }); }return removedItem;
}
Copy the code

Here the removedItem takes two parameters, one is the subscript of the element to be removed, and the other is a method Builder that builds the removed element. This method is used because the element is actually removed from the list immediately. In order for the removed element to be visible during the animation transition time, you need to construct a removed element in this way to feel like it was removed from the animation. You can also use the animation parameter to customize the animation. The insertItem method has no Builder parameter and inserts new elements by passing them directly to the Builder method of the AnimatedList, thus maintaining the same dynamic effects as the new elements in the list.

Get AnimatedListState using GlobalKey

Since all control of the AnimatedList takes place in the AnimatedState, and the AnimatedState object cannot be retrieved directly, a GlobalKey is required to obtain the AnimatedListState object. Pass a GlobalKey to the key property when building the AnimatedList. You can then retrieve the AnimatedListState object using currentState.

class _AnimatedListSampleState extends State<AnimatedListSample> {
  final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
  late ListModel<int> _list;
  late int _nextItem;

  @override
  void initState() {
    super.initState();
    _list = ListModel<int>(
      listKey: _listKey,
      initialItems: <int> [0.1.2],
      removedItemBuilder: _buildRemovedItem,
    );
    _nextItem = 3;
  }

  Widget _buildRemovedItem(
      int item, BuildContext context, Animation<double> animation) {
    return ListItem(
      animation: animation,
      item: item,
      onRemove: _remove,
    );
  }

  // Insert the "next item" into the list model.
  void _insert() {
    final int index = _list.length;
    _list.insert(index, _nextItem++);
  }

  // Remove the selected item from the list model.
  void _remove(item) {
    if(item ! =null) {
      _list.removeAt(_list.indexOf(item!));
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('AnimatedList'),
        actions: <Widget>[
          IconButton(
            icon: const Icon(Icons.add),
            onPressed: _insert,
            tooltip: 'add',
          ),
        ],
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: AnimatedList(
          key: _listKey,
          initialItemCount: _list.length,
          itemBuilder: (context, index, animation) {
            returnFadeTransition( opacity: animation, child: ListItem( onRemove: _remove, animation: animation, item: _list[index], ), ); },),),); }}Copy the code

The full source code can be found in the advance_animation directory: animation code.

conclusion

This article introduces the use of AnimatedList, which can be used to improve the user experience for some of our low-volume lists with insert or delete elements.

I am dao Code Farmer with the same name as my wechat official account. This is a column about the introduction and practice of Flutter, providing systematic learning articles about Flutter. See the corresponding source code here: The source code of Flutter Introduction and Practical column. If you have any questions, please add me to the wechat account: island-coder. If you feel you have something to gain, please give three pairs of love as follows:

👍🏻 : a praise to encourage!

🌟 : Collect articles, easy to look back!

💬 : Comment exchange, mutual progress!