Flutter for OpenHarmony生活助手App实战:睡眠记录功能实现
本文介绍了一个睡眠记录App的功能设计与实现。作者首先强调了睡眠记录对健康意识、作息调整和睡眠质量监测的重要性。在技术实现方面,文章详细讲解了睡眠记录页面的三个核心组件:睡眠总结卡片采用靛蓝-紫色渐变设计,突出显示睡眠时长和具体时间;本周趋势图使用柱状图直观展示一周睡眠变化;睡眠质量部分提供深度睡眠等详细数据。页面布局采用从总览到细节的层次结构,使用StatelessWidget实现数据展示功能,

说起睡眠,这是我最关心的健康指标之一。以前总是熬夜,第二天精神很差,工作效率也低。后来开始记录睡眠时间,发现自己的睡眠质量真的很糟糕。有了数据支撑,我才下决心改善作息。所以在做这个生活助手App时,睡眠记录是我最想实现的功能之一。
为什么睡眠记录这么重要
在开始写代码之前,我先想清楚了这个功能的价值。
第一个是健康意识。很多人不知道自己睡得够不够,通过记录睡眠时间,可以直观地看到自己的睡眠状况。成年人每天应该睡7-9小时,如果长期睡眠不足,对健康影响很大。
第二个是作息调整。通过查看睡眠趋势,可以发现作息规律的问题。比如发现自己周末总是睡得很晚,工作日又起得很早,这种不规律的作息对身体不好。
第三个是睡眠质量。不仅要看睡了多久,还要看睡得好不好。深度睡眠、浅度睡眠、REM睡眠的比例,都会影响睡眠质量。
页面整体设计
睡眠记录页面要给人一种宁静的感觉,我用了深色系的渐变:
class SleepTrackerPage extends StatelessWidget {
const SleepTrackerPage({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('睡眠记录'),
),
body: SingleChildScrollView(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSleepSummary(),
SizedBox(height: 24.h),
_buildWeeklyChart(),
SizedBox(height: 24.h),
_buildSleepQuality(),
],
),
),
);
}
}
为什么用StatelessWidget
这个页面只是展示数据,没有用户交互导致的状态变化,所以用StatelessWidget就够了。数据从外部传入或从数据库读取。
页面布局的三个部分
页面分成三块:睡眠总结卡片、本周趋势图表、睡眠质量详情。这个顺序是经过考虑的,从总览到详细,符合用户的阅读习惯。
睡眠总结卡片
页面顶部是一个醒目的睡眠总结卡片:
Widget _buildSleepSummary() {
return Container(
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Colors.indigo, Colors.purple],
),
borderRadius: BorderRadius.circular(16.r),
),
child: Column(
children: [
Text(
'昨晚睡眠',
style: TextStyle(color: Colors.white70, fontSize: 14.sp),
),
SizedBox(height: 8.h),
Text(
'7小时30分',
style: TextStyle(
color: Colors.white,
fontSize: 36.sp,
fontWeight: FontWeight.bold,
),
),
渐变色的选择
我用了靛蓝色到紫色的渐变,这两种颜色给人一种夜晚、宁静的感觉。和睡眠的主题很搭配。如果用亮色系,会显得太刺眼,不符合睡眠的氛围。
睡眠时长的展示
睡眠时长用36sp的大字号显示,非常醒目。这是用户最关心的数据,所以要放在最显眼的位置。
入睡和起床时间
SizedBox(height: 16.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildTimeInfo('入睡', '23:00'),
_buildTimeInfo('起床', '06:30'),
],
),
除了总时长,还要显示入睡和起床的具体时间。这样用户可以知道自己是几点睡的,几点起的。有些人睡得够久,但睡得太晚,这也不健康。
时间信息的组件
Widget _buildTimeInfo(String label, String time) {
return Column(
children: [
Text(
label,
style: TextStyle(color: Colors.white70, fontSize: 12.sp),
),
SizedBox(height: 4.h),
Text(
time,
style: TextStyle(
color: Colors.white,
fontSize: 18.sp,
fontWeight: FontWeight.bold,
),
),
],
);
}
标签用半透明的白色,时间用纯白色加粗,形成层次关系。这种设计让信息更清晰。
本周睡眠趋势图
趋势图让用户看到一周的睡眠变化:
Widget _buildWeeklyChart() {
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'本周睡眠趋势',
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold),
),
SizedBox(height: 20.h),
SizedBox(
height: 200.h,
child: BarChart(
BarChartData(
alignment: BarChartAlignment.spaceAround,
maxY: 10,
barTouchData: BarTouchData(enabled: false),
为什么用柱状图
睡眠时长用柱状图展示最合适。柱子的高度直观地表示睡眠时长,用户一眼就能看出哪天睡得多,哪天睡得少。
Y轴最大值的设置
maxY: 10表示Y轴最大值是10小时。这个值要根据实际情况调整,如果用户睡眠时间都在7-8小时,设置成10小时刚好。如果设置太大,柱子会显得很矮;设置太小,柱子会顶到天花板。
底部标签的实现
titlesData: FlTitlesData(
show: true,
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) {
const days = ['一', '二', '三', '四', '五', '六', '日'];
if (value.toInt() >= 0 && value.toInt() < days.length) {
return Text(days[value.toInt()], style: TextStyle(fontSize: 12.sp));
}
return const Text('');
},
),
),
leftTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
),
只显示底部的星期标签,其他轴的标签都隐藏。这样图表更简洁,用户不会被太多信息干扰。
柱子数据的构建
barGroups: [
BarChartGroupData(x: 0, barRods: [BarChartRodData(toY: 7, color: Colors.purple)]),
BarChartGroupData(x: 1, barRods: [BarChartRodData(toY: 6.5, color: Colors.purple)]),
BarChartGroupData(x: 2, barRods: [BarChartRodData(toY: 8, color: Colors.purple)]),
BarChartGroupData(x: 3, barRods: [BarChartRodData(toY: 7.5, color: Colors.purple)]),
BarChartGroupData(x: 4, barRods: [BarChartRodData(toY: 6, color: Colors.purple)]),
BarChartGroupData(x: 5, barRods: [BarChartRodData(toY: 8.5, color: Colors.purple)]),
BarChartGroupData(x: 6, barRods: [BarChartRodData(toY: 7.5, color: Colors.purple)]),
],
每个柱子用BarChartGroupData表示,x是位置(星期几),toY是高度(睡眠时长)。所有柱子都用紫色,和顶部卡片的主题色保持一致。
从数据可以看出,周五睡得最少(6小时),周六睡得最多(8.5小时)。这是很常见的模式,工作日睡得少,周末补觉。
睡眠质量详情
睡眠质量比睡眠时长更重要,我用进度条展示各个阶段的睡眠:
Widget _buildSleepQuality() {
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'睡眠质量',
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold),
),
SizedBox(height: 16.h),
_buildQualityItem('深度睡眠', '2小时15分', 0.3, Colors.indigo),
SizedBox(height: 12.h),
_buildQualityItem('浅度睡眠', '4小时30分', 0.6, Colors.blue),
SizedBox(height: 12.h),
_buildQualityItem('REM睡眠', '45分钟', 0.1, Colors.purple),
],
),
);
}
三种睡眠阶段
- 深度睡眠:最重要的睡眠阶段,身体和大脑得到充分休息。用深靛蓝色表示。
- 浅度睡眠:占比最大的睡眠阶段,身体放松但容易被唤醒。用蓝色表示。
- REM睡眠:快速眼动睡眠,做梦的阶段,对记忆巩固很重要。用紫色表示。
质量项的实现
Widget _buildQualityItem(String label, String duration, double percent, Color color) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: TextStyle(fontSize: 14.sp)),
Text(duration, style: TextStyle(fontSize: 12.sp, color: Colors.grey)),
],
),
SizedBox(height: 8.h),
LinearProgressIndicator(
value: percent,
backgroundColor: Colors.grey[200],
color: color,
minHeight: 8.h,
borderRadius: BorderRadius.circular(4.r),
),
],
);
}
每个睡眠阶段显示时长和占比。进度条的长度表示占比,颜色表示睡眠类型。这种可视化比纯数字更直观。
睡眠数据的计算
实际项目中,睡眠数据需要从传感器或手动输入获取:
class SleepRecord {
final DateTime bedTime;
final DateTime wakeTime;
final Duration deepSleep;
final Duration lightSleep;
final Duration remSleep;
SleepRecord({
required this.bedTime,
required this.wakeTime,
required this.deepSleep,
required this.lightSleep,
required this.remSleep,
});
Duration get totalSleep => wakeTime.difference(bedTime);
String get totalSleepFormatted {
final hours = totalSleep.inHours;
final minutes = totalSleep.inMinutes % 60;
return '$hours小时$minutes分';
}
double get deepSleepPercent => deepSleep.inMinutes / totalSleep.inMinutes;
double get lightSleepPercent => lightSleep.inMinutes / totalSleep.inMinutes;
double get remSleepPercent => remSleep.inMinutes / totalSleep.inMinutes;
}
定义一个SleepRecord类来表示睡眠记录,包含入睡时间、起床时间、各阶段睡眠时长。提供一些计算属性,方便获取总时长和各阶段占比。
手动记录睡眠
用户可以手动输入睡眠时间:
class AddSleepRecordPage extends StatefulWidget {
const AddSleepRecordPage({super.key});
State<AddSleepRecordPage> createState() => _AddSleepRecordPageState();
}
class _AddSleepRecordPageState extends State<AddSleepRecordPage> {
DateTime? _bedTime;
DateTime? _wakeTime;
Future<void> _selectBedTime() async {
final date = await showDatePicker(
context: context,
initialDate: DateTime.now().subtract(const Duration(days: 1)),
firstDate: DateTime.now().subtract(const Duration(days: 30)),
lastDate: DateTime.now(),
);
if (date != null) {
final time = await showTimePicker(
context: context,
initialTime: const TimeOfDay(hour: 23, minute: 0),
);
if (time != null) {
setState(() {
_bedTime = DateTime(
date.year,
date.month,
date.day,
time.hour,
time.minute,
);
});
}
}
}
用户先选择日期,再选择时间。这样可以记录过去几天的睡眠数据。
数据验证
Future<void> _saveSleepRecord() async {
if (_bedTime == null || _wakeTime == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('请选择入睡和起床时间')),
);
return;
}
if (_wakeTime!.isBefore(_bedTime!)) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('起床时间不能早于入睡时间')),
);
return;
}
final record = SleepRecord(
bedTime: _bedTime!,
wakeTime: _wakeTime!,
deepSleep: Duration.zero,
lightSleep: Duration.zero,
remSleep: Duration.zero,
);
await saveSleepRecord(record);
if (mounted) {
Navigator.pop(context);
}
}
保存前要验证:时间不能为空,起床时间不能早于入睡时间。这些都是基本的数据验证。
睡眠质量评分
根据睡眠数据计算质量评分:
int calculateSleepScore(SleepRecord record) {
int score = 0;
// 总时长评分(最高40分)
final hours = record.totalSleep.inHours;
if (hours >= 7 && hours <= 9) {
score += 40;
} else if (hours >= 6 && hours < 7) {
score += 30;
} else if (hours >= 5 && hours < 6) {
score += 20;
} else {
score += 10;
}
// 深度睡眠评分(最高30分)
final deepPercent = record.deepSleepPercent;
if (deepPercent >= 0.2) {
score += 30;
} else if (deepPercent >= 0.15) {
score += 20;
} else {
score += 10;
}
// REM睡眠评分(最高30分)
final remPercent = record.remSleepPercent;
if (remPercent >= 0.2) {
score += 30;
} else if (remPercent >= 0.15) {
score += 20;
} else {
score += 10;
}
return score;
}
评分考虑三个因素:总时长、深度睡眠占比、REM睡眠占比。满分100分,7-9小时睡眠、深度睡眠占比20%以上、REM睡眠占比20%以上,可以得满分。
质量等级
String getSleepQualityLevel(int score) {
if (score >= 90) return '优秀';
if (score >= 75) return '良好';
if (score >= 60) return '一般';
return '较差';
}
根据分数给出质量等级,让用户更直观地了解睡眠质量。
睡眠建议
根据睡眠数据给出个性化建议:
String getSleepAdvice(SleepRecord record) {
final hours = record.totalSleep.inHours;
if (hours < 6) {
return '睡眠时间严重不足,建议早点休息,保证每天至少7小时睡眠。';
}
if (hours > 9) {
return '睡眠时间过长,可能影响白天精神状态,建议调整作息。';
}
if (record.deepSleepPercent < 0.15) {
return '深度睡眠不足,建议睡前避免使用电子设备,保持卧室安静。';
}
if (record.bedTime.hour > 23 || record.bedTime.hour < 6) {
return '入睡时间较晚,建议在晚上11点前入睡,有助于提高睡眠质量。';
}
return '睡眠质量不错,继续保持良好的作息习惯!';
}
根据不同的睡眠问题给出针对性建议。这些建议要实用,不要太空泛。
实际使用体验
我自己用了一段时间睡眠记录功能,确实帮助我改善了作息。以前总是熬夜到凌晨一两点,看到睡眠数据后,发现自己的睡眠质量真的很差。
通过查看本周趋势图,我发现自己工作日睡得很少,周末又睡得很多。这种不规律的作息对身体不好。后来我开始调整,尽量每天都在11点前睡觉,早上7点起床。坚持了一段时间,感觉精神状态好多了。
睡眠质量的详细数据也很有用。我发现自己深度睡眠占比很低,后来睡前不玩手机,保持卧室安静,深度睡眠时间明显增加了。
可以改进的地方
如果要做得更完善,可以考虑以下几点。
智能手环集成
接入智能手环或手表的数据,自动记录睡眠。这样用户不用手动输入,数据也更准确。
睡眠环境监测
记录睡眠时的环境因素,比如温度、湿度、噪音等。分析这些因素对睡眠质量的影响。
睡眠音乐
提供助眠音乐或白噪音,帮助用户更快入睡。可以设置定时关闭,避免影响睡眠。
睡眠报告
生成周报或月报,总结睡眠情况,给出改善建议。可以导出PDF,方便用户查看或分享给医生。
小结
睡眠记录功能看起来简单,但对健康管理很重要。通过记录和分析睡眠数据,用户可以更好地了解自己的睡眠状况,发现问题,改善作息。
在实现过程中,我特别注重数据的可视化。趋势图、进度条、评分等级,这些都是为了让用户更直观地了解睡眠情况。数字本身是冰冷的,但通过合适的展示方式,可以让数据变得有温度。
从我自己的使用体验来看,这个功能确实帮助我改善了睡眠质量。希望这篇文章能给你带来一些启发。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)