Flutter for OpenHarmony社团管理App实战:消息中心实现

社团管理少不了消息通知这个功能。活动要开始了得通知成员,有人申请加入得通知管理员,系统有更新也得让用户知道。今天我们就来实现一个消息中心页面,把这些消息都集中展示出来。

这个页面要做的事情不复杂:展示消息列表、区分消息类型、点击查看详情。听起来简单,但细节还挺多的。

引入依赖

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

相关推荐
andr_gale2 小时前
08_flutter中如何优雅的提前获取child的宽高
android·flutter
yingdonglan2 小时前
Flutter 框架跨平台鸿蒙开发 ——AnimatedBuilder性能优化详解
flutter·性能优化·harmonyos
程序员清洒2 小时前
Flutter for OpenHarmony:Icon 与 IconButton — 图标系统集成
前端·学习·flutter·华为
时光慢煮2 小时前
打造跨端驾照学习助手:Flutter × OpenHarmony 实战解析
学习·flutter·华为·开源·openharmony
萧曵 丶3 小时前
JavaScript 函数各种写法和场景
开发语言·javascript·ecmascript
菜鸟小芯3 小时前
【开源鸿蒙跨平台开发先锋训练营】DAY8~DAY13 底部选项卡&首页功能实现
flutter·harmonyos
b2077213 小时前
Flutter for OpenHarmony 身体健康状况记录App实战 - 提醒设置实现
python·flutter·macos·cocoa·harmonyos
踏雪羽翼3 小时前
android 图表实现
android·折线图·弹窗·图表·自定义图标
Yolanda943 小时前
【项目经验】钉钉免密登录实现
前端·javascript·钉钉