如何在颤动中使用动画列表对最初渲染的项目进行动画处理

Posted

技术标签:

【中文标题】如何在颤动中使用动画列表对最初渲染的项目进行动画处理【英文标题】:How to animate the items rendered initially using Animated List in flutter 【发布时间】:2019-11-27 17:25:25 【问题描述】:

我在颤动中使用动画列表加载列表类,在添加或删除项目时,动画可以工作,但是当列表最初加载时,动画不起作用。有没有办法在最初加载列表时为项目设置动画。

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

【问题讨论】:

AnimatedList 只会对添加/删除的项目进行动画处理。这意味着列表最初开始的项目不会被动画化。一种选择是从一个空列表开始,然后将每个项目附加到您的动画列表中,并在呈现此小部件时更新动画列表的状态。 【参考方案1】:

因为AnimatedList 只能在添加/删除列表中的项目时进行动画处理。您需要使用来自AnimatedListStateinsertItemremoveItem 单独添加每个项目。实现良好加载效果的一种方法是延迟每次插入/删除项目。

这是链接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() 方法来初始化AnimatedListinitState() 中的所有项目。请记住更新底层数据结构 (_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

【讨论】:

对我来说,initState 在第一个 build 之前执行,所以 _listKey.currentStatenull。我该如何解决这个问题? @CsabaToth 你应该使用 _listKey.currentState?.insertItem.. @AlexKosyakov 虽然它不会崩溃,但也不会插入该项目。我想最初填充列表。我想我可以使用WidgetsBinding.instance?.addPostFrameCallback @NearHuscarl 很好的例子。一个问题,为什么在将项目添加到列表时不需要setState_listItems.add(fetchedList[i]);我自己的看法是,因为添加到列表将在未来发生。同时, build 方法正在构建 UI,因此删除到 _listItems 的项目将被构建并添加到 AnimatedList。我对吗?但是,如果 UI 构建在添加完成之前完成怎么办?我真的认为它需要setState,但没有它它也能工作?! @Patzu 兄弟你想太多了。当您调用_listKey.currentState?.insertItem()removeItem() 时,它确实在AnimatedList 内部调用setState。有关摘要,请参见 this 视频。 Here 是具体证据的来源。

以上是关于如何在颤动中使用动画列表对最初渲染的项目进行动画处理的主要内容,如果未能解决你的问题,请参考以下文章

在颤动中英雄动画之后开始动画

如何在颤动中制作水平滚动动画

颤动水平列表视图/页面视图,选定元素动画到全宽

使用 CSS attr() 函数和数据标签进行动画计时

如何在颤动中缩小和放大图标动画?

vue的transition-group 下面每个元素分别执行动画