Flutter:在ListView底部显示消息而不使用reverse:true

问题描述 投票:0回答:1

问题描述:我正在 Flutter 中开发一个聊天应用程序,其中使用

ListView.separated
显示消息。我希望消息显示在屏幕底部(就像典型的聊天应用程序一样)。我无法使用内置
reverse: true
属性,因为它会导致不必要地重建音频消息等问题。

目前,我的代码如下所示:

ListView.separated(
  controller: controller,
  cacheExtent: 1000,
  itemCount: state.messages.length,
  separatorBuilder: (context, index) {
    if (index == state.messages.length - 1) {
      return const SizedBox.shrink();
    }

    final currentMessage = state.messages[index];
    final nextMessage = state.messages[index + 1];

    final currentDate = currentMessage.timestamp.toLocal();
    final nextDate = nextMessage.timestamp.toLocal();

    final currentFormattedDate = _formatDate(currentDate);
    final nextFormattedDate = _formatDate(nextDate);

    return currentFormattedDate != nextFormattedDate
        ? Center(
            child: Padding(
              padding: const EdgeInsets.symmetric(vertical: 8.0),
              child: Text(
                currentFormattedDate,
                style: const TextStyle(color: Colors.grey),
              ),
            ),
          )
        : const SizedBox.shrink();
  },
  itemBuilder: (context, index) {
    final reverseIndex = state.messages.length - 1 - index;
    final message = state.messages[reverseIndex];
    final isMe = DI.find<Tinode>().isMe(message.from);
    final originalMessageOfReply = state.messages.firstWhereOrNull(
        (m) => m.seq == message.replySeq);

    return GestureDetector(
      onTap: () {
        if (state.selectedMessageIds.isNotEmpty && message.seq != null) {
          context.read<ChatRoomBloc>().add(ToggleMessageSelection(message.seq!));
        }
      },
      onLongPress: () {
        _openOptionsOverlay(context, chatMessage: message, state: state);
      },
      child: Container(
        color: state.selectedMessageIds.contains(message.seq)
            ? Colors.blue.withOpacity(0.2)
            : Colors.transparent,
        child: MessageBubble(
          message: message,
          isMe: isMe,
          receiver: widget.receiverName,
          replyMessage: originalMessageOfReply,
        ),
      ),
    );
  },
)


我尝试过的:

我尝试过使用

reverse: true,
,虽然它可以工作,但它会重建一些组件,例如音频消息,这会导致性能问题。 我尝试手动反转列表,但这并没有将消息放置在底部。

Expected Outcome:
消息应从屏幕底部向上显示,而不使用反向:true,并且不会导致不必要的重建。

Question:
我怎样才能有效地实现这种行为?有没有更好的方法可以将消息粘贴到底部而不反转 ListView?

flutter dart flutter-dependencies
1个回答
0
投票

1。初始化一个ScrollController:

ScrollController _scrollController = ScrollController();

2。消息更新时滚动至底部:

将新消息添加到列表后,滚动到 ListView 的末尾:

void _scrollToBottom() {
if (_scrollController.hasClients) {
    _scrollController.jumpTo(_scrollController.position.maxScrollExtent);
  }
}

3.更新你的ListView:

将 ScrollController 传递给 ListView.separated

ListView.separated(
controller: _scrollController,
  itemCount: messages.length,
  itemBuilder: (context, index) {
    return MessageWidget(message: messages[index]);
  },
  separatorBuilder: (context, index) => SizedBox(height: 8),
);

4。有新消息时调用 _scrollToBottom:

每当您向列表添加新消息时,请确保调用 _scrollToBottom 以保持视图更新。

void addMessage(String newMessage) {
setState(() {
    messages.add(newMessage);
  });
  _scrollToBottom();
}

示例:

import 'package:flutter/material.dart';

class ChatScreen extends StatefulWidget {
  @override
  _ChatScreenState createState() => _ChatScreenState();
}

class _ChatScreenState extends State<ChatScreen> {
  final ScrollController _scrollController = ScrollController();
  final List<Message> messages = [];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Chat")),
      body: Column(
        children: [
          Expanded(
            child: ListView.separated(
              controller: _scrollController,
              itemCount: messages.length,
              itemBuilder: (context, index) {
                return MessageWidget(message: messages[index]);
              },
              separatorBuilder: (context, index) => SizedBox(height: 8),
            ),
          ),
          _MessageInputField(onSend: _addMessage),
        ],
      ),
    );
  }

  void _scrollToBottom() {
    if (_scrollController.hasClients) {
      _scrollController.jumpTo(_scrollController.position.maxScrollExtent);
    }
  }

  void _addMessage(String content, MessageType type) {
    setState(() {
      messages.add(Message(content: content, type: type));
    });
    _scrollToBottom();
  }
}

class _MessageInputField extends StatefulWidget {
  final void Function(String, MessageType) onSend;

  _MessageInputField({required this.onSend});

  @override
  State<_MessageInputField> createState() => _MessageInputFieldState();
}

class _MessageInputFieldState extends State<_MessageInputField> {
  final TextEditingController _controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Row(
        children: [
          Expanded(
            child: TextField(
              controller: _controller,
              decoration: InputDecoration(hintText: "Enter message..."),
            ),
          ),
          IconButton(
            icon: Icon(Icons.send),
            onPressed: () {
              if (_controller.text.trim().isNotEmpty) {
                widget.onSend(_controller.text.trim(), MessageType.text);
                _controller.clear();
              }
            },
          ),
        ],
      ),
    );
  }
}

class MessageWidget extends StatelessWidget {
  final Message message;

  MessageWidget({required this.message});

  @override
  Widget build(BuildContext context) {
    switch (message.type) {
      case MessageType.text:
        return _TextMessage(content: message.content);
      case MessageType.audio:
        return _AudioMessage(content: message.content);
      default:
        return SizedBox.shrink();
    }
  }
}

class _TextMessage extends StatelessWidget {
  final String content;

  _TextMessage({required this.content});

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.all(12),
      decoration: BoxDecoration(
        color: Colors.blueAccent,
        borderRadius: BorderRadius.circular(8),
      ),
      child: Text(content, style: TextStyle(color: Colors.white)),
    );
  }
}

class _AudioMessage extends StatefulWidget {
  final String content;

  _AudioMessage({required this.content});

  @override
  State<_AudioMessage> createState() => _AudioMessageState();
}

class _AudioMessageState extends State<_AudioMessage> {
  bool isPlaying = false;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        IconButton(
          icon: Icon(isPlaying ? Icons.pause : Icons.play_arrow),
          onPressed: () {
            setState(() {
              isPlaying = !isPlaying;
            });
            // Implement actual audio play/pause logic here
          },
        ),
        Text(isPlaying ? "Playing..." : "Paused"),
      ],
    );
  }
}

enum MessageType { text, audio }

class Message {
  final String content;
  final MessageType type;

  Message({required this.content, required this.type});
}
© www.soinside.com 2019 - 2024. All rights reserved.