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

2

11

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,
                      )),
                ),
              ),
            ],
          ),
        );
      },
    );
  }
}
Opinicus answered 18/7, 2019 at 17:56 Comment(1)
AnimatedList will only animate items that are added / removed. This means items that the list initially starts with will not be animated. One option is to start with an empty list, and append each item to your animated list and update the state of the animated list when this widget is rendered.Welldone
P
23

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

Pseudohemophilia answered 1/12, 2019 at 1:18 Comment(5)
For me the initState executes before the first build, so _listKey.currentState is null. How can I solve that problem?Assiniboine
@CsabaToth you should use _listKey.currentState?.insertItem..Toscanini
@AlexKosyakov Although it wouldn't crash, that would just simply not insert the item either. I'd like to initially populate the list. I think I can use WidgetsBinding.instance?.addPostFrameCallback.Assiniboine
@Pseudohemophilia Great example. One question, why it doesn't need setState when adding items to list? _listItems.add(fetchedList[i]); My own perception is, because adding to list will happen in the future. In parallel, build method is building the UI, so items dropped to _listItems will be built and added to AnimatedList. Am I right? But, what if the UI build is finished before addition complete? I really think that it needs setState, but it is working without it ?!Ehrenberg
@Patzu You're thinking too much brother. It does call setState inside AnimatedList when you call _listKey.currentState?.insertItem() or removeItem(). See this video for the summary. Here is the source for the concrete evidence.Pseudohemophilia
D
0

You can use the following code to add and remove a new item with animation. When you click on the add icon, you can see how a new item is added, and you can also click on the delete icon to delete items.

import 'package:flutter/material.dart';

class DifferentPage extends StatefulWidget {
  const DifferentPage({super.key});

  @override
  State<DifferentPage> createState() => DifferentStatePage();
}

class DifferentStatePage extends State<DifferentPage> {
  final _item = [];
  final GlobalKey<AnimatedListState> _key = GlobalKey();

void _addItem() {
  _item.insert(0, 'Item ${_item.length + 1}');
  _key.currentState!.insertItem( 0,
    duration: const Duration(seconds: 1),
  );
}

void _removeItem(int index) {
  _key.currentState!.removeItem( index, (_, animation) {
    return SizeTransition(
      sizeFactor: animation,
      child: const Card(
        margin: EdgeInsets.all(10),
        color: Colors.red,
        child: ListTile(
          title: Text(
            'Deleted',
            style: TextStyle(fontSize: 22),
          ),
        ),
      ),
    );
  },
  duration: const Duration(milliseconds: 300),
);
  _item.removeAt(index);
}

@override
Widget build(BuildContext context) {
  return Column(
    children: [
      const SizedBox(
      height: 10,
     ),
     IconButton(
        onPressed: _addItem,
        icon: const Icon(Icons.add_box_rounded),
     ),
      Expanded(
        child: AnimatedList(
          key: _key,
          initialItemCount: 0,
          padding: const EdgeInsets.all(10),
          itemBuilder: (context, index, animation) {
            return SizeTransition(
              key: UniqueKey(),
              sizeFactor: animation,
              child: Card(
                margin: const EdgeInsets.all(10),
                color: Colors.amberAccent,
                child: ListTile(
                  title: Text(
                    _item[index],
                    style: const TextStyle(fontSize: 22),
                  ),
                  trailing: IconButton(
                    icon: const Icon(Icons.delete),
                    onPressed: () {
                      _removeItem(index);
                    },
                  ),
                ),
              ),
            );
          },
        ),
      )
    ],
  );
 }
}
Damalis answered 15/7, 2023 at 12:21 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Infeld

© 2022 - 2025 — McMap. All rights reserved.