Flutter for OpenHarmony高级闹钟App实战:睡眠周期计算器实现
睡眠周期计算器基于90分钟睡眠周期理论,可帮助用户找到最佳起床时间。该功能允许用户选择入睡时间后,自动计算4-6个周期(6-9小时)的建议起床时间,并考虑15分钟入睡缓冲期。实现上采用Flutter框架,包含时间选择器、周期计算算法和响应式UI设计。计算结果以列表形式展示,用户可直接设置闹钟,提供科学实用的睡眠管理方案。
睡眠周期计算器是DeepWake的特色功能之一,能帮用户找到最佳起床时间。说实话,很多人都有这样的经历:睡了8小时还是很困,睡了6小时反而精神。这就是睡眠周期在起作用。
咱们这次要实现的睡眠周期计算器,不仅能根据入睡时间计算建议起床时间,还能直接创建闹钟。做这个功能的时候,我一直在想怎么让计算结果既科学又实用,最后决定基于90分钟的睡眠周期理论,并加入15分钟的入睡时间。
页面状态设计
睡眠周期计算器需要管理入睡时间和计算结果。
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import '../../controllers/alarm_controller.dart';
import '../../models/alarm.dart';
class SleepCyclePage extends StatefulWidget {
const SleepCyclePage({super.key});
State<SleepCyclePage> createState() => _SleepCyclePageState();
}
StatefulWidget的选择:睡眠周期页面需要管理入睡时间和计算结果,这些状态会随用户操作变化,所以用StatefulWidget。
导入依赖:需要AlarmController来创建闹钟,Alarm模型来构建闹钟对象。flutter_screenutil用于响应式布局,Get用于导航和状态管理。
页面定位:这是一个工具页面,不是主要功能页面,所以设计上要简洁实用,不需要太复杂的交互。
class _SleepCyclePageState extends State<SleepCyclePage> {
TimeOfDay _sleepTime = TimeOfDay.now();
List<TimeOfDay> _wakeUpTimes = [];
void initState() {
super.initState();
_calculateWakeUpTimes();
}
状态变量设计:_sleepTime记录用户选择的入睡时间,默认为当前时间。_wakeUpTimes存储计算出的建议起床时间列表。
初始化计算:在initState中调用_calculateWakeUpTimes,页面打开时就显示计算结果,不需要用户额外操作。这种主动计算的设计让用户体验更流畅。
TimeOfDay的选择:用TimeOfDay而不是DateTime,因为只需要时分,不需要年月日。TimeOfDay更轻量,也更符合业务需求。
睡眠周期计算逻辑
实现基于睡眠周期理论的计算算法。
void _calculateWakeUpTimes() {
_wakeUpTimes.clear();
final now = DateTime.now();
final sleepDateTime = DateTime(
now.year,
now.month,
now.day,
_sleepTime.hour,
_sleepTime.minute,
);
清空旧结果:每次计算前先清空_wakeUpTimes,避免累积旧数据。
时间转换:把TimeOfDay转换成DateTime,因为需要用DateTime的add方法来计算未来时间。用当前日期配合选择的时分构建完整的DateTime对象。
**为什么用当前日期?**因为用户通常是当天晚上入睡,第二天早上起床。如果入睡时间比当前时间早,说明是今晚入睡,否则可能是现在就要睡。
// 每个睡眠周期90分钟,建议4-6个周期
for (int cycles = 4; cycles <= 6; cycles++) {
final wakeTime = sleepDateTime.add(Duration(minutes: 90 * cycles + 15));
_wakeUpTimes.add(TimeOfDay(hour: wakeTime.hour, minute: wakeTime.minute));
}
}
睡眠周期理论:科学研究表明,一个完整的睡眠周期约90分钟,包括浅睡眠、深睡眠和快速眼动睡眠。在周期结束时醒来,人会感觉更清醒。
周期数量选择:建议4-6个周期,对应6-9小时睡眠。少于4个周期睡眠不足,多于6个周期大多数人也睡不了那么久。
入睡时间补偿:加15分钟入睡时间,因为大多数人不是躺下就睡着。这个15分钟是经验值,让计算结果更贴近实际。
时间计算:用Duration(minutes: 90 * cycles + 15)计算总时长,然后用add方法得到起床时间。最后转换回TimeOfDay存储。
页面布局结构
实现清晰的信息展示布局。
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('睡眠周期计算器')),
body: ListView(
padding: EdgeInsets.all(16.w),
children: [
Card(
child: Padding(
padding: EdgeInsets.all(16.w),
Scaffold结构:标准的页面结构,AppBar显示标题,body用ListView展示内容。
ListView的选择:用ListView而不是Column,因为内容可能超出屏幕高度,ListView能自动滚动。padding设置整体边距。
Card容器:用Card包裹入睡时间选择器,让这个区域在视觉上独立出来。Card自带阴影和圆角,Material Design风格。
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('入睡时间', style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.bold)),
SizedBox(height: 12.h),
ListTile(
leading: const Icon(Icons.bedtime),
title: Text(
'${_sleepTime.hour.toString().padLeft(2, '0')}:${_sleepTime.minute.toString().padLeft(2, '0')}',
标题文字:用粗体18.sp的文字标注"入睡时间",让用户知道这个区域的作用。
ListTile布局:用ListTile展示时间,leading放图标,title放时间文字,trailing放编辑图标。这是Material Design的标准模式。
时间格式化:用padLeft(2, ‘0’)确保时分都是两位数,比如"09:05"而不是"9:5"。这种格式更规范,也更易读。
style: TextStyle(fontSize: 24.sp, fontWeight: FontWeight.bold),
),
trailing: const Icon(Icons.edit),
onTap: () async {
final time = await showTimePicker(context: context, initialTime: _sleepTime);
if (time != null) {
setState(() {
_sleepTime = time;
_calculateWakeUpTimes();
});
}
},
大字号显示:时间用24.sp的粗体,让用户一眼就能看到当前选择的入睡时间。
时间选择器:点击ListTile弹出系统时间选择器,用户可以方便地选择时间。showTimePicker是Flutter提供的Material Design时间选择器。
状态更新:选择新时间后,用setState更新_sleepTime并重新计算起床时间。这样用户每次修改入睡时间,建议起床时间都会自动更新。
空值检查:用if (time != null)检查用户是否取消了选择,只有确认选择才更新状态。
建议起床时间列表
展示计算出的多个建议起床时间。
},
),
],
),
),
),
],
),
),
SizedBox(height: 16.h),
Text('建议起床时间', style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.bold)),
SizedBox(height: 12.h),
分段标题:用"建议起床时间"标题分隔入睡时间和起床时间两个区域,让页面结构更清晰。
间距设置:Card和标题之间16.h间距,标题和列表之间12.h间距,形成合理的视觉节奏。
一致的样式:标题样式与入睡时间的标题保持一致,都是18.sp粗体,让页面风格统一。
..._wakeUpTimes.asMap().entries.map((entry) {
final index = entry.key;
final time = entry.value;
final cycles = index + 4;
final hours = (cycles * 1.5).toStringAsFixed(1);
return Card(
child: ListTile(
leading: Icon(Icons.alarm, size: 32.sp),
title: Text(
'${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}',
展开运算符:用…展开map结果,把多个Card直接插入children列表。这种写法简洁,避免了嵌套的Column。
索引和值:用asMap().entries同时获取索引和值,索引用于计算周期数(index + 4),值就是起床时间。
周期数计算:因为循环是从4到6,所以index + 4就是实际的周期数。hours用cycles * 1.5计算总小时数,因为每个周期90分钟即1.5小时。
图标大小:闹钟图标用32.sp,比普通图标大一些,让每个建议时间更醒目。
style: TextStyle(fontSize: 20.sp, fontWeight: FontWeight.bold),
),
subtitle: Text('$cycles 个周期 ($hours 小时)'),
trailing: IconButton(
icon: const Icon(Icons.add_alarm),
onPressed: () => _createAlarm(time),
),
),
);
}),
时间显示:起床时间用20.sp粗体,比入睡时间稍小,但仍然很醒目。
副标题信息:显示周期数和总小时数,让用户了解这个起床时间对应的睡眠时长。比如"5 个周期 (7.5 小时)"。
快捷创建闹钟:trailing放一个add_alarm图标按钮,点击直接创建闹钟。这种一键创建的设计让用户操作更便捷,不需要手动去闹钟编辑页面设置时间。
回调简写:onPressed用箭头函数简写,直接调用_createAlarm方法并传入时间。
睡眠知识提示
添加睡眠周期相关的科普信息。
SizedBox(height: 16.h),
Card(
color: Colors.blue.withOpacity(0.1),
child: Padding(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(Icons.info_outline, color: Colors.blue),
提示卡片:用浅蓝色背景的Card展示睡眠知识,color用withOpacity(0.1)设置10%透明度,让背景色很淡,不会太突兀。
图标和标题:用Row布局,左边放info图标,右边放标题文字。图标用蓝色,与卡片背景色呼应。
视觉区分:这个卡片的样式与上面的时间卡片不同,让用户知道这是辅助信息,不是主要功能。
SizedBox(width: 8.w),
Text('睡眠周期小贴士', style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold)),
],
),
SizedBox(height: 8.h),
const Text('• 每个睡眠周期约90分钟'),
const Text('• 在周期结束时醒来会更清醒'),
const Text('• 建议睡4-6个完整周期'),
const Text('• 已包含15分钟入睡时间'),
],
),
),
),
],
知识点列表:用项目符号列出4个关键知识点,简洁明了。这些信息帮助用户理解睡眠周期计算器的原理。
const优化:提示文字用const,因为这些文字不会变化,const能提升性能,减少重建开销。
信息价值:这些提示不是装饰,而是真正有用的信息。告诉用户为什么是90分钟,为什么建议4-6个周期,为什么加了15分钟。让用户明白计算逻辑,增加信任感。
创建闹钟功能
实现从计算结果直接创建闹钟。
void _createAlarm(TimeOfDay time) {
final controller = Get.find<AlarmController>();
final alarm = Alarm.create(
label: '睡眠周期闹钟',
time: time,
);
controller.createAlarm(alarm);
Get.back();
Get.snackbar('成功', '闹钟已创建');
}
}
获取控制器:用Get.find获取已注册的AlarmController实例,这是GetX的依赖注入机制。
创建闹钟对象:用Alarm.create工厂方法创建闹钟,传入标签和时间。标签固定为"睡眠周期闹钟",让用户知道这个闹钟的来源。
保存闹钟:调用controller.createAlarm保存闹钟到数据库和状态中。
用户反馈:创建成功后返回上一页,并显示成功提示。Get.back()关闭当前页面,Get.snackbar显示轻量级提示。
流程简化:整个创建流程只需要一次点击,不需要跳转到编辑页面,不需要手动设置时间。这种简化的流程让睡眠周期计算器真正实用。
时间格式化的细节
时间显示的格式化处理很重要。
padLeft的作用:padLeft(2, ‘0’)把数字转换成至少2位的字符串,不足2位在左边补0。比如小时9变成"09",分钟5变成"05"。
**为什么要补零?**因为时间格式通常是"HH:MM",两位数字更规范,也更易读。"09:05"比"9:5"看起来更专业。
字符串插值:'sleepTime.hour.toString().padLeft(2,′0′):{_sleepTime.hour.toString().padLeft(2, '0')}:sleepTime.hour.toString().padLeft(2,′0′):{_sleepTime.minute.toString().padLeft(2, ‘0’)}'这种写法虽然长,但很清晰。先toString转字符串,再padLeft补零,最后用冒号连接。
复用性考虑:如果多处需要格式化时间,可以提取成方法。但这里只有两处,直接写更简单。
睡眠周期理论的应用
睡眠周期计算器基于科学的睡眠理论。
90分钟周期:这是平均值,实际每个人的睡眠周期可能在80-110分钟之间。但90分钟是最常见的,适合大多数人。
周期阶段:一个完整周期包括浅睡眠、深睡眠、快速眼动睡眠(REM)。在REM阶段结束时醒来,人会感觉更清醒,不会昏昏沉沉。
**为什么不是整数小时?**很多人以为睡8小时最好,但8小时是5.33个周期,可能在深睡眠阶段醒来,反而很困。7.5小时(5个周期)或9小时(6个周期)可能更合适。
个体差异:这个计算器提供的是建议,不是绝对标准。用户可以尝试不同的起床时间,找到最适合自己的睡眠时长。
用户体验优化
睡眠周期计算器的设计注重实用性。
主动计算:页面打开就显示结果,不需要用户点击"计算"按钮。这种主动的设计减少了操作步骤。
实时更新:修改入睡时间后立即重新计算,用户能实时看到变化。这种即时反馈让交互更流畅。
一键创建:每个建议时间旁边都有创建闹钟按钮,点击就能创建。不需要记住时间,不需要手动输入,大大降低了使用门槛。
信息透明:显示周期数和总小时数,让用户了解每个建议时间的含义。提示卡片解释计算原理,增加用户信任。
视觉清晰:用Card分隔不同区域,用图标增强识别性,用大字号突出重要信息。整个页面一目了然,不需要思考就能理解。
扩展功能思考
睡眠周期计算器还可以增加更多功能。
反向计算:除了根据入睡时间计算起床时间,还可以根据起床时间计算建议入睡时间。比如用户明天7点要起床,计算器告诉他应该几点睡。
个性化周期:让用户设置自己的睡眠周期时长,而不是固定90分钟。有些人可能是85分钟,有些人可能是95分钟。
历史记录:记录用户使用计算器的历史,分析哪个起床时间效果最好。结合闹钟历史记录,看用户在哪个时间醒来后精神状态最好。
智能推荐:根据用户的作息习惯,推荐最适合的睡眠时长。比如用户通常睡7.5小时,就优先推荐5个周期的起床时间。
午睡模式:增加午睡计算功能,推荐20分钟(浅睡眠)或90分钟(完整周期)的午睡时长。
总结
睡眠周期计算器虽然功能简单,但很实用。通过科学的睡眠周期理论,帮助用户找到最佳起床时间,提升睡眠质量。
说实话,做这个功能让我对睡眠有了更深的理解。睡眠不是越多越好,而是要睡对时间。在睡眠周期结束时醒来,即使睡得少也能精神饱满。这个计算器把复杂的理论转化成简单的工具,让每个人都能受益。
如果你也在做健康类应用,建议重点关注科学性和实用性的平衡。功能要基于可靠的理论,但不能太复杂,要让普通用户也能轻松使用。界面要清晰,操作要简单,最好能一键完成任务。
欢迎加入OpenHarmony跨平台开发社区交流:https://openharmonycrossplatform.csdn.net
更多推荐

所有评论(0)