【问题标题】:Use AnimatedList inside a StreamBuilder在 StreamBuilder 中使用 AnimatedList
【发布时间】: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,
          )
        ],
      ),
    );
  }
}```

【问题讨论】:

    标签: flutter google-cloud-firestore stream-builder flutter-animatedlist


    【解决方案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 代码执行以下操作:

    1. 它将当前消息存储在构建方法之外的列表currentMessageList
    2. 它侦听流以获取新消息并将新列表与currentMessageList 中的前一个列表进行比较。
    3. 它获取列表和循环之间的差异,以更新特定索引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 这意味着它会更新整个列表。
    猜你喜欢
    • 1970-01-01
    • 2019-10-24
    • 2019-01-05
    • 2021-11-17
    • 2020-12-19
    • 1970-01-01
    • 2022-12-25
    • 2021-11-10
    • 2019-11-29
    相关资源
    最近更新 更多