在 StreamBuilder 中使用 AnimatedList

Use AnimatedList inside a StreamBuilder

我正在使用 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,
          )
        ],
      ),
    );
  }
}```

您可以将小部件的 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 class 更新为以下代码:

// 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<Message> updateList = messageList.where((e) => !currentMessageList.contains(e)).toList(); 按预期工作。 [如果不覆盖这些 getter,检查将失败,因为具有相同值的两个不同 Message 对象将不等同。
  • 它使用 equatable 包来避免样板。