Search code examples
flutterdartflutter-animatedlist

Flutter animated list: animate entire list


I am trying to implement Flutter's AnimatedList. What I'd like to achieve is to allready have a list of elements and then insert them one by one in the list with a total duration of 1 second.

For example: I have a list of 5 containers (a red one, a blue one, a green one, a pink one and a white one). I want each container to be slided in the list view.

I would now like that on startup, this list is displayed in the following timestamps:

0..200ms: red container

200..400ms: blue container

400..600ms: green container

600..800ms: pink container

800..1000ms: white container

Such that the entire list takes up 1 second to build and the amount of time 1 container should take for its animation is 1/nseconds and each container at index i in the list should start its animation at i*(1/n)seconds. Yet all documentation or examples I could find is simply displaying a button and then inserting a new item in the list, whilst I want an already created list to be displayed by the means of an animation.


Solution

  • Have you tried Timer.periodic.

    You can simply use it and insert item after time time mentioned. like this :

    void startTimer() {
      const oneSec = const Duration(milliseconds: 1000);
      _timer = new Timer.periodic(
        oneSec,
        (Timer timer) {
          _insert();
          if(_list.length == 10){
          timer.cancel();
          }
        },
      );
    

    Complete code on Dart pad :

    /// Flutter code sample for AnimatedList
    
    // This sample application uses an [AnimatedList] to create an effect when
    // items are removed or added to the list.
    import 'dart:async';
    import 'package:flutter/foundation.dart';
    import 'package:flutter/material.dart';
    
    void main() {
      runApp(const AnimatedListSample());
    }
    
    class AnimatedListSample extends StatefulWidget {
      const AnimatedListSample({Key? key}) : super(key: key);
    
      @override
      _AnimatedListSampleState createState() => _AnimatedListSampleState();
    }
    
    class _AnimatedListSampleState extends State<AnimatedListSample> {
      final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
      late ListModel<int> _list;
      int? _selectedItem;
      late int
          _nextItem; // The next item inserted when the user presses the '+' button.
    
      Timer? _timer;
      @override
      void initState() {
        super.initState();
        _list = ListModel<int>(
          listKey: _listKey,
          initialItems: <int>[0, 1, 2],
          removedItemBuilder: _buildRemovedItem,
        );
        _nextItem = 3;
        startTimer();
      }
    
      void startTimer() {
      const oneSec = const Duration(milliseconds: 1000);
      _timer = new Timer.periodic(
        oneSec,
        (Timer timer) {
          _insert();
          if(_list.length == 10){
          timer.cancel();
          }
        },
      );
    }
      
      @override
    void dispose() {
      _timer!.cancel();
      super.dispose();
    }
    
      // Used to build list items that haven't been removed.
      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];
            });
          },
        );
      }
    
      // Used to build an item after it has been removed from the list. This
      // method is needed because a removed item remains visible until its
      // animation has completed (even though it's gone as far this ListModel is
      // concerned). The widget will be used by the
      // [AnimatedListState.removeItem] method's
      // [AnimatedListRemovedItemBuilder] parameter.
      Widget _buildRemovedItem(
          int item, BuildContext context, Animation<double> animation) {
        return CardItem(
          animation: animation,
          item: item,
          selected: false,
          // No gesture detector here: we don't want removed items to be interactive.
        );
      }
    
      // Insert the "next item" into the list model.
      void _insert() {
        final int index =
            _selectedItem == null ? _list.length : _list.indexOf(_selectedItem!);
        _list.insert(index, _nextItem++);
      }
    
      // Remove the selected item from the list model.
      void _remove() {
        if (_selectedItem != null) {
          _list.removeAt(_list.indexOf(_selectedItem!));
          setState(() {
            _selectedItem = null;
          });
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            appBar: AppBar(
              title: const Text('AnimatedList'),
              actions: <Widget>[
                IconButton(
                  icon: const Icon(Icons.add_circle),
                  onPressed: _insert,
                  tooltip: 'insert a new item',
                ),
                IconButton(
                  icon: const Icon(Icons.remove_circle),
                  onPressed: _remove,
                  tooltip: 'remove the selected item',
                ),
              ],
            ),
            body: Padding(
              padding: const EdgeInsets.all(16.0),
              child: AnimatedList(
                key: _listKey,
                initialItemCount: _list.length,
                itemBuilder: _buildItem,
              ),
            ),
          ),
        );
      }
    }
    
    typedef RemovedItemBuilder = Widget Function(
        int item, BuildContext context, Animation<double> animation);
    
    /// Keeps a Dart [List] in sync with an [AnimatedList].
    ///
    /// The [insert] and [removeAt] methods apply to both the internal list and
    /// the animated list that belongs to [listKey].
    ///
    /// This class only exposes as much of the Dart List API as is needed by the
    /// sample app. More list methods are easily added, however methods that
    /// mutate the list must make the same changes to the animated list in terms
    /// of [AnimatedListState.insertItem] and [AnimatedList.removeItem].
    class ListModel<E> {
      ListModel({
        required this.listKey,
        required this.removedItemBuilder,
        Iterable<E>? initialItems,
      }) : _items = List<E>.from(initialItems ?? <E>[]);
    
      final GlobalKey<AnimatedListState> listKey;
      final RemovedItemBuilder removedItemBuilder;
      final List<E> _items;
    
      AnimatedListState? get _animatedList => listKey.currentState;
    
      void insert(int index, E item) {
        _items.insert(index, item);
        _animatedList!.insertItem(index);
      }
    
      E removeAt(int index) {
        final E removedItem = _items.removeAt(index);
        if (removedItem != null) {
          _animatedList!.removeItem(
            index,
            (BuildContext context, Animation<double> animation) {
              return removedItemBuilder(index, context, animation);
            },
          );
        }
        return removedItem;
      }
    
      int get length => _items.length;
    
      E operator [](int index) => _items[index];
    
      int indexOf(E item) => _items.indexOf(item);
    }
    
    /// Displays its integer item as 'item N' on a Card whose color is based on
    /// the item's value.
    ///
    /// The text is displayed in bright green if [selected] is
    /// true. This widget's height is based on the [animation] parameter, it
    /// varies from 0 to 128 as the animation varies 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) {
        TextStyle textStyle = Theme.of(context).textTheme.headline4!;
        if (selected)
          textStyle = textStyle.copyWith(color: Colors.lightGreenAccent[400]);
        return Padding(
          padding: const EdgeInsets.all(2.0),
          child: SizeTransition(
            axis: Axis.vertical,
            sizeFactor: animation,
            child: GestureDetector(
              behavior: HitTestBehavior.opaque,
              onTap: onTap,
              child: SizedBox(
                height: 80.0,
                child: Card(
                  color: Colors.primaries[item % Colors.primaries.length],
                  child: Center(
                    child: Text('Item $item', style: textStyle),
                  ),
                ),
              ),
            ),
          ),
        );
      }
    }
    
    
    

    You can customise that duration in startTimer method using any formulae u like. Remember the duration should be 200 milliseconds as you mentioned.