Flutter 框架跨平台鸿蒙开发 - 反向闹钟 - 不是叫你起床,而是提醒你该睡觉了
运行效果图反向闹钟是一款创新的睡眠健康管理应用,与传统闹钟不同,它的核心功能是提醒用户该睡觉了,而非叫醒用户。通过科学的作息管理和温和的睡前提醒,帮助用户建立健康的睡眠习惯,改善睡眠质量。现代生活节奏快,很多人习惯熬夜,传统闹钟只能叫醒你,却无法帮助你早睡。反向闹钟填补了这一空白,通过设定目标睡觉时间,在睡前适时提醒用户放下手机、准备入睡,从根本上改善睡眠质量。反向闹钟应用通过创新的"提醒睡觉"理
反向闹钟应用
欢迎加入开源鸿蒙跨平台社区:
https://openharmonycrossplatform.csdn.net
一、项目概述
运行效果图




1.1 应用简介
反向闹钟是一款创新的睡眠健康管理应用,与传统闹钟不同,它的核心功能是提醒用户该睡觉了,而非叫醒用户。通过科学的作息管理和温和的睡前提醒,帮助用户建立健康的睡眠习惯,改善睡眠质量。
现代生活节奏快,很多人习惯熬夜,传统闹钟只能叫醒你,却无法帮助你早睡。反向闹钟填补了这一空白,通过设定目标睡觉时间,在睡前适时提醒用户放下手机、准备入睡,从根本上改善睡眠质量。
1.2 核心功能
| 功能模块 | 功能描述 | 实现方式 |
|---|---|---|
| 睡前倒计时 | 实时显示距离睡觉时间还有多久 | 动态计算当前时间与目标睡觉时间的差值 |
| 作息设置 | 设置睡觉和起床时间 | TimePicker 时间选择器 |
| 提醒模式 | 四种提醒强度可选 | ChoiceChip 模式选择 |
| 睡眠记录 | 记录实际入睡和起床时间 | 日期时间选择器组合 |
| 睡眠统计 | 分析睡眠质量和准时率 | 数据可视化图表 |
| 睡眠小贴士 | 每日推送睡眠健康建议 | 循环数组展示 |
1.3 提醒模式
| 序号 | 模式名称 | 图标 | 颜色 | 说明 |
|---|---|---|---|---|
| 1 | 温和提醒 | notifications_none | 绿色 #10B981 | 轻柔提示,不打扰 |
| 2 | 普通提醒 | notifications | 紫色 #6366F1 | 标准提醒通知 |
| 3 | 严格模式 | notifications_active | 橙色 #F59E0B | 频繁提醒,确保注意 |
| 4 | 强制模式 | lock_clock | 红色 #EF4444 | 强制锁屏,必须睡觉 |
1.4 技术栈
| 技术领域 | 技术选型 | 版本要求 |
|---|---|---|
| 开发框架 | Flutter | >= 3.0.0 |
| 编程语言 | Dart | >= 2.17.0 |
| 设计规范 | Material Design 3 | - |
| 状态管理 | setState | - |
| 时间处理 | intl | - |
| 目标平台 | 鸿蒙OS / Android / iOS | API 21+ |
1.5 项目结构
lib/
└── main_reverse_alarm.dart
├── ReverseAlarmApp # 应用入口
├── BedtimeReminderMode # 提醒模式枚举
├── SleepQuality # 睡眠质量枚举
├── SleepSchedule # 作息计划模型
├── SleepRecord # 睡眠记录模型
├── ReverseAlarmHomePage # 主页面(底部导航)
├── _buildHomePage # 首页(倒计时)
├── _buildSchedulePage # 作息设置页面
├── _buildStatisticsPage # 统计页面
└── _buildSettingsPage # 设置页面
二、系统架构
2.1 整体架构图
2.2 类图设计
2.3 页面导航流程
2.4 数据流向图
三、核心模块设计
3.1 数据模型设计
3.1.1 提醒模式枚举 (BedtimeReminderMode)
enum BedtimeReminderMode {
gentle('温和提醒', Icons.notifications_none, Color(0xFF10B981)),
normal('普通提醒', Icons.notifications, Color(0xFF6366F1)),
strict('严格模式', Icons.notifications_active, Color(0xFFF59E0B)),
force('强制模式', Icons.lock_clock, Color(0xFFEF4444));
final String label;
final IconData icon;
final Color color;
const BedtimeReminderMode(this.label, this.icon, this.color);
}
3.1.2 睡眠质量枚举 (SleepQuality)
enum SleepQuality {
excellent('非常好', 5, Color(0xFF10B981)),
good('好', 4, Color(0xFF6366F1)),
average('一般', 3, Color(0xFFF59E0B)),
poor('较差', 2, Color(0xFFF97316)),
terrible('很差', 1, Color(0xFFEF4444));
final String label;
final int value;
final Color color;
const SleepQuality(this.label, this.value, this.color);
}
3.1.3 作息计划模型 (SleepSchedule)
class SleepSchedule {
final TimeOfDay bedtime;
final TimeOfDay wakeTime;
final List<int> repeatDays;
final bool isEnabled;
const SleepSchedule({
required this.bedtime,
required this.wakeTime,
this.repeatDays = const [1, 2, 3, 4, 5, 6, 7],
this.isEnabled = true,
});
Duration get sleepDuration {
var bedtimeMinutes = bedtime.hour * 60 + bedtime.minute;
var wakeTimeMinutes = wakeTime.hour * 60 + wakeTime.minute;
if (wakeTimeMinutes < bedtimeMinutes) {
wakeTimeMinutes += 24 * 60;
}
return Duration(minutes: wakeTimeMinutes - bedtimeMinutes);
}
String get sleepDurationText {
final duration = sleepDuration;
final hours = duration.inHours;
final minutes = duration.inMinutes % 60;
if (minutes == 0) {
return '$hours小时';
}
return '$hours小时$minutes分钟';
}
}
3.1.4 睡眠记录模型 (SleepRecord)
class SleepRecord {
final String id;
final DateTime date;
final DateTime? actualBedtime;
final DateTime? actualWakeTime;
final SleepQuality? quality;
final String? note;
SleepRecord({
required this.id,
required this.date,
this.actualBedtime,
this.actualWakeTime,
this.quality,
this.note,
});
Duration? get actualSleepDuration {
if (actualBedtime == null || actualWakeTime == null) return null;
return actualWakeTime!.difference(actualBedtime!);
}
}
3.2 页面结构图
3.2.1 首页结构
3.2.2 作息设置页结构
3.2.3 统计页结构
3.3 倒计时计算逻辑
四、UI设计规范
4.1 配色方案
应用采用紫色为主色调,传递宁静、舒适的睡眠氛围:
| 颜色类型 | 色值 | 用途 |
|---|---|---|
| 主色 | #6366F1 (Indigo) | 导航、强调元素、睡觉时间 |
| 辅色 | #8B5CF6 (Purple) | 渐变背景、装饰元素 |
| 起床色 | #F59E0B (Amber) | 起床时间、警告提醒 |
| 成功色 | #10B981 (Emerald) | 成功状态、睡眠时长 |
| 温和模式 | #10B981 (Green) | 温和提醒 |
| 普通模式 | #6366F1 (Indigo) | 普通提醒 |
| 严格模式 | #F59E0B (Orange) | 严格提醒 |
| 强制模式 | #EF4444 (Red) | 强制提醒 |
| 贴士背景 | #FEF3C7 (Warm Yellow) | 睡眠小贴士卡片 |
4.2 字体规范
| 元素 | 字号 | 字重 | 颜色 |
|---|---|---|---|
| 倒计时数字 | 72px | Bold | #000000 |
| 时间单位 | 18px | Regular | #000000 |
| 状态文字 | 18px | Medium | 根据状态变化 |
| 卡片标题 | 18px | Bold | #000000 |
| 时间显示 | 28px | Bold | 主题色 |
| 按钮文字 | 13px | Regular | #000000 |
| 小贴士标题 | 14px | Bold | #92400E |
| 小贴士内容 | 13px | Regular | #A16207 |
4.3 组件规范
4.3.1 倒计时卡片
┌─────────────────────────────────────────┐
│ │
│ ⏰ 睡觉时间快到了 │
│ │
│ 14 32 │
│ 小时 分钟 │
│ │
│ ┌─────────────────────────────┐ │
│ │ 🔔 普通提醒 · 提前30分钟 │ │
│ └─────────────────────────────┘ │
│ │
└─────────────────────────────────────────┘
4.3.2 作息设置卡片
┌─────────────────────────────────────────┐
│ 今日作息 [开关]│
├─────────────────────────────────────────┤
│ │
│ 🌙 睡觉时间 │ ☀️ 起床时间 │
│ 23:00 │ 07:00 │
│ │
├─────────────────────────────────────────┤
│ │
│ 🌙 目标睡眠时长: 8小时0分钟 │
│ │
└─────────────────────────────────────────┘
4.3.3 快捷操作按钮
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 🛏️ │ │ 📅 │ │ 📊 │
│ 记录睡眠 │ │ 调整作息 │ │ 睡眠分析 │
└──────────┘ └──────────┘ └──────────┘
4.3.4 睡眠小贴士卡片
┌─────────────────────────────────────────┐
│ 💡 今日睡眠小贴士 │
│ 睡前1小时避免使用电子设备 │
└─────────────────────────────────────────┘
五、核心功能实现
5.1 睡前倒计时实现
Widget _buildHomePage() {
final now = DateTime.now();
final currentTime = TimeOfDay.fromDateTime(now);
final bedtimeMinutes = _schedule.bedtime.hour * 60 + _schedule.bedtime.minute;
final currentMinutes = currentTime.hour * 60 + currentTime.minute;
// 计算剩余分钟数
var minutesUntilBedtime = bedtimeMinutes - currentMinutes;
if (minutesUntilBedtime < 0) {
minutesUntilBedtime += 24 * 60; // 跨天处理
}
final hoursUntil = minutesUntilBedtime ~/ 60;
final minsUntil = minutesUntilBedtime % 60;
// 判断是否接近睡觉时间
final isNearBedtime = minutesUntilBedtime <= _reminderMinutesBefore && minutesUntilBedtime > 0;
final isPastBedtime = currentMinutes > bedtimeMinutes && currentMinutes - bedtimeMinutes < 120;
return Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
expandedHeight: 200,
pinned: true,
flexibleSpace: FlexibleSpaceBar(
title: const Text('反向闹钟'),
background: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Color(0xFF6366F1), Color(0xFF8B5CF6)],
),
),
),
),
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
_buildCountdownCard(hoursUntil, minsUntil, isNearBedtime, isPastBedtime),
const SizedBox(height: 16),
_buildSleepScheduleCard(),
const SizedBox(height: 16),
_buildQuickActions(),
const SizedBox(height: 16),
_buildSleepTipCard(),
],
),
),
),
],
),
);
}
5.2 倒计时卡片实现
Widget _buildCountdownCard(int hoursUntil, int minsUntil, bool isNearBedtime, bool isPastBedtime) {
String statusText;
IconData statusIcon;
Color statusColor;
if (isPastBedtime) {
statusText = '已过睡觉时间';
statusIcon = Icons.warning;
statusColor = Colors.orange;
} else if (isNearBedtime) {
statusText = '睡觉时间快到了';
statusIcon = Icons.access_alarm;
statusColor = _reminderMode.color;
} else {
statusText = '距离睡觉时间';
statusIcon = Icons.schedule;
statusColor = Theme.of(context).colorScheme.primary;
}
return Card(
elevation: 4,
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: isNearBedtime
? [statusColor.withValues(alpha: 0.2), statusColor.withValues(alpha: 0.1)]
: [Theme.of(context).colorScheme.surface, Theme.of(context).colorScheme.surface],
),
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(statusIcon, color: statusColor, size: 28),
const SizedBox(width: 8),
Text(
statusText,
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500, color: statusColor),
),
],
),
const SizedBox(height: 16),
if (!isPastBedtime) ...[
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text('$hoursUntil', style: const TextStyle(fontSize: 72, fontWeight: FontWeight.bold, height: 1)),
const Padding(padding: EdgeInsets.only(bottom: 12), child: Text('小时', style: TextStyle(fontSize: 18))),
const SizedBox(width: 8),
Text('$minsUntil', style: const TextStyle(fontSize: 72, fontWeight: FontWeight.bold, height: 1)),
const Padding(padding: EdgeInsets.only(bottom: 12), child: Text('分钟', style: TextStyle(fontSize: 18))),
],
),
],
if (_isReminderEnabled && !isPastBedtime) ...[
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: _reminderMode.color.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(20),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(_reminderMode.icon, size: 16, color: _reminderMode.color),
const SizedBox(width: 6),
Text('${_reminderMode.label} · 提前$_reminderMinutesBefore分钟', style: TextStyle(fontSize: 13, color: _reminderMode.color)),
],
),
),
],
],
),
),
);
}
5.3 睡眠质量分布图表实现
Widget _buildSleepQualityChart(Map<SleepQuality, int> distribution) {
final total = distribution.values.fold(0, (a, b) => a + b);
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('睡眠质量分布', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
if (total == 0)
const Center(child: Padding(padding: EdgeInsets.all(32), child: Text('暂无数据', style: TextStyle(color: Colors.grey))))
else
Column(
children: SleepQuality.values.reversed.map((quality) {
final count = distribution[quality] ?? 0;
final percentage = total > 0 ? count / total : 0.0;
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Row(
children: [
Container(
width: 12, height: 12,
decoration: BoxDecoration(color: quality.color, borderRadius: BorderRadius.circular(6)),
),
const SizedBox(width: 8),
SizedBox(width: 60, child: Text(quality.label, style: const TextStyle(fontSize: 13))),
Expanded(
child: ClipRRect(
borderRadius: BorderRadius.circular(4),
child: LinearProgressIndicator(
value: percentage,
backgroundColor: Colors.grey[200],
valueColor: AlwaysStoppedAnimation<Color>(quality.color),
minHeight: 8,
),
),
),
const SizedBox(width: 8),
SizedBox(width: 40, child: Text('$count次', style: TextStyle(fontSize: 12, color: Colors.grey[600]), textAlign: TextAlign.right)),
],
),
);
}).toList(),
),
],
),
),
);
}
5.4 睡眠记录弹窗实现
void _showRecordSleepDialog() {
DateTime? selectedBedtime;
DateTime? selectedWakeTime;
SleepQuality? selectedQuality;
final noteController = TextEditingController();
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => StatefulBuilder(
builder: (context, setModalState) => DraggableScrollableSheet(
initialChildSize: 0.8,
maxChildSize: 0.95,
minChildSize: 0.5,
expand: false,
builder: (context, scrollController) => Container(
padding: const EdgeInsets.all(20),
child: ListView(
controller: scrollController,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('记录睡眠', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
IconButton(icon: const Icon(Icons.close), onPressed: () => Navigator.pop(context)),
],
),
const SizedBox(height: 20),
ListTile(
leading: const Icon(Icons.bedtime, color: Color(0xFF6366F1)),
title: const Text('实际睡觉时间'),
subtitle: Text(selectedBedtime != null ? DateFormat('MM月dd日 HH:mm').format(selectedBedtime!) : '点击选择'),
trailing: const Icon(Icons.chevron_right),
onTap: () async {
final now = DateTime.now();
final currentContext = context;
final date = await showDatePicker(
context: currentContext,
initialDate: now,
firstDate: now.subtract(const Duration(days: 7)),
lastDate: now,
);
if (date != null && currentContext.mounted) {
final time = await showTimePicker(context: currentContext, initialTime: TimeOfDay.fromDateTime(now));
if (time != null) {
setModalState(() {
selectedBedtime = DateTime(date.year, date.month, date.day, time.hour, time.minute);
});
}
}
},
),
// ... 起床时间选择类似
const SizedBox(height: 16),
const Text('睡眠质量'),
const SizedBox(height: 8),
Wrap(
spacing: 8,
children: SleepQuality.values.map((quality) {
final isSelected = selectedQuality == quality;
return ChoiceChip(
avatar: Icon(Icons.star, size: 16, color: isSelected ? Colors.white : quality.color),
label: Text(quality.label),
selected: isSelected,
selectedColor: quality.color,
labelStyle: TextStyle(color: isSelected ? Colors.white : null),
onSelected: (selected) {
if (selected) setModalState(() => selectedQuality = quality);
},
);
}).toList(),
),
const SizedBox(height: 16),
TextField(
controller: noteController,
decoration: const InputDecoration(labelText: '备注(可选)', border: OutlineInputBorder(), prefixIcon: Icon(Icons.note)),
maxLines: 2,
),
const SizedBox(height: 24),
FilledButton(
onPressed: () {
if (selectedBedtime != null && selectedWakeTime != null) {
final record = SleepRecord(
id: DateTime.now().millisecondsSinceEpoch.toString(),
date: selectedBedtime!,
actualBedtime: selectedBedtime,
actualWakeTime: selectedWakeTime,
quality: selectedQuality,
note: noteController.text.isEmpty ? null : noteController.text,
);
setState(() => _sleepRecords.insert(0, record));
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('睡眠记录已保存')));
}
},
child: const Text('保存记录'),
),
],
),
),
),
),
);
}
六、数据统计算法
6.1 平均睡眠时长计算
Duration _calculateAverageSleepDuration() {
final recordsWithDuration = _sleepRecords.where((r) => r.actualSleepDuration != null).toList();
if (recordsWithDuration.isEmpty) return const Duration(hours: 7);
final totalMinutes = recordsWithDuration.fold<int>(0, (sum, r) => sum + r.actualSleepDuration!.inMinutes);
return Duration(minutes: totalMinutes ~/ recordsWithDuration.length);
}
6.2 准时率计算
double _calculateOnTimeRate() {
if (_sleepRecords.isEmpty) return 0.0;
final bedtimeMinutes = _schedule.bedtime.hour * 60 + _schedule.bedtime.minute;
final onTimeCount = _sleepRecords.where((r) {
if (r.actualBedtime == null) return false;
final actualMinutes = r.actualBedtime!.hour * 60 + r.actualBedtime!.minute;
return actualMinutes <= bedtimeMinutes + 30; // 允许30分钟误差
}).length;
return onTimeCount / _sleepRecords.length;
}
6.3 睡眠质量分布计算
Map<SleepQuality, int> _calculateSleepQualityDistribution() {
final distribution = <SleepQuality, int>{};
for (final record in _sleepRecords) {
if (record.quality != null) {
distribution[record.quality!] = (distribution[record.quality!] ?? 0) + 1;
}
}
return distribution;
}
七、状态管理流程
7.1 提醒状态流转
7.2 记录睡眠流程
7.3 作息设置流程
八、扩展功能规划
8.1 后续版本规划
8.2 功能扩展建议
8.2.1 白噪音助眠
帮助用户更快入睡:
- 集成白噪音播放器
- 支持雨声、海浪、森林等自然音效
- 定时关闭功能
8.2.2 智能睡眠分析
基于传感器数据分析:
- 监测睡眠深浅周期
- 记录翻身次数
- 生成睡眠报告
8.2.3 睡眠日记
记录睡前状态:
- 语音记录心情
- 记录咖啡因摄入
- 记录运动情况
九、注意事项
9.1 开发注意事项
-
时间计算:注意跨天情况的处理,睡觉时间可能在第二天
-
异步操作:使用
context.mounted检查避免 async gaps 问题 -
状态更新:修改作息后需要同步更新首页倒计时显示
-
数据验证:记录睡眠时验证起床时间必须晚于睡觉时间
9.2 常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 倒计时显示错误 | 跨天计算未处理 | 检查是否加了24小时 |
| 弹窗报错 | async gaps | 使用 mounted 检查 |
| 统计不更新 | 状态未刷新 | 调用 setState |
| 时间选择无效 | 上下文丢失 | 保存 context 引用 |
9.3 使用提示
🌙 睡眠健康小贴士 🌙
规律作息:每天同一时间睡觉和起床。
睡前放松:避免剧烈运动和刺激性内容。
环境优化:保持卧室黑暗、安静、凉爽。
远离屏幕:睡前一小时不看手机电脑。
适度饮食:避免睡前大量进食和饮水。
十、运行说明
10.1 环境要求
| 环境 | 版本要求 |
|---|---|
| Flutter SDK | >= 3.0.0 |
| Dart SDK | >= 2.17.0 |
| 鸿蒙OS | API 21+ |
10.2 运行命令
# 查看可用设备
flutter devices
# 运行到Web服务器
flutter run -d web-server -t lib/main_reverse_alarm.dart --web-port 8098
# 运行到鸿蒙设备
flutter run -d 127.0.0.1:5555 lib/main_reverse_alarm.dart
# 运行到Windows
flutter run -d windows -t lib/main_reverse_alarm.dart
# 代码分析
flutter analyze lib/main_reverse_alarm.dart
十一、总结
反向闹钟应用通过创新的"提醒睡觉"理念,帮助用户建立健康的作息习惯。应用采用 Flutter + Material Design 3 技术栈,具有美观的界面和流畅的交互体验。核心功能包括睡前倒计时、作息设置、睡眠记录和统计分析,通过数据可视化帮助用户了解自己的睡眠状况。
技术亮点
- 创新的产品理念:反向思维,关注睡前而非起床
- 精确的时间计算:支持跨天计算,准确显示剩余时间
- 丰富的数据可视化:进度条、统计卡片、分布图表
- 灵活的提醒模式:四种模式适应不同用户需求
- 贴心的用户体验:每日小贴士、智能状态提示
核心代码文件
- [main_reverse_alarm.dart](file:///f:/Flutter/flutter_harmonyos/lib/main_reverse_alarm.dart) - 完整应用代码
规律作息,健康生活
更多推荐




所有评论(0)