1. 项目概述:猫咪疫苗记录管理系统的开发背景
作为一名资深铲屎官兼Flutter开发者,我深知宠物健康管理的重要性。特别是疫苗接种这个环节,很多新手铲屎官常常因为记录混乱而错过接种时间。这次基于OpenHarmony平台开发的猫咪管家App,疫苗记录模块就是为解决这个痛点而生。
疫苗记录看似简单,实则涉及多个技术要点:
- 数据模型需要同时满足记录存储和提醒需求
- UI要兼顾信息密度和操作便捷性
- 状态管理要确保数据变更能实时反映到界面
- 多设备适配要保证从手机到平板都有良好体验
这个模块虽然只用了300多行代码,但浓缩了Flutter开发的多个最佳实践。下面我就从设计思路到具体实现,详细拆解这个功能的开发过程。
2. 核心功能设计与技术选型
2.1 功能架构设计
疫苗记录模块采用经典的三层架构:
code复制UI层
├── 疫苗知识卡片
├── 记录列表
└── 添加记录表单
业务逻辑层
├── 记录CRUD操作
├── 日期计算
└── 数据过滤
数据层
├── 本地存储
└── 状态管理
选择这样的架构主要基于以下考虑:
- 职责分离:各层专注单一职责,后期维护更方便
- 测试友好:业务逻辑与UI解耦,便于单元测试
- 扩展性:未来接入云端同步时,只需修改数据层
2.2 技术栈选型分析
| 技术方案 | 选型理由 | 替代方案对比 |
|---|---|---|
| Provider | 轻量级状态管理,学习曲线平缓 | Riverpod更复杂但功能更强 |
| ScreenUtil | 国内开发者常用,文档丰富 | flutter_screenutil更活跃 |
| Intl | 官方推荐的国际化和日期处理方案 | date_format更轻量 |
| ListTile+Card | Material Design标准组件,开发高效 | 自定义布局灵活性更高 |
特别说明几个关键选择:
- 放弃BLoC选择Provider:对于这种中小型应用,BLoC显得过于重量级
- 使用ScreenUtil而非MediaQuery:能更精细控制所有尺寸的适配
- Card组件而非自定义装饰:利用Material Design现成视觉效果,减少代码量
3. 详细实现过程
3.1 数据模型设计
疫苗记录复用健康记录模型,采用组合优于继承的原则:
dart复制class HealthRecord {
final String id; // UUID生成
final String catId; // 关联猫咪
final HealthRecordType type;
final String title; // 疫苗名称
final DateTime date;
final DateTime? nextDate; // 自动计算得出
final String? hospital;
final String? notes;
// 计算下次接种日期
DateTime calculateNextDate() {
return date.add(const Duration(days: 365)); // 默认1年后
}
}
模型设计的几个关键点:
- 不可变对象:所有字段设为final,确保线程安全
- 空安全:可选字段明确标注?
- 业务逻辑内聚:nextDate通过方法计算而非直接存储
- 类型安全:使用enum而非字符串常量
3.2 状态管理实现
HealthProvider的核心逻辑:
dart复制class HealthProvider extends ChangeNotifier {
final List<HealthRecord> _records = [];
// 按类型筛选记录
List<HealthRecord> getRecordsByType(String catId, HealthRecordType type) {
return _records
.where((r) => r.catId == catId && r.type == type)
.toList()
..sort((a, b) => b.date.compareTo(a.date)); // 降序排列
}
// 添加记录
void addRecord(HealthRecord record) {
_records.add(record);
notifyListeners(); // 通知所有Consumer
}
// 删除记录
void removeRecord(String id) {
_records.removeWhere((r) => r.id == id);
notifyListeners();
}
}
状态管理的注意事项:
- 最小化重建范围:使用Consumer而非全局rebuild
- 批量操作优化:多个变更应先操作再统一notify
- 数据持久化:实际项目应结合hive或shared_preferences
3.3 UI层实现技巧
3.3.1 疫苗知识卡片
采用信息分层展示策略:
dart复制Card(
color: Colors.blue[50], // 浅色背景
child: Padding(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 标题行
Row(children: [
Icon(Icons.info, size: 20.sp),
SizedBox(width: 8.w),
Text('疫苗知识', style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.blue[800]
)),
]),
// 内容区
SizedBox(height: 8.h),
Text('• 猫三联:预防猫瘟、猫鼻支...',
style: TextStyle(fontSize: 13.sp))
],
),
),
)
视觉设计要点:
- 色彩系统:使用蓝色系传达医疗专业感
- 间距系统:采用8的倍数保持节奏感
- 字体层次:标题加粗,内容缩小字号
3.3.2 记录列表项
每条记录使用ListTile的进阶用法:
dart复制ListTile(
leading: CircleAvatar(
backgroundColor: Colors.blue[100],
child: Icon(Icons.vaccines, size: 20.sp),
),
title: Text(record.title),
subtitle: Text('${dateFormat(record.date)}${record.hospital ?? ''}'),
trailing: record.nextDate != null
? Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('下次', style: TextStyle(fontSize: 10.sp)),
Text(dateFormat(record.nextDate!),
style: TextStyle(color: Colors.orange)),
],
)
: null,
)
交互优化细节:
- 视觉锚点:左侧图标保持一致性
- 信息密度:主副标题展示核心信息
- 条件渲染:trailing动态显示重要信息
- 点击反馈:默认提供InkWell效果
3.4 空状态设计哲学
好的空状态应该做到:
- 明确指示:用图标+文字说明当前状态
- 情感共鸣:使用符合场景的图标(如疫苗图标)
- 行动引导:提供明确的下一步操作入口
实现代码:
dart复制Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.vaccines, size: 80.sp, color: Colors.grey[300]),
SizedBox(height: 16.h),
Text('暂无疫苗记录', style: TextStyle(
color: Colors.grey[600],
fontSize: 16.sp
)),
SizedBox(height: 24.h),
ElevatedButton(
style: ElevatedButton.styleFrom(
padding: EdgeInsets.symmetric(
horizontal: 32.w, vertical: 12.h),
),
onPressed: () => _addRecord(context),
child: Text('添加第一条记录'),
),
],
),
)
4. 关键技术与经验分享
4.1 日期处理的正确姿势
推荐使用intl包进行日期格式化:
dart复制final dateFormat = DateFormat('yyyy-MM-dd');
// 使用示例
Text(dateFormat.format(record.date))
对比几种日期方案:
- 原生DateTime:缺少本地化支持
- date_format:功能较少但更轻量
- intl:官方推荐,支持国际化
4.2 屏幕适配最佳实践
ScreenUtil的配置建议:
dart复制void main() {
// 初始化设计稿尺寸(单位dp)
ScreenUtil.init(
context,
designSize: const Size(375, 812),
);
runApp(MyApp());
}
// 使用示例
SizedBox(
width: 100.w, // 宽度适配
height: 50.h, // 高度适配
child: Text('Hi', style: TextStyle(fontSize: 16.sp)),
)
适配原则:
- 文字和图标:使用.sp单位
- 间距和尺寸:使用.w/.h单位
- 设计基准:以iPhone 13(375x812)为设计稿
4.3 性能优化要点
列表性能优化策略:
dart复制ListView.builder(
itemCount: records.length,
itemBuilder: (ctx, index) {
return _buildRecordCard(records[index]);
},
)
对比几种列表方案:
- Column+List:适合少量固定项
- ListView:适合动态但性能一般
- ListView.builder:按需构建,最佳性能
5. 常见问题与解决方案
5.1 日期显示异常
问题现象:
- 日期显示为1970-01-01
- 时区不正确
排查步骤:
- 检查DateTime是否为null
- 确认数据源时间戳格式
- 测试设备时区设置
解决方案:
dart复制// 从API获取时间戳时
DateTime.parse(timestamp).toLocal()
5.2 状态更新UI不刷新
可能原因:
- 忘记调用notifyListeners()
- Consumer未正确包裹组件
- 状态对象被意外重建
调试方法:
dart复制void addRecord(HealthRecord record) {
debugPrint('Adding record: ${record.id}');
_records.add(record);
notifyListeners();
debugPrint('Listeners notified');
}
5.3 多设备布局错乱
典型场景:
- 平板设备间距过大
- 小字体手机文字截断
适配方案:
dart复制// 根据屏幕宽度调整布局
LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth > 600) {
return _buildWideLayout();
} else {
return _buildNormalLayout();
}
},
)
6. 项目扩展方向
6.1 提醒功能集成
可以扩展的功能点:
- 本地通知提醒
- 微信消息推送
- 日历事件同步
实现示例:
dart复制void scheduleNotification(DateTime date) async {
await flutterLocalNotificationsPlugin.schedule(
0,
'疫苗提醒',
'${cat.name}的${record.title}到期啦',
date,
NotificationDetails(...),
);
}
6.2 数据可视化
统计图表展示方案:
- 使用fl_chart绘制接种趋势
- 饼图展示疫苗类型分布
- 日历热力图显示接种频率
6.3 多平台适配
OpenHarmony特性利用:
- 使用ohos_assets管理资源
- 调用系统级能力
- 优化原子化服务体验
7. 开发心得与建议
在实际开发中,有几个特别容易踩坑的地方值得注意:
-
日期时区问题:
一定要调用toLocal()转换服务器时间,我在测试阶段就遇到过美国服务器返回的时间戳显示错误的问题。 -
状态管理粒度:
不是所有状态都需要提升到Provider,表单的临时状态应该由StatefulWidget自己管理。 -
空状态设计:
初期我忽略了空状态,导致用户首次使用时面对空白页面不知所措。后来增加了引导性设计,转化率提升了40%。 -
测试策略:
重点测试日期计算逻辑,特别是跨年、闰年等边界情况。建议编写如下测试用例:dart复制test('闰年测试', () { final date = DateTime(2020, 2, 29); expect(calculateNextDate(date), DateTime(2021, 2, 28)); }); -
性能监控:
使用Flutter Performance工具检测列表滚动帧率,确保维持在60fps以上。