【问题标题】:Weird Behavior of ListView.builder and working with its indexListView.builder 的奇怪行为及其索引
【发布时间】:2021-07-21 15:51:42
【问题描述】:

我正在使用 ListView.builder 和 StreamBuilder 从 Firebase 获取用户消息。我已经为聊天的日期分隔实现了自己的登录。通过它我在聊天之间生成自定义日期消息,以便不同日期的聊天有明确的分隔。但奇怪的是我遇到了列表视图的这种行为:

我猜这是因为 Listview 重用了小部件,并且我在代码中使用了基于索引的逻辑来生成日期消息。 如果无法解决此问题,请建议我另一种显示日期和单独聊天(如 whatsapp)的方法

代码如下:

    StreamBuilder(
          stream: FirebaseFirestore.instance
              .collection('chat')
              .orderBy(
                'createdAt',
                descending: true,
              ) //by default it is in accending order.
              .snapshots(), //with ordering by timestamps!
          builder: (ctx, chatSnapshot) {
            if (chatSnapshot.connectionState == ConnectionState.waiting) {
              return Center(
                child: AdaptiveCircularProgressIndicator(),
              );
            }
    
            final chatDocs = chatSnapshot.data.docs;
            // print('CHATS_DATA : ${chatDocs.data()}');
            for (int i = 0; i < chatDocs.length; i++) {
              print(
                  'data for index : $i\n\t on ${getDateStr(chatDocs[i].data()['createdAt'])} ${chatDocs[i].data()['username']} : ${chatDocs[i].data()['text']}');
            }
            return ListView.builder(
              // addSemanticIndexes: false,
              // addAutomaticKeepAlives: false,
              // addRepaintBoundaries: false,
              // cacheExtent: 10,
              reverse: true,
              itemBuilder: (ctx, index) {
                var putMessage;
                bool putDate = false;
                String currentDate;
                var nextDate;
                if (index == 0) {
                  datesMessage = getDateStr(chatDocs[index].data()['createdAt']);
    
                  currentDate = datesMessage;
                } else {
                  currentDate = getDateStr(chatDocs[index].data()['createdAt']);
                }
                if (index != chatDocs.length - 1)
                  nextDate = getDateStr(chatDocs[index + 1].data()['createdAt']);
    
                // print('for $index : $datesMessage $currentDate');
                bool loadNew = (index == chatDocs.length - 1) ||
                    (index != chatDocs.length - 1 &&
                        chatDocs[index + 1].data()['userId'] !=
                            chatDocs[index].data()['userId']) ||
                    (currentDate != nextDate);
    
                if (index != 0 && datesMessage != currentDate) {
                  putMessage = datesMessage;
                  datesMessage = currentDate;
                  putDate = true;
                  print('date changed.');
                } else {
                  putDate = false;
                }
    
                return Column(
                  children: [
                    // if (index == chatDocs.length - 1) Text(currentDate),
                    if (loadNew)
                      SizedBox(
                        height: 15,
                      ),
                    MessageBubble(
                      chatDocs[index].data()['createdAt'],
                      loadNew ? true : false,
                      chatDocs[index].data()['text'],
                      chatDocs[index].data()['username'],
                      chatDocs[index].data()['userImage'] ?? _defaultProfileImage,
                      chatDocs[index].data()['userId'] ==
                          user.uid, //checks wheather the message is mine or the other user.
                      key: ValueKey(
                        chatDocs[index]
                            .id, //this will ensure that flutter will efficiently re render the UI !
                      ),
                    ),
                    if (putDate)
                      Container(
                        margin: EdgeInsets.only(top: 15),
                        padding: EdgeInsets.all(5),
                        child: Text(
                          putMessage,
                          style: TextStyle(
                            color: Colors.white,
                          ),
                        ),
                        decoration: BoxDecoration(
                          color: Colors.blueGrey,
                          borderRadius: BorderRadius.circular(5),
                        ),
                      ),
                  ],
                );
              },
              itemCount: chatDocs.length,
            );
          },
        );

[查看日期消息,它正在从 7 月 21 日更改为 7 月 19 日!]

<a href="/link/to/site">
  <img src="https://media.giphy.com/media/C2EcGHt68LEW0QAqJP/giphy.gif" />
</a>

这是完整的 GIF:

这是我从 Firebase 获取的数据如果有人需要:

