
社团管理少不了消息通知这个功能。活动要开始了得通知成员,有人申请加入得通知管理员,系统有更新也得让用户知道。今天我们就来实现一个消息中心页面,把这些消息都集中展示出来。
这个页面要做的事情不复杂:展示消息列表、区分消息类型、点击查看详情。听起来简单,但细节还挺多的。
引入依赖
dart
import 'package:flutter/material.dart';
Material库是Flutter的基础,提供了各种UI组件。
没有它基本上啥也干不了。
dart
import 'package:provider/provider.dart';
Provider用来做状态管理,消息数据从这里拿。
数据变了页面自动刷新,不用操心。
dart
import 'package:intl/intl.dart';
intl库用来格式化日期时间,显示消息发送时间要用到。
这个库功能挺强大的,国际化也靠它。
dart
import '../../providers/app_provider.dart';
我们自己的Provider,存着消息列表数据。
路径根据你项目结构调整。
页面基本结构
dart
class MessageCenterPage extends StatelessWidget {
const MessageCenterPage({super.key});
用StatelessWidget就行,页面不需要自己维护状态。
数据都从Provider拿,简单省事。
dart
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('消息中心')
),
Scaffold搭个架子,AppBar放标题。
标准套路,没啥好说的。
获取消息数据
dart
body: Consumer<AppProvider>(
builder: (context, provider, _) {
final messages = provider.messages;
Consumer监听Provider,数据变了自动重建。
messages就是消息列表,直接拿来用。
空状态处理
dart
if (messages.isEmpty) {
return const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.message_outlined,
size: 64,
color: Colors.grey
),
没有消息的时候显示个空状态,不能让用户看着空白发呆。
放个大图标,看着不那么单调。
dart
SizedBox(height: 16),
Text(
'暂无消息',
style: TextStyle(
color: Colors.grey,
fontSize: 16
)
),
],
),
);
}
图标下面加行文字说明,用户一看就懂。
灰色调表示这是个空状态。
消息列表构建
dart
return ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: messages.length,
itemBuilder: (context, index) {
final message = messages[index];
ListView.builder渲染列表,性能比Column好。
padding加点边距,看着舒服。
消息卡片容器
dart
return Card(
margin: const EdgeInsets.only(
bottom: 12
),
child: ListTile(
每条消息用Card包起来,自带阴影效果。
ListTile是标准的列表项组件,用起来方便。
消息图标
dart
leading: CircleAvatar(
backgroundColor: message.type == 'system'
? Colors.orange.withOpacity(0.1)
: const Color(0xFF4A90E2).withOpacity(0.1),
child: Icon(
message.type == 'system'
? Icons.notifications
: Icons.person,
color: message.type == 'system'
? Colors.orange
: const Color(0xFF4A90E2),
),
),
系统消息用橙色通知图标,普通消息用蓝色人物图标。
一眼就能区分消息类型,不用看内容。
消息标题行
dart
title: Row(
children: [
Text(
message.senderName,
style: const TextStyle(
fontWeight: FontWeight.bold
)
),
标题显示发送者名称,用粗体突出。
系统消息的发送者就是"系统通知"。
dart
const Spacer(),
Text(
_formatTime(message.sendTime),
style: const TextStyle(
fontSize: 12,
color: Colors.grey
),
),
],
),
右边显示发送时间,用灰色小字。
Spacer把时间推到最右边。
消息内容预览
dart
subtitle: Padding(
padding: const EdgeInsets.only(
top: 4
),
child: Text(
message.content,
maxLines: 2,
overflow: TextOverflow.ellipsis
),
),
副标题显示消息内容,最多两行。
超出的部分用省略号,点进去看完整的。
点击事件
dart
onTap: () => _showMessageDetail(
context,
message
),
),
);
},
);
},
),
);
}
点击消息弹出详情,不用跳转新页面。
用户体验更流畅。
时间格式化方法
dart
String _formatTime(DateTime time) {
final now = DateTime.now();
final diff = now.difference(time);
这个方法把时间格式化成友好的文字。
先算出时间差,再决定显示什么。
dart
if (diff.inMinutes < 60) {
return '${diff.inMinutes}分钟前';
}
一小时内显示"几分钟前"。
比显示具体时间更直观。
dart
if (diff.inHours < 24) {
return '${diff.inHours}小时前';
}
一天内显示"几小时前"。
用户一看就知道是今天的消息。
dart
if (diff.inDays < 7) {
return '${diff.inDays}天前';
}
一周内显示"几天前"。
还算是比较新的消息。
dart
return DateFormat('MM-dd').format(time);
}
超过一周就显示具体日期了。
月-日的格式,简洁明了。
消息详情弹窗
dart
void _showMessageDetail(
BuildContext context,
message
) {
showModalBottomSheet(
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(16)
),
),
用底部弹窗显示详情,不用跳转页面。
圆角设计看着更舒服。
dart
builder: (ctx) => Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
mainAxisSize.min让弹窗高度自适应内容。
不会占满整个屏幕。
发送者信息
dart
Row(
children: [
CircleAvatar(
backgroundColor: const Color(0xFF4A90E2).withOpacity(0.1),
child: Text(
message.senderName[0],
style: const TextStyle(
color: Color(0xFF4A90E2)
)
),
),
头像显示发送者名字的首字母。
没有真实头像的时候常用这种方式。
dart
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
message.senderName,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16
)
),
发送者名称用粗体显示。
让用户知道这消息是谁发的。
dart
Text(
DateFormat('yyyy-MM-dd HH:mm').format(
message.sendTime
),
style: const TextStyle(
color: Colors.grey,
fontSize: 12
)
),
],
),
],
),
详情里显示完整的发送时间。
年月日时分都有,信息完整。
分割线
dart
const SizedBox(height: 16),
const Divider(),
const SizedBox(height: 16),
用分割线把头部和内容分开。
视觉上更清晰。
消息正文
dart
Text(
message.content,
style: const TextStyle(
fontSize: 15,
height: 1.6
)
),
const SizedBox(height: 20),
],
),
),
);
}
}
正文用稍大的字号,行高1.6阅读舒适。
底部留点空白,不贴着屏幕边缘。
消息数据模型
dart
class Message {
final String id;
final String senderName;
final String content;
final String type;
final DateTime sendTime;
Message({
required this.id,
required this.senderName,
required this.content,
required this.type,
required this.sendTime,
});
}
消息模型包含这几个字段就够用了。
type区分系统消息和普通消息。
Provider中的消息数据
dart
class AppProvider extends ChangeNotifier {
List<Message> _messages = [];
List<Message> get messages => _messages;
void setMessages(List<Message> messages) {
_messages = messages;
notifyListeners();
}
Provider里存着消息列表。
setMessages更新数据后通知监听者。
添加消息方法
dart
void addMessage(Message message) {
_messages.insert(0, message);
notifyListeners();
}
void clearMessages() {
_messages.clear();
notifyListeners();
}
}
addMessage把新消息插到最前面。
clearMessages清空所有消息。
测试数据
dart
void initTestMessages(AppProvider provider) {
provider.setMessages([
Message(
id: 'msg001',
senderName: '系统通知',
content: '欢迎使用社团管理系统,有任何问题请联系管理员。',
type: 'system',
sendTime: DateTime.now().subtract(
const Duration(minutes: 30)
),
),
开发阶段用测试数据调试页面。
这条是30分钟前的系统消息。
dart
Message(
id: 'msg002',
senderName: '张三',
content: '下周六的活动记得参加,地点在学生活动中心。',
type: 'normal',
sendTime: DateTime.now().subtract(
const Duration(hours: 2)
),
),
这条是2小时前的普通消息。
页面上会显示"2小时前"。
dart
Message(
id: 'msg003',
senderName: '李四',
content: '社团纳新已经开始了,欢迎大家踊跃报名参加。',
type: 'normal',
sendTime: DateTime.now().subtract(
const Duration(days: 3)
),
),
]);
}
这条是3天前的消息。
页面上会显示"3天前"。
页面路由
dart
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const MessageCenterPage()
),
);
从其他页面跳转过来就这么写。
标准的路由跳转方式。
在个人中心添加入口
dart
ListTile(
leading: const Icon(
Icons.message,
color: Color(0xFF4A90E2)
),
title: const Text('消息中心'),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 2
),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(10),
),
child: const Text(
'3',
style: TextStyle(
color: Colors.white,
fontSize: 12
)
),
),
入口可以显示未读消息数量。
红色小圆点很醒目。
dart
const SizedBox(width: 8),
const Icon(Icons.chevron_right),
],
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const MessageCenterPage()
),
);
},
),
点击跳转到消息中心。
trailing里放未读数和箭头。
关于消息已读状态
dart
class Message {
// 其他字段...
final bool isRead;
Message copyWith({bool? isRead}) {
return Message(
// 其他字段...
isRead: isRead ?? this.isRead,
);
}
}
实际项目中消息应该有已读未读状态。
copyWith方便更新单个字段。
标记已读方法
dart
void markAsRead(String messageId) {
final index = _messages.indexWhere(
(m) => m.id == messageId
);
if (index != -1) {
_messages[index] = _messages[index].copyWith(
isRead: true
);
notifyListeners();
}
}
点击消息的时候调用这个方法。
把对应消息标记为已读。
未读消息数量
dart
int get unreadCount => _messages
.where((m) => !m.isRead)
.length;
计算属性,返回未读消息数量。
入口那里显示的红点数字就用这个。
小结
消息中心页面实现起来不算复杂,但细节挺多的。时间格式化要人性化,消息类型要区分清楚,详情展示要方便快捷。
代码里用到的几个技巧:showModalBottomSheet做底部弹窗、DateTime.difference算时间差、条件表达式区分消息类型。这些在其他场景也经常用到。
实际项目中还要考虑消息推送、已读状态同步这些,但那是后端的事了。前端把展示做好就行。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net