Slivers in Flutter - Part (3)

Slivers in Flutter - Part (3)

What is SliverAnimatedList? How to animate List using SliverAnimatedList?

Subscribe to my newsletter and never miss my upcoming articles

Listen to this article
  • In the previous part i.e. Part(2), we saw SliverPersistentHeader, SliverFixedExtentList, SliverPrototypeExtentList, SliverFillViewport, SliverToBoxAdapter, SliverFillRemaining, SliverPadding
  • Let's see what is SliverAnimatedList widget, and how to use it.

LetsDoItKyleBroflovskiGIF.gif


SliverAnimatedList

  • 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,
                  ),
                ],
              ),
            ],
    )
    

    sliveranimated_appbar.png

  • 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],
         /* Determines if the item is currently selected or not */
        selected: _selectedItem == _list[index], 
          /* Called when user wants to delete the card. It is used to update the color of the card. If `tapped` the `_selectedItem` gets index and the card at that index will get `grey` color other wise normal color. */
        onTap: () {          
          setState(() {
            _selectedItem = _selectedItem == _list[index] ? null : _list[index];
          });
        },
      );
    }
    
  • Let's also define the _list and _selectedItem
  • /* List of item */
    late List<int> _list;
    /* Used to determine which item is selected in order to perform deletion. */
    int? _selectedItem;      
    /* This is used to do define the next item that will be displayed when user presses `+` button */
    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,
        ),
        /* The FadeTransition Animation will be triggered whenever the user add item in list*/
        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 -
  • sliveranimated_card.png
  • 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!);
       /* Updating actual list */
      _list.insert(index, _nextItem++);
      /* Updating UI */
      _listKey.currentState!.insertItem(index);
    }
    
    void _remove() {
      final int index =
          _selectedItem == null ? _list.length : _list.indexOf(_selectedItem!);
      /* Removing from actual list */
      _list.removeAt(index);
      /* Updating UI */
      _listKey.currentState!.removeItem(
        index,
        /* πŸ‘‡ This method is needed because a removed item remains visible until its animation has completed. */
        (context, animation) => SizeTransition( 
          sizeFactor: animation,
          child: Card(
            child: Center(
              child: Text(
                'Item $index',
                style: Theme.of(context).textTheme.headline4,
              ),
            ),
          ),
        ),
      );
      setState(() {
        _selectedItem = null;
      });
    }
    
  • FINAL Output
  • animatedlist.gif


Wrapping Up

Β 
Share this