Search code examples
flutterflutter-animationflutter-animatedlist

How to animate the items rendered initially using Animated List in flutter


I am using Animated List in flutter to load a list class, while adding or removing the items, the animation works but when the list is initially loaded, the animation does not work. Is there a way to animate items when initially loading the list.

class AnimationTest extends StatefulWidget {
  @override
  _AnimationTestState createState() => _AnimationTestState();
}

class _AnimationTestState extends State<AnimationTest> with SingleTickerProviderStateMixin {
  AnimationController _controller;

  @override
  void initState() {
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 1),
    );
    super.initState();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedList(
      key: _listKey,
      initialItemCount: 3,
      itemBuilder: (BuildContext context, int index, Animation animation) {
        return SlideTransition(
          position: animation.drive(Tween<Offset>(begin: Offset(1.0, 0.0), end: Offset.zero)
              .chain(CurveTween(curve: Curves.decelerate))),
          child: Row(
            children: <Widget>[
              Expanded(
                child: InkWell(
                  onTap: () => _listKey.currentState.insertItem(0,duration: Duration(milliseconds: 600)),
                  child: Container(
                      padding: EdgeInsets.only(left: 10, right: 10),
                      height: 100,
                      child: Card(
                        margin: EdgeInsets.symmetric(vertical: 4.0),
                        color: Theme.of(context).backgroundColor,
                      )),
                ),
              ),
            ],
          ),
        );
      },
    );
  }
}

Solution

  • Because AnimatedList can only animate when adding/removing item in the list. You need to add each item individually by using insertItem or removeItem from AnimatedListState. One way to achieve a nice loading effect is to delay each time you insert/remove item.

    demo load/unload list items

    Here is the code to chain Future so that each item is loaded one after another after a specified delay

    var future = Future(() {});
    for (var i = 0; i < fetchedList.length; i++) {
      future = future.then((_) {
        return Future.delayed(Duration(milliseconds: 100), () {
          // add/remove item
        });
      });
    }
    

    From there you can create a loadItems() method to initialize all items in the AnimatedList in initState(). Remember to update both the underlying data structure (_listItems) and AnimatedList itself for it to work.

    var _listItems = <Widget>[];
    final GlobalKey<AnimatedListState> _listKey = GlobalKey();
    
    @override
    void initState() {
      super.initState();
    
      _loadItems();
    }
    
    void _loadItems() {
      // fetching data from web api, local db...
      final fetchedList = [
        ListTile(
          title: Text('Economy'),
          trailing: Icon(Icons.directions_car),
        ),
        ListTile(
          title: Text('Comfort'),
          trailing: Icon(Icons.motorcycle),
        ),
        ListTile(
          title: Text('Business'),
          trailing: Icon(Icons.flight),
        ),
      ];
    
      var future = Future(() {});
      for (var i = 0; i < fetchedList.length; i++) {
        future = future.then((_) {
          return Future.delayed(Duration(milliseconds: 100), () {
            _listItems.add(fetchedList[i]);
            _listKey.currentState.insertItem(i);
          });
        });
      }
    }
    

    This is the complete example. I added 2 buttons in the app bar so you can play around with the animations

    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'YourAwesomeApp',
          home: PageWithAnimatedList(),
        );
      }
    }
    
    class PageWithAnimatedList extends StatefulWidget {
      @override
      _PageWithAnimatedListState createState() => _PageWithAnimatedListState();
    }
    
    class _PageWithAnimatedListState extends State<PageWithAnimatedList> {
      var _listItems = <Widget>[];
      final GlobalKey<AnimatedListState> _listKey = GlobalKey();
    
      @override
      void initState() {
        super.initState();
    
        _loadItems();
      }
    
      void _loadItems() {
        // fetching data from web api, db...
        final fetchedList = [
          ListTile(
            title: Text('Economy'),
            trailing: Icon(Icons.directions_car),
          ),
          ListTile(
            title: Text('Comfort'),
            trailing: Icon(Icons.motorcycle),
          ),
          ListTile(
            title: Text('Business'),
            trailing: Icon(Icons.flight),
          ),
        ];
    
        var future = Future(() {});
        for (var i = 0; i < fetchedList.length; i++) {
          future = future.then((_) {
            return Future.delayed(Duration(milliseconds: 100), () {
              _listItems.add(fetchedList[i]);
              _listKey.currentState.insertItem(_listItems.length - 1);
            });
          });
        }
      }
    
      void _unloadItems() {
        var future = Future(() {});
        for (var i = _listItems.length - 1; i >= 0; i--) {
          future = future.then((_) {
            return Future.delayed(Duration(milliseconds: 100), () {
              final deletedItem = _listItems.removeAt(i);
              _listKey.currentState.removeItem(i,
                  (BuildContext context, Animation<double> animation) {
                return SlideTransition(
                  position: CurvedAnimation(
                    curve: Curves.easeOut,
                    parent: animation,
                  ).drive((Tween<Offset>(
                    begin: Offset(1, 0),
                    end: Offset(0, 0),
                  ))),
                  child: deletedItem,
                );
              });
            });
          });
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            actions: <Widget>[
              IconButton(icon: Icon(Icons.add), onPressed: _loadItems),
              IconButton(icon: Icon(Icons.remove), onPressed: _unloadItems)
            ],
          ),
          body: AnimatedList(
            key: _listKey,
            padding: EdgeInsets.only(top: 10),
            initialItemCount: _listItems.length,
            itemBuilder: (context, index, animation) {
              return SlideTransition(
                position: CurvedAnimation(
                  curve: Curves.easeOut,
                  parent: animation,
                ).drive((Tween<Offset>(
                  begin: Offset(1, 0),
                  end: Offset(0, 0),
                ))),
                child: _listItems[index],
              );
            },
          ),
        );
      }
    }
    

    Live Demo