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

Я использую Анимированный список во флаттере, чтобы загрузить класс списка, при добавлении или удалении элементов анимация работает, но когда список изначально загружен, анимация не работает. Есть ли способ анимировать элементы при первоначальной загрузке списка.

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

2 ответа

Потому как 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],
          );
        },
      ),
    );
  }
}

Вы можете использовать следующий код для добавления и удаления нового элемента с анимацией. Щелкнув значок добавления, вы увидите, как добавляется новый элемент, а также можете щелкнуть значок удаления, чтобы удалить элементы.

      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);
                    },
                  ),
                ),
              ),
            );
          },
        ),
      )
    ],
  );
 }
}
Другие вопросы по тегам