I/flutter (14440): data for index : 0
I/flutter (14440):   on July 21, 2021 Kamini : Wow
I/flutter (14440): data for index : 1
I/flutter (14440):   on July 21, 2021 Kamini : Hi ????
I/flutter (14440): data for index : 2
I/flutter (14440):   on July 21, 2021 Aayush : Hiiii
I/flutter (14440): data for index : 3
I/flutter (14440):   on July 21, 2021 Aayush : Hello
I/flutter (14440): data for index : 4
I/flutter (14440):   on July 21, 2021 Aayush : ????
I/flutter (14440): data for index : 5
I/flutter (14440):   on July 21, 2021 Aayush : Hellooooo ????
I/flutter (14440): data for index : 6
I/flutter (14440):   on July 21, 2021 Kamini : Hi there ????
I/flutter (14440): data for index : 7
I/flutter (14440):   on July 19, 2021 Harshal : Sorry to bother you ????
I/flutter (14440): data for index : 8
I/flutter (14440):   on July 19, 2021 Harshal : Okay, I understand that.
I/flutter (14440): data for index : 9
I/flutter (14440):   on July 19, 2021 Harshal : Do you mind confirming your billing address for me?
I/flutter (14440): data for index : 10
I/flutter (14440):   on July 19, 2021 Aayush : Thanks so much for being patient! We’ll be with you soon.
I/flutter (14440): data for index : 11
I/flutter (14440):   on July 19, 2021 Harshal : Yup  ????
I/flutter (14440): data for index : 12
I/flutter (14440):   on July 19, 2021 Aayush : Did you get it?
I/flutter (14440): data for index : 13
I/flutter (14440):   on July 19, 2021 Aayush : Welcome????
I/flutter (14440): data for index : 14
I/flutter (14440):   on July 19, 2021 Harshal : Yeah, Perfect????
I/flutter (14440): data for index : 15
I/flutter (14440):   on July 19, 2021 Aayush : Got it?
I/flutter (14440): data for index : 16
I/flutter (14440):   on July 19, 2021 Aayush : something like this...
I/flutter (14440): data for index : 17
I/flutter (14440):   on July 19, 2021 Aayush : I appreciate you explaining that to me. I’m going to connect you to our AayushTheApple team. I’ll let them know what you’re reaching out about. ????
I/flutter (14440): data for index : 18
I/flutter (14440):   on July 19, 2021 Harshal : I can see why you’d want that! I’m sorry, but it’s not something that we currently offer right now.
I/flutter (14440): data for index : 19
I/flutter (14440):   on July 19, 2021 Harshal : I hear you. I’m sorry that you’re still having trouble with this. I’m going to talk to my team to see what else we can do here.
I/flutter (14440): data for index : 20
I/flutter (14440):   on July 19, 2021 Aayush : Nobody is perfect. There will come a time where every single customer service representative has to apologize. Apologies are more valuable than credits or discounts. They’re an essential tool in your team’s toolkits.
I/flutter (14440): data for index : 21
I/flutter (14440):   on July 19, 2021 Harshal : Do you mind if we start a co-browsing session so I can help you with this process?
I/flutter (14440): data for index : 22
I/flutter (14440):   on July 19, 2021 Harshal : Sure, why not?
I/flutter (14440): data for index : 23
I/flutter (14440):   on July 17, 2021 Aayush : I think it might be easier if we could co-browse to address your issue. We’ll need to access your screen. Is that alright?

