Flutter for OpenHarmony二手物品置换App实战 - 聊天对话实现
本文介绍了如何在"闲置换"应用中实现聊天页面功能。主要包含消息气泡展示和消息发送两大核心功能,采用左右分布的设计模式区分用户消息(绿色气泡靠右)和对方消息(白色气泡靠左)。代码实现使用Flutter框架,通过ListView展示消息列表,底部包含输入框和发送按钮。消息气泡根据发送者自动调整布局和颜色,支持长文本自动换行。输入栏采用悬浮设计,包含扩展功能按钮和文本输入区域。这种设
聊天功能是买卖双方沟通的核心,买家询问商品细节、协商价格、约定交易方式都在聊天中完成。今天我们来实现"闲置换"的聊天页面,包括消息气泡展示和消息发送功能。
聊天页面的设计思路
聊天页面的核心是消息列表和输入框。消息列表展示双方的对话,自己发的消息靠右显示绿色气泡,对方的消息靠左显示白色气泡。底部是输入框和发送按钮,支持快速发送文字消息。这种左右分布的布局是聊天界面的标准设计,用户一眼就能分清哪些是自己说的。
完整代码实现
import 'package:flutter/material.dart';
class ChatPage extends StatefulWidget {
final int userId;
final String userName;
const ChatPage({super.key, required this.userId, required this.userName});
State<ChatPage> createState() => _ChatPageState();
}
class _ChatPageState extends State<ChatPage> {
final TextEditingController _messageController = TextEditingController();
final ScrollController _scrollController = ScrollController();
final List<Map<String, dynamic>> _messages = [
{'isMe': false, 'content': '你好,这个还在吗?', 'time': '10:30'},
{'isMe': true, 'content': '在的,有什么问题吗?', 'time': '10:31'},
{'isMe': false, 'content': '可以便宜点吗?', 'time': '10:32'},
];
聊天页面接收userId和userName两个参数,用于显示对方的信息和发送消息时标识接收者。定义了两个Controller:_messageController控制输入框,可以获取用户输入的内容和清空输入框;_scrollController控制消息列表滚动,发送新消息后需要自动滚动到底部让用户看到最新消息。_messages是消息列表,每条消息包含是否是自己发的、消息内容、发送时间,实际项目中还需要消息id、发送状态等字段。
void dispose() {
_messageController.dispose();
_scrollController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.userName),
actions: [
IconButton(icon: const Icon(Icons.more_vert), onPressed: () {}),
],
),
body: Column(
children: [
Expanded(
child: ListView.builder(
controller: _scrollController,
padding: const EdgeInsets.all(16),
itemCount: _messages.length,
itemBuilder: (context, index) => _buildMessageBubble(_messages[index]),
),
),
_buildInputBar(),
],
),
);
}
dispose方法释放两个Controller,这是Flutter开发的好习惯,避免内存泄漏。页面结构用Column垂直排列消息列表和输入框,Expanded让消息列表占据除输入框外的所有空间。AppBar显示对方的用户名,让用户知道是在和谁聊天,右边放更多按钮可以弹出举报、拉黑等选项。消息列表用ListView.builder实现懒加载,传入_scrollController用于控制滚动,padding给列表加内边距让消息不会贴着屏幕边缘。
Widget _buildMessageBubble(Map<String, dynamic> message) {
final isMe = message['isMe'] as bool;
return Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Row(
mainAxisAlignment: isMe ? MainAxisAlignment.end : MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!isMe) ...[
CircleAvatar(
radius: 18,
backgroundColor: Colors.grey[300],
child: Text(widget.userName[0], style: const TextStyle(fontSize: 14)),
),
const SizedBox(width: 8),
],
消息气泡的布局根据是否是自己发的来决定对齐方式。自己的消息MainAxisAlignment.end靠右显示,对方的消息MainAxisAlignment.start靠左显示。crossAxisAlignment: CrossAxisAlignment.start让头像和气泡顶部对齐,如果消息很长换行了,头像不会跑到中间去。对方的消息前面显示头像,用CircleAvatar做成圆形,显示用户名的首字母,实际项目中应该显示真实头像图片。展开运算符...[]配合if实现条件渲染,只有对方的消息才显示头像。
Flexible(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
decoration: BoxDecoration(
color: isMe ? const Color(0xFF07C160) : Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Text(
message['content'],
style: TextStyle(color: isMe ? Colors.white : Colors.black87),
),
),
),
if (isMe) ...[
const SizedBox(width: 8),
CircleAvatar(
radius: 18,
backgroundColor: const Color(0xFF07C160),
child: const Text('我', style: TextStyle(fontSize: 14, color: Colors.white)),
),
],
],
),
);
}
消息气泡用Flexible包裹,这样长消息会自动换行而不是撑破布局超出屏幕。气泡用圆角矩形,自己的消息是绿色背景白色文字,对方的消息是白色背景黑色文字,这种配色方案和微信类似,用户很熟悉。自己的消息后面显示头像,绿色背景加"我"字,和对方的头像形成对比。padding给气泡内容加内边距,文字不会贴着边缘,阅读更舒适。
Widget _buildInputBar() {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, -2),
),
],
),
child: SafeArea(
child: Row(
children: [
IconButton(
icon: const Icon(Icons.add_circle_outline, color: Colors.grey),
onPressed: () {},
),
输入栏用Container包裹,加上向上的阴影让它看起来悬浮在消息列表上方,有层次感。SafeArea确保在有底部安全区域的设备上输入框不会被遮挡,比如iPhone的Home Indicator区域。左边放一个加号按钮,点击可以展开更多功能,比如发送图片、位置、商品链接等,这些功能在二手交易场景中很实用,卖家可以发送商品图片,买家可以发送收货地址。
Expanded(
child: TextField(
controller: _messageController,
decoration: InputDecoration(
hintText: '输入消息...',
hintStyle: TextStyle(color: Colors.grey[400]),
filled: true,
fillColor: Colors.grey[100],
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(20),
borderSide: BorderSide.none,
),
),
),
),
const SizedBox(width: 8),
GestureDetector(
onTap: _sendMessage,
child: Container(
padding: const EdgeInsets.all(10),
decoration: const BoxDecoration(
color: Color(0xFF07C160),
shape: BoxShape.circle,
),
child: const Icon(Icons.send, color: Colors.white, size: 20),
),
),
],
),
),
);
}
输入框用Expanded占据中间的空间,圆角胶囊形状,灰色背景,看起来简洁现代。contentPadding控制文字和边框的距离,让输入框看起来不会太挤。发送按钮是一个绿色圆形,里面放发送图标,用GestureDetector处理点击比IconButton更灵活,可以自定义按钮的样式和大小。点击发送按钮调用_sendMessage方法发送消息。
void _sendMessage() {
if (_messageController.text.trim().isEmpty) return;
setState(() {
_messages.add({
'isMe': true,
'content': _messageController.text,
'time': '${DateTime.now().hour}:${DateTime.now().minute}',
});
});
_messageController.clear();
Future.delayed(const Duration(milliseconds: 100), () {
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
});
}
}
发送消息的方法先检查输入是否为空,trim()去掉首尾空格,避免发送纯空格的无意义消息。然后往消息列表添加新消息,isMe: true表示是自己发的,时间用当前时间。_messageController.clear()清空输入框,方便用户继续输入下一条消息。Future.delayed延迟100毫秒后滚动到底部,这个延迟是为了等待setState完成UI更新,新消息渲染出来后再滚动。animateTo带动画地滚动到maxScrollExtent也就是列表底部,Curves.easeOut让滚动有一个减速的效果,看起来更自然流畅。
聊天功能的扩展
实际项目中聊天功能还需要这些能力:消息类型扩展支持图片、语音、位置、商品卡片等;消息状态显示发送中、已发送、已读等状态;历史消息加载打开聊天页面时加载最近的消息,往上滚动时加载更早的历史消息;实时消息接收用WebSocket保持长连接,对方发消息时实时显示在列表中。
小结
这篇实现了"闲置换"App的聊天页面,包括消息气泡的左右布局、输入框和发送功能。自己的消息绿色靠右,对方的消息白色靠左,发送后自动滚动到底部。这是一个基础的聊天功能,实际项目中还需要扩展更多消息类型和功能。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐


所有评论(0)