在 StreamBuilder 中使用 AnimatedList

Posted

技术标签:

【中文标题】在 StreamBuilder 中使用 AnimatedList【英文标题】:Use AnimatedList inside a StreamBuilder 【发布时间】:2021-08-27 22:24:53 【问题描述】:

我正在使用 firebase 构建一个聊天应用程序,并且我目前将每条消息作为文档存储在 firebase 的集合中。我使用 StreamBuilder 获取最新消息并显示它们。我想在收到和发送新消息时添加动画。我尝试过使用 Animatedlist,但是,我不知道如何使它与 StreamBuilder 一起使用。据我了解,每次添加新消息时,我都必须调用 insertItem 函数。有更聪明的方法吗?或者这将如何实现?

这是我目前所拥有的:

class Message 
  final String uid;
  final String message;
  final Timestamp timestamp;

  Message(this.uid, this.timestamp, this.message);


class MessagesWidget extends StatefulWidget 
  final String receiver;
  MessagesWidget(@required this.receiver);

  @override
  _MessagesWidgetState createState() => _MessagesWidgetState();


class _MessagesWidgetState extends State<MessagesWidget>
  final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();

  Tween<Offset> _offset = Tween(begin: Offset(1,0), end: Offset(0,0));

  @override
  Widget build(BuildContext context) 
    final user = Provider.of<User>(context);
    return Container(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.center,
        mainAxisSize: MainAxisSize.max,
        mainAxisAlignment: MainAxisAlignment.end,
        children: <Widget>[
          Expanded(
            child: StreamBuilder<List<Message>>(
                stream: DatabaseService(uid: user.uid).getMessages(widget.receiver),
                builder: (context, snapshot) 
                  switch (snapshot.connectionState) 
                    case ConnectionState.waiting:
                      return Loading();
                    default:
                      final messages = snapshot.data;
                      return messages.isEmpty
                          ? SayHi(userID: widget.receiver,)
                          : AnimatedList(
                              key: _listKey,
                              physics: BouncingScrollPhysics(),
                              reverse: true,
                              initialItemCount: messages.length,
                              itemBuilder: (context, index, animation) 
                                final message = messages[index];
                                return SlideTransition(
                                    position: animation.drive(_offset),
                                    child: MessageWidget(
                                    message: message,
                                    userID: widget.receiver,
                                    isCurrentUser: message.uid == user.uid,
                                  ),
                                );
                              ,
                            );
                  
                ),
          ),
          SizedBox(
            height: 10,
          ),
          NewMessage(
            receiver: widget.receiver,
          )
        ],
      ),
    );
  
```

【问题讨论】:

【参考方案1】:

您可以将小部件的 State 更新为以下内容:

class _MessagesWidgetState extends State<MessagesWidget> 
  final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();

  Tween<Offset> _offset = Tween(begin: Offset(1, 0), end: Offset(0, 0));

  Stream<List<Message>> stream;

  List<Message> currentMessageList = [];

  User user;

  @override
  void initState() 
    super.initState();

    user = Provider.of<User>(context, listen: false);

    stream = DatabaseService(uid: user.uid).getMessages(widget.receiver);

    stream.listen((newMessages) 
      final List<Message> messageList = newMessages;

      if (_listKey.currentState != null &&
          _listKey.currentState.widget.initialItemCount < messageList.length) 
        List<Message> updateList =
            messageList.where((e) => !currentMessageList.contains(e)).toList();

        for (var update in updateList) 
          final int updateIndex = messageList.indexOf(update);
          _listKey.currentState.insertItem(updateIndex);
        
      

      currentMessageList = messageList;
    );
  

  @override
  Widget build(BuildContext context) 
    return Container(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.center,
        mainAxisSize: MainAxisSize.max,
        mainAxisAlignment: MainAxisAlignment.end,
        children: <Widget>[
          Expanded(
            child: StreamBuilder<List<Message>>(
                stream: stream,
                builder: (context, snapshot) 
                  switch (snapshot.connectionState) 
                    case ConnectionState.waiting:
                      return Loading();
                    default:
                      final messages = snapshot.data;
                      return messages.isEmpty
                          ? SayHi(
                              userID: widget.receiver,
                            )
                          : AnimatedList(
                              key: _listKey,
                              physics: BouncingScrollPhysics(),
                              reverse: true,
                              initialItemCount: messages.length,
                              itemBuilder: (context, index, animation) 
                                final message = messages[index];
                                return SlideTransition(
                                  position: animation.drive(_offset),
                                  child: MessageWidget(
                                    message: message,
                                    userID: widget.receiver,
                                    isCurrentUser: message.uid == user.uid,
                                  ),
                                );
                              ,
                            );
                  
                ),
          ),
          SizedBox(
            height: 10,
          ),
          NewMessage(
            receiver: widget.receiver,
          )
        ],
      ),
    );
  


另外,将您的 Message 类更新为以下代码:

// Using the equatable package, remember to add it to your pubspec.yaml file
import 'package:equatable/equatable.dart';

class Message extends Equatable
  final String uid;
  final String message;
  final Timestamp timestamp;

  Message(this.uid, this.timestamp, this.message);

  @override
  List<Object> get props => [uid, message, timestamp];


说明:

上面的State 代码执行以下操作:

    它将当前消息存储在构建方法之外的列表currentMessageList中 它侦听流以获取新消息并将新列表与currentMessageList 中的前一个列表进行比较。 它获取列表和循环之间的差异,以更新特定索引updateIndex 处的AnimatedList 小部件。

上面的Message 代码执行以下操作:

它覆盖== 运算符和对象hashcode 以允许在此行中进行检查:List&lt;Message&gt; updateList = messageList.where((e) =&gt; !currentMessageList.contains(e)).toList(); 按预期工作。 [如果不覆盖这些 getter,检查将失败,因为具有相同值的两个不同 Message 对象将不等价]。 它使用equatable 包来避免样板。

【讨论】:

谢谢!这种方法似乎奏效了。但是,由于某种原因,整个列表都是动画的,而不仅仅是一条新消息。你会碰巧知道如何改变它吗?非常感谢您的详细回答。 当你说“整个列表是动画的”时,这是指屏幕第一次打开的时候还是你向集合中添加新文档的时候?另外,请附上屏幕录像,因为这将有助于调试。 我的意思是整个消息列表都是动画的,而不仅仅是添加的消息。这是因为这行代码:List&lt;Message&gt; updateList = (messageList.where((e) =&gt; !currentMessageList.contains(e))).toList(); updateList 始终包含流的所有元素,这意味着它们被插入到动画列表中,因此所有元素都是动画的。我不知道如何插入视频,但是,它基本上只是在我添加新消息时使整个列表动画。 你在流监听器的 initState 中有这行:currentMessageList = messageList; 吗? 是的,我愿意。我复制了您直接发送的代码,仅将两次出现的“List”更改为“List”,否则代码相同。打印 updateList 的长度并在更新循环中打印 updateIndex 给出:26,0,1...25 这意味着它会更新整个列表。

以上是关于在 StreamBuilder 中使用 AnimatedList的主要内容,如果未能解决你的问题,请参考以下文章

在 StreamBuilder 中使用 SnackBar 的奇特方式是啥?

在 Streambuilder 中的集合上使用 where() 不断返回 null

在 Flutter 中使用 streamBuilder 时条件不起作用

如何在 Flutter 的 StreamBuilder 中使用 Future [重复]

FutureBuilder 与 Streambuilder

使用streambuilder颤动firestore分页