Как анимировать элементы, изначально визуализированные с помощью анимированного списка во флаттере

Я использую Animated 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,
                      )),
                ),
              ),
            ],
          ),
        );
      },
    );
  }
}

person Shashi Kiran    schedule 18.07.2019    source источник
comment
AnimatedList будет анимировать только те элементы, которые добавляются / удаляются. Это означает, что элементы, с которых изначально начинается список, не будут анимированы. Один из вариантов - начать с пустого списка, добавить каждый элемент в свой анимированный список и обновить состояние анимированного списка при визуализации этого виджета.   -  person Alex Fallenstedt    schedule 19.07.2019


Ответы (1)


Потому что AnimatedList может анимировать только при добавлении / удалении элемента в списке. Вам нужно добавить каждый элемент отдельно, используя insertItem или removeItem из AnimatedListState. Один из способов добиться хорошего эффекта загрузки - задерживать каждый раз, когда вы вставляете / удаляете элемент.

демонстрация загрузки / выгрузки элементов списка

Вот код для цепочки Future, чтобы каждый элемент загружался один за другим после указанной задержки.

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

Оттуда вы можете создать loadItems() метод для инициализации всех элементов в AnimatedList в initState(). Не забудьте обновить как базовую структуру данных (_listItems), так и сам AnimatedList, чтобы она работала.

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);
      });
    });
  }
}

Это полный пример. Я добавил 2 кнопки на панели приложений, чтобы вы могли поиграть с анимацией.

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

person NearHuscarl    schedule 01.12.2019
comment
Для меня initState выполняется перед первым build, поэтому _listKey.currentState равно null. Как я могу решить эту проблему? - person Csaba Toth; 23.08.2020
comment
@CsabaToth вы должны использовать _listKey.currentState? .InsertItem .. - person Alex Kosyakov; 28.06.2021
comment
@AlexKosyakov Хотя он не вылетел бы, но и элемент просто не вставил бы. Я хотел бы изначально заполнить список. Думаю, я могу использовать WidgetsBinding.instance?.addPostFrameCallback. - person Csaba Toth; 28.06.2021