flutter_for_openharmony家庭药箱管理app实战+血压记录实现
本文介绍了在Flutter for OpenHarmony应用中实现血压记录模块的设计方案。该模块包含数据录入、趋势图表展示和健康评估三大核心功能,采用卡片式布局提升用户体验。文章详细讲解了页面结构搭建、成员选择器实现、趋势图表展示(使用fl_chart库)以及最新记录卡片设计等关键技术点。系统可自动判断血压状态,支持多家庭成员数据切换,并通过可视化图表直观展示7天内血压变化趋势。该设计方案注重用

在家庭健康管理中,血压监测是非常重要的一环,特别是对于中老年人群。定期记录血压数据不仅能帮助我们了解身体状况,还能在就医时为医生提供重要的参考依据。本文将详细介绍如何在Flutter for OpenHarmony应用中实现一个功能完善的血压记录模块,包括数据录入、趋势图表展示、健康状态评估等核心功能。
血压记录功能看似简单,实则需要考虑很多细节。比如如何让用户快速录入数据、如何直观展示血压变化趋势、如何根据医学标准智能判断健康状态等。这些都需要我们在设计和开发时仔细思考。
整体设计思路
血压记录模块的核心在于数据的可视化和健康状态的智能判断。我们需要记录收缩压、舒张压和心率三个关键指标,并通过图表直观展示血压变化趋势。同时,系统会根据医学标准自动判断血压状态,给用户提供健康参考。
整个页面采用卡片式布局,顶部是成员选择器,中间展示趋势图表和最新记录,底部列出历史数据。这种设计让用户能够快速了解血压变化情况,发现异常及时就医。卡片之间使用适当的间距分隔,视觉上清晰舒适。
页面结构搭建
首先看页面的整体结构,我们使用Consumer2同时监听家庭成员和健康数据两个Provider:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('血压记录'),
),
body: Consumer2<FamilyProvider, HealthProvider>(
builder: (context, familyProvider, healthProvider, child) {
if (_selectedMemberId == null && familyProvider.members.isNotEmpty) {
_selectedMemberId = familyProvider.members.first.id;
}
final records = _selectedMemberId != null
? healthProvider.getRecordsByMemberAndType(_selectedMemberId!, 'blood_pressure')
: <HealthRecord>[];
return SingleChildScrollView(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildMemberSelector(familyProvider),
SizedBox(height: 16.h),
if (records.isNotEmpty) _buildChart(records),
SizedBox(height: 16.h),
_buildLatestRecord(records),
SizedBox(height: 16.h),
_buildRecordList(records),
],
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () => _showAddDialog(context),
backgroundColor: const Color(0xFF00897B),
child: const Icon(Icons.add),
),
);
}
这里使用Consumer2可以同时监听两个数据源的变化,当家庭成员或健康记录发生变化时,页面会自动刷新。通过getRecordsByMemberAndType方法筛选出当前选中成员的血压记录,确保数据的准确性。
页面使用SingleChildScrollView包裹,让内容可以滚动,适应不同屏幕尺寸。各个模块通过SizedBox进行间隔,视觉上更加舒适。右下角的浮动按钮用于添加新记录,使用主题色背景,与整体风格保持一致。
成员选择器实现
成员选择器让用户可以切换查看不同家庭成员的血压记录:
Widget _buildMemberSelector(FamilyProvider provider) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 12.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8.r),
border: Border.all(color: Colors.grey[300]!),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: _selectedMemberId,
isExpanded: true,
items: provider.members.map((m) {
return DropdownMenuItem(value: m.id, child: Text(m.name));
}).toList(),
onChanged: (value) {
setState(() {
_selectedMemberId = value;
});
},
),
),
);
}
下拉选择器采用白色背景和圆角边框设计,视觉上更加清爽。使用DropdownButtonHideUnderline隐藏默认下划线,让整体样式更统一。当用户切换成员时,通过setState触发页面重建,自动加载对应成员的血压数据。
这种设计的好处是用户可以方便地在不同家庭成员之间切换,查看各自的血压记录。特别是在有老人的家庭中,这个功能非常实用。选择器使用isExpanded属性让下拉框占满容器宽度,避免文字被截断。
趋势图表展示
血压趋势图使用fl_chart库实现,可以同时展示收缩压和舒张压的变化曲线:
Widget _buildChart(List<HealthRecord> records) {
final chartRecords = records.take(7).toList().reversed.toList();
return Container(
height: 200.h,
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: LineChart(
LineChartData(
gridData: FlGridData(show: false),
titlesData: FlTitlesData(show: false),
borderData: FlBorderData(show: false),
lineBarsData: [
LineChartBarData(
spots: chartRecords.asMap().entries.map((e) {
return FlSpot(e.key.toDouble(), (e.value.data['systolic'] as int).toDouble());
}).toList(),
isCurved: true,
color: Colors.red,
barWidth: 2,
dotData: FlDotData(show: true),
),
LineChartBarData(
spots: chartRecords.asMap().entries.map((e) {
return FlSpot(e.key.toDouble(), (e.value.data['diastolic'] as int).toDouble());
}).toList(),
isCurved: true,
color: Colors.blue,
barWidth: 2,
dotData: FlDotData(show: true),
),
],
),
),
);
}
图表取最近7天的数据进行展示,红色曲线代表收缩压,蓝色曲线代表舒张压。使用isCurved让曲线更加平滑,视觉效果更好。通过asMap().entries可以同时获取索引和数据,方便构建图表坐标点。
这里有个小技巧,我们先用take(7)取最近7条记录,然后用reversed反转顺序,这样图表就能按时间顺序从左到右展示。图表隐藏了网格线、坐标轴和边框,让界面更加简洁,突出数据本身。每个数据点都显示圆点标记,方便用户查看具体数值。
最新记录卡片
最新记录卡片是页面的核心,展示最近一次测量的详细数据和健康状态:
Widget _buildLatestRecord(List<HealthRecord> records) {
if (records.isEmpty) {
return Container(
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(12.r),
),
child: Center(
child: Text('暂无血压记录', style: TextStyle(color: Colors.grey[500])),
),
);
}
final latest = records.first;
final systolic = latest.data['systolic'] as int;
final diastolic = latest.data['diastolic'] as int;
final heartRate = latest.data['heartRate'] as int;
String status = '正常';
Color statusColor = Colors.green;
if (systolic >= 140 || diastolic >= 90) {
status = '偏高';
statusColor = Colors.red;
} else if (systolic < 90 || diastolic < 60) {
status = '偏低';
statusColor = Colors.orange;
}
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('最新记录', style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold)),
Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(
color: statusColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(10.r),
),
child: Text(status, style: TextStyle(color: statusColor, fontSize: 12.sp)),
),
],
),
SizedBox(height: 16.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildValueItem('收缩压', '$systolic', 'mmHg', Colors.red),
_buildValueItem('舒张压', '$diastolic', 'mmHg', Colors.blue),
_buildValueItem('心率', '$heartRate', 'bpm', Colors.purple),
],
),
SizedBox(height: 8.h),
Text(
DateFormat('yyyy-MM-dd HH:mm').format(latest.recordDate),
style: TextStyle(fontSize: 12.sp, color: Colors.grey[500]),
),
],
),
);
}
这段代码实现了智能的健康状态判断逻辑。根据医学标准,收缩压≥140或舒张压≥90判定为偏高,收缩压<90或舒张压<60判定为偏低。状态标签使用不同颜色的背景,让用户一眼就能看出血压是否正常。
卡片顶部显示"最新记录"标题和健康状态标签,中间是三个关键指标的数值展示,底部是记录时间。这种布局让信息层次分明,用户可以快速获取关键信息。如果没有记录,则显示友好的空状态提示,引导用户添加第一条记录。
数值展示组件
为了让三个关键指标展示更加统一,我们封装了一个数值展示组件:
Widget _buildValueItem(String label, String value, String unit, Color color) {
return Column(
children: [
Text(label, style: TextStyle(fontSize: 12.sp, color: Colors.grey[600])),
SizedBox(height: 4.h),
Text(value, style: TextStyle(fontSize: 24.sp, fontWeight: FontWeight.bold, color: color)),
Text(unit, style: TextStyle(fontSize: 10.sp, color: Colors.grey[500])),
],
);
}
这个组件采用垂直布局,标签在上、数值居中、单位在下。数值使用大号粗体字体突出显示,并根据类型使用不同颜色:收缩压用红色、舒张压用蓝色、心率用紫色,让数据更加直观易读。
这种设计的好处是用户可以快速识别不同的指标,颜色编码让信息传达更加高效。标签和单位使用灰色小号字体,不抢夺数值的视觉焦点。整个组件的高度和宽度都是自适应的,可以很好地适应不同屏幕尺寸。
历史记录列表
历史记录列表展示最近10条血压测量数据:
Widget _buildRecordList(List<HealthRecord> records) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('历史记录', style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold)),
SizedBox(height: 8.h),
...records.take(10).map((record) {
return Container(
margin: EdgeInsets.only(bottom: 8.h),
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8.r),
),
child: Row(
children: [
Text(
DateFormat('MM-dd').format(record.recordDate),
style: TextStyle(fontSize: 14.sp, color: Colors.grey[600]),
),
SizedBox(width: 16.w),
Text(
'${record.data['systolic']}/${record.data['diastolic']}',
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w500),
),
const Spacer(),
Text(
'${record.data['heartRate']} bpm',
style: TextStyle(fontSize: 14.sp, color: Colors.grey[600]),
),
],
),
);
}).toList(),
],
);
}
每条记录采用横向布局,左侧显示日期,中间显示血压值(收缩压/舒张压),右侧显示心率。使用take(10)限制显示数量,避免列表过长影响性能。日期格式简化为月-日,让界面更加简洁。
历史记录列表让用户可以快速浏览过往的血压数据,发现变化趋势。每条记录使用白色背景和圆角设计,与整体风格保持一致。记录之间有适当的间距,避免视觉上过于拥挤。血压值使用稍大的字体和中等粗细,作为每条记录的视觉焦点。
添加记录对话框
点击浮动按钮可以弹出添加记录的对话框:
void _showAddDialog(BuildContext context) {
final systolicController = TextEditingController();
final diastolicController = TextEditingController();
final heartRateController = TextEditingController();
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('添加血压记录'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: systolicController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(labelText: '收缩压 (mmHg)'),
),
TextField(
controller: diastolicController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(labelText: '舒张压 (mmHg)'),
),
TextField(
controller: heartRateController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(labelText: '心率 (bpm)'),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
TextButton(
onPressed: () {
if (_selectedMemberId != null) {
final member = context.read<FamilyProvider>().getMemberById(_selectedMemberId!);
if (member != null) {
final record = HealthRecord(
id: const Uuid().v4(),
memberId: member.id,
memberName: member.name,
recordDate: DateTime.now(),
type: 'blood_pressure',
data: {
'systolic': int.tryParse(systolicController.text) ?? 0,
'diastolic': int.tryParse(diastolicController.text) ?? 0,
'heartRate': int.tryParse(heartRateController.text) ?? 0,
},
);
context.read<HealthProvider>().addRecord(record);
}
}
Navigator.pop(context);
Get.snackbar('成功', '血压记录已添加', snackPosition: SnackPosition.BOTTOM);
},
child: const Text('保存'),
),
],
),
);
}
对话框包含三个输入框,分别用于输入收缩压、舒张压和心率。使用TextInputType.number限制只能输入数字,提升用户体验。保存时使用int.tryParse进行安全的类型转换,避免输入非法字符导致崩溃。
这个对话框的设计非常简洁,只包含必要的输入项,降低用户的操作负担。输入框使用标准的Material Design样式,带有浮动标签,用户体验良好。保存成功后会显示提示信息,让用户知道操作已完成。使用Uuid生成唯一ID,确保每条记录都有唯一标识。
总结
血压记录功能是健康管理模块的重要组成部分。通过图表可视化、智能状态判断、多成员支持等设计,让用户能够方便地记录和追踪血压变化。这种数据驱动的健康管理方式,可以帮助用户及时发现异常,做好健康预防工作。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)