【问题讨论】:

    标签: flutter dart listview


    【解决方案1】:

    您可以使用 ListView.separated() 来做到这一点。

    下面的逻辑可能有点混乱,但我已经尽力描述它了:) 请阅读代码中的cmets。

    import 'package:flutter/material.dart';
    import 'package:intl/intl.dart';
    
    class Message {
      final DateTime dateSent;
      final String text;
    
      Message({
        required this.dateSent,
        required this.text,
      });
    }
    
    class SomePage extends StatelessWidget {
      final separatorDateFormatter =
          DateFormat('dd MMM yyyy'); // Date Formatter for the separator
    
      Widget build(BuildContext context) {
        // We will generate some data to work with as an example
        final messages = <Message>[];
        // Generate 10 days
        for (var day = 1; day < 11; day++) {
          final time = DateTime.now().subtract(Duration(days: day));
          // With 3 messages for each day
          for (var i = 1; i < 4; i++) {
            final messageTime = time.subtract(Duration(hours: i));
            messages.add(Message(
              dateSent: messageTime,
              text: 'Day $day, Message #$i, Time $messageTime',
            ));
          }
        }
    
        return Scaffold(
          backgroundColor: Colors.grey[400],
          appBar: AppBar(
            title: Text('ListView.separated'),
          ),
          body: ListView.separated(
            // We add +1 to the actual list size so that we can build the very first separator (conversation start date)
            itemCount: messages.length + (messages.isNotEmpty ? 1 : 0),
            // Your message widget here
            itemBuilder: (context, index) {
              if (index == 0) {
                final firstMessage = messages.first;
                return _buildDateSeparator(firstMessage.dateSent);
              }
              final message = messages.elementAt(index - 1);
              return _messageWidget(message);
            },
            // This is the place to build the date separator
            separatorBuilder: (context, index) {
              // If index is 0, the return nothing (since we have added +1 to the count above)
              if (index == 0) return SizedBox();
    
              // We take current Message from the list (as usual, -1 because of increased length above)
              final currentMessage = messages.elementAt(index - 1);
    
              // We get the next message from the list
              // No need to check if the message might be null, since **separatorBuilder()** will not be called after the last entry of the list
              final nextMessage = messages.elementAt(index);
    
              // Compare it's date to the current one
              if (!_isSameDate(currentMessage, nextMessage)) {
                // Return date separator if the date doesn't match
                return _buildDateSeparator(nextMessage.dateSent);
              }
    
              // Return nothing otherwise
              return SizedBox();
            },
          ),
        );
      }
    
      // The function that compares the date between two messages
      // This code can be exported to some helper class or refactored in to an Extension for DateTime class
      bool _isSameDate(Message message1, Message message2) =>
          message1.dateSent.year == message2.dateSent.year &&
          message1.dateSent.month == message2.dateSent.month &&
          message1.dateSent.day == message2.dateSent.day;
    
      Widget _messageWidget(Message message) => Container(
            height: 40.0,
            color: Colors.grey,
            margin: EdgeInsets.symmetric(
              vertical: 5.0,
            ),
            child: Center(
              child: Text(message.text),
            ),
          );
    
      Widget _buildDateSeparator(DateTime date) => Container(
            child: Center(
              child: Text(separatorDateFormatter.format(date)),
            ),
          );
    }
    
    

    另请注意,ListView.separated() 不会在 List 的第一项之前和最后一项之后(仅在项之间)调用 separatorBuilder()。因此,为了显示初始日期分隔符(对话开始日期),当我们想要选择当前列表条目时,我们必须将 itemCount 增加 1 并将索引减少 1。

    【讨论】:

    • 感谢您提供详细的解决方案!我会告诉你这是否有效:)
    • 您好!您的解决方案似乎有效,但我有两个疑问,第一个是如何反转列表?我知道通过应用reverse:true 但我们还必须更改一些逻辑以及为什么滚动到末尾时出现范围错误?
    • 您好,非常感谢!您的方法很棒?顺便说一句,我必须进行更改,因为我想要反向 Listview 构建器,以便在索引逻辑方面也有一些更改,但您的解决方案对我有用。
    【解决方案2】:

    我相信你是对的,这是由 ListView.builder 重用小部件引起的。为了解决这个问题,你可以给每个 dateSeparator 一个 GlobalObjectKey(date)。像这样的:

    Widget _buildDateSeparator(DateTime date) => Container(
        child: Center(
        key: GlobalObjectKey(date),
          child: Text(separatorDateFormatter.format(date)),
        ),
      );
    

    我没有测试过,但我以前遇到过类似的问题。

    【讨论】:

    • 感谢您的回复!但是我已经尝试过了,但问题仍然存在:(
    【解决方案3】:

    感谢@Thepeanut, 应用他提供的逻辑后, 这是最终的工作代码:

    StreamBuilder(
          stream: FirebaseFirestore.instance
              .collection('chat')
              .orderBy(
                'createdAt',
                descending: true,
              ) //by default it is in accending order.
              .snapshots(), //with ordering by timestamps!
          builder: (ctx, chatSnapshot) {
            if (chatSnapshot.connectionState == ConnectionState.waiting) {
              return Center(
                child: AdaptiveCircularProgressIndicator(false),
              );
            }
    
            final chatDocs = chatSnapshot.data.docs;
            // print('CHATS_DATA : ${chatDocs.data()}');
            for (int i = 0; i < chatDocs.length; i++) {
              print(
                  'data for index : $i\n\t on ${getDateStr(chatDocs[i].data()['createdAt'])} ${chatDocs[i].data()['username']} : ${chatDocs[i].data()['text']}');
            }
    
            return ListView.separated(
              reverse: true,
              itemBuilder: (context, index) {
                // bool loadNew = false;
                bool loadNew = (index == chatDocs.length - 1) ||
                    (index != chatDocs.length - 1 &&
                        chatDocs[index + 1].data()['userId'] !=
                            chatDocs[index].data()['userId']);
                if (index == chatDocs.length - 1) {
                  var dateMessage = getDateStr(chatDocs[index].data()['createdAt']);
                  print(
                      'index end : itembuilder. DateSepator builded. || $dateMessage');
                  return Column(
                    children: [
                      _buildDateSeparator(dateMessage),
                      _buildMessage(
                        chatDocs: chatDocs,
                        loadNew: loadNew,
                        index: index,
                        user: user,
                      ),
                    ],
                  );
                }
                print(
                    'index $index : itembuilder. Message builded. || ${chatDocs[index].data()['text']}');
                return _buildMessage(
                  chatDocs: chatDocs,
                  loadNew: loadNew,
                  index: index,
                  user: user,
                );
              },
              separatorBuilder: (context, index) {
                if (index == chatDocs.length - 1) {
                  print('\tno separator.');
                  return SizedBox();
                }
                final currentMessageDate =
                    chatDocs[index].data()['createdAt'].toDate();
                // if (index == chatDocs.length - 1) {
                //   var dateMessage =
                //       getDateStr(chatDocs[index - 1].data()['createdAt']);
                //   return _buildDateSeparator(dateMessage);
                // }
                // print('accessing overflow : $index ${chatDocs.length}');
                final nextMessageDate =
                    chatDocs[index + 1].data()['createdAt'].toDate();
                if (!_isSameDate(currentMessageDate, nextMessageDate)) {
                  var dateMessage = getDateStr(chatDocs[index].data()['createdAt']);
                  print('\tDate separator. || $dateMessage');
                  return _buildDateSeparator(dateMessage);
                }
                print('\tno separator end.');
                return SizedBox();
              },
              itemCount: chatDocs.length,
              // itemCount: chatDocs.length + (chatDocs.isNotEmpty ? 1 : 0),
            );
          },
        )
    

    附加功能:

    Widget _buildDateSeparator(String dateMessage) {
        return Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Container(
              // key: GlobalObjectKey(putMessage),
              margin: EdgeInsets.only(top: 15),
              padding: EdgeInsets.all(5),
              child: Text(
                dateMessage,
                textAlign: TextAlign.center,
                style: TextStyle(
                  color: Colors.white,
                ),
              ),
              constraints: BoxConstraints(),
              decoration: BoxDecoration(
                color: Colors.blueGrey,
                borderRadius: BorderRadius.circular(5),
              ),
            ),
          ],
        );
      }
    
      Widget _buildMessage({
        @required var chatDocs,
        @required bool loadNew,
        @required int index,
        @required var user,
      }) {
        return Column(
          children: [
            if (loadNew)
              SizedBox(
                height: 15,
              ),
            MessageBubble(
              chatDocs[index].data()['createdAt'],
              loadNew ? true : false,
              chatDocs[index].data()['text'],
              chatDocs[index].data()['username'],
              chatDocs[index].data()['userImage'] ?? _defaultProfileImage,
              chatDocs[index].data()['userId'] ==
                  user.uid, //checks wheather the message is mine or the other user.
              key: ValueKey(
                chatDocs[index]
                    .id, //this will ensure that flutter will efficiently re render the UI !
              ),
            ),
          ],
        );
      }
    
      bool _isSameDate(DateTime date1, DateTime date2) {
        return date1.year == date2.year &&
            date1.month == date2.month &&
            date1.day == date2.day;
      }
    

    请参阅他的 answer 了解需要 cmets 的逻辑!

    【讨论】:

      猜你喜欢
      • 2015-07-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多