睡眠周期计算器是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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