Flutter for OpenHarmony 猫咪管家App实战 - 健康记录列表实现
本文介绍了猫咪健康记录列表页面的实现方案。该页面支持按类型筛选(疫苗、驱虫等)、查看记录详情和添加新记录等功能。通过StatefulWidget管理筛选状态,Consumer监听数据变化,ListView展示记录卡片,并处理空状态显示。页面包含筛选菜单、记录列表和悬浮添加按钮,使用Card组件呈现每条记录的标题、类型、日期和费用等信息。该方案提供了完整的健康记录管理界面,支持用户便捷地查看和维护猫

猫咪的健康记录需要一个地方集中展示和管理。今天我们来实现健康记录列表页面,支持按类型筛选、查看详情、添加新记录等功能。
功能需求
健康记录列表需要实现:
- 展示所有健康记录
- 支持按类型筛选(疫苗、驱虫、体检等)
- 显示记录的关键信息
- 空状态友好提示
- 快速添加新记录
这些功能组合起来,就是一个完整的健康记录管理页面。
依赖引入
首先导入需要的包:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:intl/intl.dart';
import '../../providers/health_provider.dart';
import '../../models/health_record.dart';
import 'add_health_record_screen.dart';
Provider用于状态管理,监听数据变化自动刷新UI。
intl包处理日期格式化显示。
有状态组件
列表页面需要维护筛选状态:
class HealthRecordListScreen extends StatefulWidget {
final String catId;
const HealthRecordListScreen({super.key, required this.catId});
State<HealthRecordListScreen> createState() => _HealthRecordListScreenState();
}
catId标识是哪只猫咪的健康记录。
StatefulWidget用于管理筛选类型的状态。
筛选状态
State类中定义筛选变量:
class _HealthRecordListScreenState extends State<HealthRecordListScreen> {
HealthRecordType? _filterType;
_filterType为null时显示全部记录。
选择具体类型后只显示该类型的记录。
页面结构
build方法构建整体布局:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('健康记录'),
actions: [
PopupMenuButton<HealthRecordType?>(
icon: const Icon(Icons.filter_list),
onSelected: (type) => setState(() => _filterType = type),
itemBuilder: (context) => [
const PopupMenuItem(value: null, child: Text('全部')),
...HealthRecordType.values.map((type) => PopupMenuItem(
value: type,
child: Text(_getTypeString(type)),
)),
],
),
],
),
AppBar的actions放置筛选按钮。
PopupMenuButton点击弹出筛选菜单。
数据监听
Consumer监听Provider数据变化:
body: Consumer<HealthProvider>(
builder: (context, provider, child) {
var records = provider.getRecordsForCat(widget.catId);
if (_filterType != null) {
records = records.where((r) => r.type == _filterType).toList();
}
getRecordsForCat获取指定猫咪的所有记录。
如果设置了筛选类型,用where方法过滤。
空状态处理
没有记录时显示引导界面:
if (records.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.medical_services, size: 80.sp, color: Colors.grey[300]),
SizedBox(height: 16.h),
Text('暂无健康记录', style: TextStyle(color: Colors.grey[600])),
],
),
);
}
大图标让页面不会太空。
灰色调表示空状态。
列表展示
有数据时用ListView展示:
return ListView.builder(
padding: EdgeInsets.all(16.w),
itemCount: records.length,
itemBuilder: (context, index) => _buildRecordCard(records[index]),
);
},
),
ListView.builder按需构建列表项,性能更好。
padding给列表留出边距。
悬浮添加按钮
页面底部的FAB:
floatingActionButton: FloatingActionButton(
onPressed: () => Navigator.push(context, MaterialPageRoute(
builder: (_) => AddHealthRecordScreen(catId: widget.catId),
)),
backgroundColor: Colors.orange,
child: const Icon(Icons.add),
),
);
}
FloatingActionButton是Material Design的标准添加按钮。
点击跳转到添加健康记录页面。
记录卡片组件
构建单条记录的卡片:
Widget _buildRecordCard(HealthRecord record) {
return Card(
margin: EdgeInsets.only(bottom: 12.h),
child: ListTile(
leading: CircleAvatar(
backgroundColor: _getTypeColor(record.type).withOpacity(0.1),
child: Icon(_getTypeIcon(record.type), color: _getTypeColor(record.type)),
),
title: Text(record.title),
Card提供阴影和圆角效果。
CircleAvatar显示类型对应的图标。
副标题显示详细信息:
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('${record.typeString} · ${record.hospital ?? ""}'),
Text(DateFormat('yyyy-MM-dd').format(record.date)),
],
),
显示记录类型、医院和日期。
医院为null时显示空字符串。
费用显示:
trailing: record.cost != null
? Text('¥${record.cost!.toStringAsFixed(0)}', style: TextStyle(color: Colors.orange))
: null,
),
);
}
有费用时显示金额,没有则不显示。
橙色突出显示费用信息。
类型转换方法
枚举转中文:
String _getTypeString(HealthRecordType type) {
switch (type) {
case HealthRecordType.vaccination: return '疫苗接种';
case HealthRecordType.deworming: return '驱虫';
case HealthRecordType.checkup: return '体检';
case HealthRecordType.surgery: return '手术';
case HealthRecordType.medication: return '用药';
case HealthRecordType.other: return '其他';
}
}
switch语句处理每种类型。
六种类型覆盖常见的健康记录场景。
类型颜色映射
不同类型用不同颜色区分:
Color _getTypeColor(HealthRecordType type) {
switch (type) {
case HealthRecordType.vaccination: return Colors.blue;
case HealthRecordType.deworming: return Colors.green;
case HealthRecordType.checkup: return Colors.purple;
case HealthRecordType.surgery: return Colors.red;
case HealthRecordType.medication: return Colors.orange;
case HealthRecordType.other: return Colors.grey;
}
}
疫苗用蓝色,驱虫用绿色,手术用红色。
颜色区分让用户一眼就能识别类型。
类型图标映射
不同类型用不同图标:
IconData _getTypeIcon(HealthRecordType type) {
switch (type) {
case HealthRecordType.vaccination: return Icons.vaccines;
case HealthRecordType.deworming: return Icons.bug_report;
case HealthRecordType.checkup: return Icons.medical_services;
case HealthRecordType.surgery: return Icons.local_hospital;
case HealthRecordType.medication: return Icons.medication;
case HealthRecordType.other: return Icons.note;
}
}
}
图标让类型更直观。
Material Icons提供了丰富的医疗相关图标。
PopupMenuButton详解
弹出菜单按钮的使用:
PopupMenuButton<HealthRecordType?>(
icon: const Icon(Icons.filter_list),
onSelected: (type) => setState(() => _filterType = type),
itemBuilder: (context) => [
const PopupMenuItem(value: null, child: Text('全部')),
...HealthRecordType.values.map((type) => PopupMenuItem(
value: type,
child: Text(_getTypeString(type)),
)),
],
)
icon设置按钮图标。
itemBuilder返回菜单项列表。
筛选逻辑
where方法过滤列表:
if (_filterType != null) {
records = records.where((r) => r.type == _filterType).toList();
}
where返回符合条件的元素。
toList转换为List类型。
Consumer使用
监听Provider数据变化:
Consumer<HealthProvider>(
builder: (context, provider, child) {
var records = provider.getRecordsForCat(widget.catId);
// 使用records构建UI
},
)
builder在数据变化时重新执行。
provider参数可以调用Provider的方法。
CircleAvatar样式
圆形头像的自定义:
CircleAvatar(
backgroundColor: _getTypeColor(record.type).withOpacity(0.1),
child: Icon(_getTypeIcon(record.type), color: _getTypeColor(record.type)),
)
背景色用10%透明度,不会太抢眼。
图标颜色与背景色呼应。
ListTile布局
列表项的标准布局:
ListTile(
leading: CircleAvatar(...), // 左侧图标
title: Text(record.title), // 主标题
subtitle: Column(...), // 副标题
trailing: Text(...), // 右侧内容
)
四个位置可以放置不同内容。
自动处理间距和对齐。
条件渲染
根据数据是否存在决定显示:
trailing: record.cost != null
? Text('¥${record.cost!.toStringAsFixed(0)}', ...)
: null,
有费用时显示金额。
null的trailing不占用空间。
日期格式化
格式化日期显示:
DateFormat('yyyy-MM-dd').format(record.date)
输出类似"2024-03-15"的格式。
intl包提供了丰富的格式化选项。
空状态设计
好的空状态应该包含:
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.medical_services, size: 80.sp, color: Colors.grey[300]),
SizedBox(height: 16.h),
Text('暂无健康记录', style: TextStyle(color: Colors.grey[600])),
],
),
)
大图标让页面不会太空。
文字说明当前状态。
小结
健康记录列表涉及的知识点:
- PopupMenuButton筛选菜单
- Consumer监听数据变化
- 条件渲染和空状态处理
- 颜色和图标的映射
这些技巧在其他列表页面也能复用。
欢迎加入OpenHarmony跨平台开发社区,一起交流Flutter开发经验:
https://openharmonycrossplatform.csdn.net
更多推荐


所有评论(0)