Flutter 框架跨平台鸿蒙开发 - 手机闹钟管理器:打造专业级闹钟应用
✅ 闹钟管理(增删改查)✅ 24小时制时间选择✅ 多种重复模式(每天/工作日/周末/自定义)✅ 8种铃声选择✅ 振动开关设置✅ 贪睡时长调节(5-30分钟)✅ 开关控制启用/禁用✅ 自动时间排序✅ 状态统计显示✅ 数据本地持久化。
Flutter手机闹钟管理器:打造专业级闹钟应用
项目简介
手机闹钟管理器是一款功能完善的Flutter应用,提供专业的闹钟管理功能。支持自定义重复模式、多种铃声选择、振动设置、贪睡功能等,让用户能够精确管理每一个闹钟,确保不错过任何重要时刻。
运行效果图



核心功能
- 闹钟管理:添加、编辑、删除、启用/禁用闹钟
- 时间选择:24小时制时间选择器
- 重复设置:每天、工作日、周末、自定义重复
- 铃声选择:8种内置铃声可选
- 振动设置:支持振动提醒开关
- 贪睡功能:5-30分钟可调节贪睡时长
- 状态统计:显示已启用/总数量
- 自动排序:按时间自动排序显示
- 数据持久化:本地保存所有闹钟数据
技术特点
- Material Design 3设计风格
- TimeOfDay时间选择器
- 圆形星期选择按钮
- FilterChip快速重复设置
- Switch开关控制
- Slider滑块调节
- 大字号时间显示
- SharedPreferences数据持久化
核心代码实现
1. 闹钟数据模型
class Alarm {
String id;
TimeOfDay time; // 闹钟时间
String label; // 闹钟标签
bool enabled; // 是否启用
List<int> repeatDays; // 重复日期(1-7代表周一到周日)
String ringtone; // 铃声
bool vibrate; // 是否振动
int snoozeMinutes; // 贪睡时长
// 是否重复
bool get isRepeating => repeatDays.isNotEmpty;
// 重复文本
String get repeatText {
if (repeatDays.isEmpty) return '仅一次';
if (repeatDays.length == 7) return '每天';
if (repeatDays.length == 5 &&
!repeatDays.contains(6) &&
!repeatDays.contains(7)) {
return '工作日';
}
if (repeatDays.length == 2 &&
repeatDays.contains(6) &&
repeatDays.contains(7)) {
return '周末';
}
const weekDays = ['一', '二', '三', '四', '五', '六', '日'];
return repeatDays.map((day) => '周${weekDays[day - 1]}').join(' ');
}
}
模型字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | String | 唯一标识符 |
| time | TimeOfDay | 闹钟时间 |
| label | String | 闹钟标签(如:起床、上班) |
| enabled | bool | 是否启用 |
| repeatDays | List | 重复日期(1-7) |
| ringtone | String | 铃声名称 |
| vibrate | bool | 是否振动 |
| snoozeMinutes | int | 贪睡时长(分钟) |
计算属性:
isRepeating:判断是否设置了重复repeatText:生成重复文本描述
2. TimeOfDay时间选择器
void _selectTime() async {
final picked = await showTimePicker(
context: context,
initialTime: _time,
builder: (context, child) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(
alwaysUse24HourFormat: true // 强制24小时制
),
child: child!,
);
},
);
if (picked != null) {
setState(() {
_time = picked;
});
}
}
// 时间显示
final timeString = '${_time.hour.toString().padLeft(2, '0')}:'
'${_time.minute.toString().padLeft(2, '0')}';
TimeOfDay要点:
hour:小时(0-23)minute:分钟(0-59)padLeft(2, '0'):补零显示(如:09:05)alwaysUse24HourFormat:使用24小时制
3. 重复设置功能
// 快速重复设置
void _setQuickRepeat(String type) {
setState(() {
switch (type) {
case 'everyday':
_repeatDays = [1, 2, 3, 4, 5, 6, 7];
break;
case 'weekday':
_repeatDays = [1, 2, 3, 4, 5];
break;
case 'weekend':
_repeatDays = [6, 7];
break;
case 'none':
_repeatDays = [];
break;
}
});
}
// 自定义重复
void _toggleRepeatDay(int day) {
setState(() {
if (_repeatDays.contains(day)) {
_repeatDays.remove(day);
} else {
_repeatDays.add(day);
}
_repeatDays.sort();
});
}
// UI - FilterChip快速选择
Wrap(
spacing: 8,
children: [
FilterChip(
label: const Text('每天'),
selected: _repeatDays.length == 7,
onSelected: (_) => _setQuickRepeat('everyday'),
),
FilterChip(
label: const Text('工作日'),
selected: _repeatDays.length == 5 &&
!_repeatDays.contains(6) &&
!_repeatDays.contains(7),
onSelected: (_) => _setQuickRepeat('weekday'),
),
// ... 其他选项
],
)
4. 圆形星期选择按钮
Widget _buildDayButton(String label, int day) {
final isSelected = _repeatDays.contains(day);
return InkWell(
onTap: () => _toggleRepeatDay(day),
child: Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: isSelected ? Colors.blue : Colors.grey[200],
shape: BoxShape.circle, // 圆形
),
child: Center(
child: Text(
label,
style: TextStyle(
color: isSelected ? Colors.white : Colors.black87,
fontWeight: FontWeight.bold,
),
),
),
),
);
}
// 使用
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildDayButton('一', 1),
_buildDayButton('二', 2),
_buildDayButton('三', 3),
_buildDayButton('四', 4),
_buildDayButton('五', 5),
_buildDayButton('六', 6),
_buildDayButton('日', 7),
],
)
BoxDecoration圆形要点:
shape: BoxShape.circle:设置为圆形width和height必须相等- 配合
InkWell实现点击效果
5. 开关控制闹钟
void _toggleAlarm(Alarm alarm) {
setState(() {
alarm.enabled = !alarm.enabled;
});
_saveAlarms();
}
// UI - Switch开关
Switch(
value: alarm.enabled,
onChanged: (_) => _toggleAlarm(alarm),
)
Switch组件:
value:当前状态onChanged:状态改变回调- 自动处理动画效果
6. 贪睡时长调节
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
const Text('贪睡时长(分钟)'),
Row(
children: [
IconButton(
icon: const Icon(Icons.remove_circle_outline),
onPressed: () {
setState(() {
_snoozeMinutes = (_snoozeMinutes - 5).clamp(5, 30);
});
},
),
Expanded(
child: Slider(
value: _snoozeMinutes.toDouble(),
min: 5,
max: 30,
divisions: 5,
label: '$_snoozeMinutes分钟',
onChanged: (value) {
setState(() {
_snoozeMinutes = value.toInt();
});
},
),
),
IconButton(
icon: const Icon(Icons.add_circle_outline),
onPressed: () {
setState(() {
_snoozeMinutes = (_snoozeMinutes + 5).clamp(5, 30);
});
},
),
],
),
Text('贪睡 $_snoozeMinutes 分钟后再次提醒'),
],
),
),
)
7. 自动排序功能
void _addAlarm() async {
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (_) => const AddAlarmPage()),
);
if (result != null) {
setState(() {
_alarms.add(result);
// 按时间排序
_alarms.sort((a, b) {
final aMinutes = a.time.hour * 60 + a.time.minute;
final bMinutes = b.time.hour * 60 + b.time.minute;
return aMinutes.compareTo(bMinutes);
});
});
_saveAlarms();
}
}
排序逻辑:
- 将时间转换为分钟数(hour * 60 + minute)
- 比较分钟数大小
- 从早到晚排序
8. 大字号时间显示
Card(
child: InkWell(
onTap: _selectTime,
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
children: [
const Text(
'闹钟时间',
style: TextStyle(fontSize: 16, color: Colors.grey),
),
const SizedBox(height: 8),
Text(
timeString,
style: const TextStyle(
fontSize: 64, // 大字号
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
const SizedBox(height: 8),
const Text(
'点击修改时间',
style: TextStyle(fontSize: 14, color: Colors.grey),
),
],
),
),
),
)
技术要点详解
TimeOfDay类详解
TimeOfDay是Flutter中表示时间的类:
// 创建TimeOfDay
final now = TimeOfDay.now();
final custom = TimeOfDay(hour: 8, minute: 30);
// 属性
int hour = now.hour; // 小时(0-23)
int minute = now.minute; // 分钟(0-59)
// 格式化显示
String format(BuildContext context) {
return now.format(context); // 根据系统设置格式化
}
// 比较时间
bool isBefore(TimeOfDay other) {
final thisMinutes = hour * 60 + minute;
final otherMinutes = other.hour * 60 + other.minute;
return thisMinutes < otherMinutes;
}
// 序列化
Map<String, int> toJson() => {'hour': hour, 'minute': minute};
TimeOfDay fromJson(Map<String, int> json) =>
TimeOfDay(hour: json['hour']!, minute: json['minute']!);
FilterChip组件
FilterChip是可选择的筹码组件:
FilterChip(
label: Text('标签'),
selected: isSelected,
onSelected: (bool selected) {
setState(() {
isSelected = selected;
});
},
selectedColor: Colors.blue,
checkmarkColor: Colors.white,
backgroundColor: Colors.grey[200],
labelStyle: TextStyle(
color: isSelected ? Colors.white : Colors.black,
),
)
FilterChip vs ChoiceChip:
- FilterChip:可多选
- ChoiceChip:单选
DropdownButtonFormField
下拉选择表单字段:
DropdownButtonFormField<String>(
value: _selectedValue,
decoration: const InputDecoration(
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.music_note),
),
items: _options.map((option) {
return DropdownMenuItem(
value: option,
child: Text(option),
);
}).toList(),
onChanged: (value) {
setState(() {
_selectedValue = value!;
});
},
validator: (value) {
if (value == null) return '请选择';
return null;
},
)
SwitchListTile
带标题的开关组件:
SwitchListTile(
title: const Text('振动'),
subtitle: const Text('闹钟响起时振动'),
value: _vibrate,
onChanged: (value) {
setState(() {
_vibrate = value;
});
},
secondary: const Icon(Icons.vibration),
activeColor: Colors.blue,
)
SwitchListTile vs Switch:
- SwitchListTile:包含标题、副标题、图标
- Switch:仅开关本身
List排序技巧
Dart中List的排序方法:
// 升序排序
list.sort((a, b) => a.compareTo(b));
// 降序排序
list.sort((a, b) => b.compareTo(a));
// 自定义排序
list.sort((a, b) {
// 返回负数:a在b前面
// 返回0:顺序不变
// 返回正数:b在a前面
return a.value.compareTo(b.value);
});
// 多条件排序
list.sort((a, b) {
// 先按第一条件排序
int result = a.priority.compareTo(b.priority);
if (result != 0) return result;
// 第一条件相同,按第二条件排序
return a.time.compareTo(b.time);
});
闹钟功能详解
重复模式说明
闹钟支持多种重复模式:
| 模式 | 说明 | repeatDays值 |
|---|---|---|
| 仅一次 | 只响一次 | [] |
| 每天 | 每天都响 | [1,2,3,4,5,6,7] |
| 工作日 | 周一到周五 | [1,2,3,4,5] |
| 周末 | 周六和周日 | [6,7] |
| 自定义 | 选择特定日期 | 如[1,3,5] |
重复逻辑:
// 判断今天是否需要响铃
bool shouldRingToday(Alarm alarm) {
if (alarm.repeatDays.isEmpty) {
// 仅一次:检查是否是设定日期
return true;
}
// 获取今天是星期几(1-7)
final today = DateTime.now().weekday;
// 检查今天是否在重复列表中
return alarm.repeatDays.contains(today);
}
贪睡功能原理
贪睡功能让用户可以延迟闹钟:
class SnoozeManager {
// 贪睡闹钟
void snoozeAlarm(Alarm alarm) {
final now = DateTime.now();
final snoozeTime = now.add(Duration(minutes: alarm.snoozeMinutes));
// 设置新的闹钟时间
scheduleAlarm(snoozeTime, alarm);
}
// 计算下次响铃时间
DateTime getNextAlarmTime(Alarm alarm) {
final now = DateTime.now();
var next = DateTime(
now.year,
now.month,
now.day,
alarm.time.hour,
alarm.time.minute,
);
// 如果今天的时间已过,找下一个重复日
if (next.isBefore(now)) {
next = next.add(const Duration(days: 1));
}
// 如果有重复设置,找到下一个重复日
if (alarm.repeatDays.isNotEmpty) {
while (!alarm.repeatDays.contains(next.weekday)) {
next = next.add(const Duration(days: 1));
}
}
return next;
}
}
铃声管理
应用内置多种铃声:
class RingtoneManager {
static const ringtones = {
'默认铃声': 'assets/sounds/default.mp3',
'清晨': 'assets/sounds/morning.mp3',
'阳光': 'assets/sounds/sunshine.mp3',
'海浪': 'assets/sounds/ocean.mp3',
'鸟鸣': 'assets/sounds/birds.mp3',
'钢琴': 'assets/sounds/piano.mp3',
'吉他': 'assets/sounds/guitar.mp3',
'铃铛': 'assets/sounds/bell.mp3',
};
// 播放铃声
Future<void> playRingtone(String name) async {
final path = ringtones[name];
if (path != null) {
// 使用audioplayers播放
final player = AudioPlayer();
await player.play(AssetSource(path));
}
}
}
功能扩展建议
1. 本地通知集成
使用flutter_local_notifications实现真实闹钟:
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:timezone/timezone.dart' as tz;
class AlarmNotificationService {
final FlutterLocalNotificationsPlugin _notifications =
FlutterLocalNotificationsPlugin();
Future<void> initialize() async {
const androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher');
const iosSettings = DarwinInitializationSettings();
const settings = InitializationSettings(
android: androidSettings,
iOS: iosSettings,
);
await _notifications.initialize(settings);
}
Future<void> scheduleAlarm(Alarm alarm) async {
final now = DateTime.now();
var scheduledDate = DateTime(
now.year,
now.month,
now.day,
alarm.time.hour,
alarm.time.minute,
);
if (scheduledDate.isBefore(now)) {
scheduledDate = scheduledDate.add(const Duration(days: 1));
}
await _notifications.zonedSchedule(
alarm.id.hashCode,
alarm.label.isEmpty ? '闹钟' : alarm.label,
'时间到了',
tz.TZDateTime.from(scheduledDate, tz.local),
NotificationDetails(
android: AndroidNotificationDetails(
'alarm_channel',
'闹钟',
importance: Importance.max,
priority: Priority.high,
sound: RawResourceAndroidNotificationSound(alarm.ringtone),
enableVibration: alarm.vibrate,
playSound: true,
),
),
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime,
matchDateTimeComponents: alarm.isRepeating
? DateTimeComponents.time
: DateTimeComponents.dateAndTime,
);
}
Future<void> cancelAlarm(String alarmId) async {
await _notifications.cancel(alarmId.hashCode);
}
}
2. 音频播放功能
集成audioplayers播放铃声:
import 'package:audioplayers/audioplayers.dart';
class AudioService {
final AudioPlayer _player = AudioPlayer();
Future<void> playAlarmSound(String ringtone) async {
await _player.setReleaseMode(ReleaseMode.loop); // 循环播放
await _player.setVolume(1.0);
await _player.play(AssetSource('sounds/$ringtone.mp3'));
}
Future<void> stopAlarmSound() async {
await _player.stop();
}
Future<void> previewRingtone(String ringtone) async {
await _player.setReleaseMode(ReleaseMode.release);
await _player.play(AssetSource('sounds/$ringtone.mp3'));
// 3秒后自动停止
await Future.delayed(const Duration(seconds: 3));
await _player.stop();
}
}
3. 振动功能
使用vibration包实现振动:
import 'package:vibration/vibration.dart';
class VibrationService {
Future<void> vibrateAlarm() async {
// 检查设备是否支持振动
final hasVibrator = await Vibration.hasVibrator();
if (hasVibrator == true) {
// 持续振动模式:振动500ms,暂停1000ms,循环
Vibration.vibrate(
pattern: [500, 1000, 500, 1000],
repeat: 0, // 从索引0开始重复
);
}
}
Future<void> stopVibration() async {
await Vibration.cancel();
}
Future<void> vibrateOnce() async {
await Vibration.vibrate(duration: 200);
}
}
4. 闹钟响铃界面
创建全屏闹钟响铃页面:
class AlarmRingingPage extends StatefulWidget {
final Alarm alarm;
const AlarmRingingPage({super.key, required this.alarm});
State<AlarmRingingPage> createState() => _AlarmRingingPageState();
}
class _AlarmRingingPageState extends State<AlarmRingingPage> {
final AudioService _audioService = AudioService();
final VibrationService _vibrationService = VibrationService();
void initState() {
super.initState();
_startAlarm();
}
Future<void> _startAlarm() async {
await _audioService.playAlarmSound(widget.alarm.ringtone);
if (widget.alarm.vibrate) {
await _vibrationService.vibrateAlarm();
}
}
Future<void> _stopAlarm() async {
await _audioService.stopAlarmSound();
await _vibrationService.stopVibration();
}
void _dismissAlarm() {
_stopAlarm();
Navigator.pop(context);
}
void _snoozeAlarm() {
_stopAlarm();
// 设置贪睡
final snoozeTime = DateTime.now().add(
Duration(minutes: widget.alarm.snoozeMinutes)
);
// 重新调度闹钟
Navigator.pop(context);
}
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blue,
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.alarm,
size: 120,
color: Colors.white,
),
const SizedBox(height: 32),
Text(
'${widget.alarm.time.hour.toString().padLeft(2, '0')}:'
'${widget.alarm.time.minute.toString().padLeft(2, '0')}',
style: const TextStyle(
fontSize: 72,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const SizedBox(height: 16),
if (widget.alarm.label.isNotEmpty)
Text(
widget.alarm.label,
style: const TextStyle(
fontSize: 24,
color: Colors.white,
),
),
const SizedBox(height: 64),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Column(
children: [
FloatingActionButton(
onPressed: _snoozeAlarm,
backgroundColor: Colors.white,
child: const Icon(Icons.snooze, color: Colors.blue),
),
const SizedBox(height: 8),
Text(
'贪睡 ${widget.alarm.snoozeMinutes}分钟',
style: const TextStyle(color: Colors.white),
),
],
),
Column(
children: [
FloatingActionButton(
onPressed: _dismissAlarm,
backgroundColor: Colors.red,
child: const Icon(Icons.close, color: Colors.white),
),
const SizedBox(height: 8),
const Text(
'关闭',
style: TextStyle(color: Colors.white),
),
],
),
],
),
],
),
),
),
);
}
void dispose() {
_stopAlarm();
super.dispose();
}
}
5. 渐强音量
实现铃声音量逐渐增大:
class GradualVolumeService {
final AudioPlayer _player = AudioPlayer();
Timer? _volumeTimer;
double _currentVolume = 0.1;
Future<void> playWithGradualVolume(String ringtone) async {
_currentVolume = 0.1;
await _player.setVolume(_currentVolume);
await _player.play(AssetSource('sounds/$ringtone.mp3'));
// 每秒增加音量
_volumeTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (_currentVolume < 1.0) {
_currentVolume += 0.1;
_player.setVolume(_currentVolume);
} else {
timer.cancel();
}
});
}
void dispose() {
_volumeTimer?.cancel();
_player.dispose();
}
}
6. 闹钟历史记录
记录闹钟响铃历史:
class AlarmHistory {
String alarmId;
DateTime ringTime;
String action; // 'dismissed' 或 'snoozed'
AlarmHistory({
required this.alarmId,
required this.ringTime,
required this.action,
});
}
class AlarmHistoryManager {
List<AlarmHistory> _history = [];
void addHistory(String alarmId, String action) {
_history.add(AlarmHistory(
alarmId: alarmId,
ringTime: DateTime.now(),
action: action,
));
_saveHistory();
}
List<AlarmHistory> getHistoryForAlarm(String alarmId) {
return _history.where((h) => h.alarmId == alarmId).toList();
}
Map<String, int> getStatistics() {
return {
'total': _history.length,
'dismissed': _history.where((h) => h.action == 'dismissed').length,
'snoozed': _history.where((h) => h.action == 'snoozed').length,
};
}
}
7. 智能闹钟
根据睡眠周期智能设置闹钟:
class SmartAlarmCalculator {
// 睡眠周期约90分钟
static const sleepCycleDuration = Duration(minutes: 90);
// 计算最佳起床时间
List<TimeOfDay> calculateOptimalWakeTime(TimeOfDay bedTime, int cycles) {
final bedDateTime = DateTime(
DateTime.now().year,
DateTime.now().month,
DateTime.now().day,
bedTime.hour,
bedTime.minute,
);
List<TimeOfDay> suggestions = [];
// 加上入睡时间(约15分钟)
var wakeTime = bedDateTime.add(const Duration(minutes: 15));
// 计算每个周期的起床时间
for (int i = 1; i <= cycles; i++) {
wakeTime = wakeTime.add(sleepCycleDuration);
suggestions.add(TimeOfDay.fromDateTime(wakeTime));
}
return suggestions;
}
// 推荐睡眠周期数(4-6个周期)
Widget buildSleepCycleSuggestions(TimeOfDay bedTime) {
return Column(
children: [
const Text('建议起床时间(基于90分钟睡眠周期):'),
...List.generate(6, (index) {
final cycles = index + 1;
final times = calculateOptimalWakeTime(bedTime, cycles);
final wakeTime = times.last;
final hours = cycles * 1.5;
return ListTile(
title: Text(
'${wakeTime.hour.toString().padLeft(2, '0')}:'
'${wakeTime.minute.toString().padLeft(2, '0')}'
),
subtitle: Text('$cycles个周期(${hours}小时)'),
trailing: cycles >= 4 && cycles <= 6
? const Icon(Icons.check_circle, color: Colors.green)
: null,
);
}),
],
);
}
}
8. 天气关联
根据天气调整闹钟:
import 'package:weather/weather.dart';
class WeatherAlarmService {
final WeatherFactory _weatherFactory = WeatherFactory('YOUR_API_KEY');
Future<void> adjustAlarmForWeather(Alarm alarm) async {
try {
final weather = await _weatherFactory.currentWeatherByCityName('Beijing');
// 雨天提前15分钟
if (weather.weatherMain == 'Rain') {
final newTime = TimeOfDay(
hour: alarm.time.hour,
minute: (alarm.time.minute - 15).clamp(0, 59),
);
alarm.time = newTime;
}
// 雪天提前30分钟
if (weather.weatherMain == 'Snow') {
final newTime = TimeOfDay(
hour: alarm.time.hour,
minute: (alarm.time.minute - 30).clamp(0, 59),
);
alarm.time = newTime;
}
} catch (e) {
print('获取天气失败: $e');
}
}
}
常见问题解答
Q1: 如何实现闹钟分组管理?
A: 添加分组功能:
class AlarmGroup {
String id;
String name;
Color color;
List<String> alarmIds;
}
class AlarmWithGroup {
Alarm alarm;
AlarmGroup? group;
}
// 按分组显示
Widget _buildGroupedAlarms() {
final groups = _getGroups();
return ListView.builder(
itemCount: groups.length,
itemBuilder: (context, index) {
final group = groups[index];
final alarms = _getAlarmsInGroup(group.id);
return ExpansionTile(
title: Text(group.name),
leading: Icon(Icons.folder, color: group.color),
children: alarms.map((alarm) => _buildAlarmCard(alarm)).toList(),
);
},
);
}
Q2: 如何添加数学题解锁功能?
A: 实现解题才能关闭闹钟:
class MathChallenge {
int num1;
int num2;
String operator;
MathChallenge() {
final random = Random();
num1 = random.nextInt(20) + 1;
num2 = random.nextInt(20) + 1;
operator = ['+', '-', '×'][random.nextInt(3)];
}
int get answer {
switch (operator) {
case '+':
return num1 + num2;
case '-':
return num1 - num2;
case '×':
return num1 * num2;
default:
return 0;
}
}
String get question => '$num1 $operator $num2 = ?';
}
class AlarmDismissChallenge extends StatefulWidget {
final Alarm alarm;
const AlarmDismissChallenge({super.key, required this.alarm});
State<AlarmDismissChallenge> createState() => _AlarmDismissChallengeState();
}
class _AlarmDismissChallengeState extends State<AlarmDismissChallenge> {
late MathChallenge _challenge;
final _answerController = TextEditingController();
String _message = '';
void initState() {
super.initState();
_challenge = MathChallenge();
}
void _checkAnswer() {
final userAnswer = int.tryParse(_answerController.text);
if (userAnswer == _challenge.answer) {
Navigator.pop(context, true); // 答对了,关闭闹钟
} else {
setState(() {
_message = '答案错误,请重试';
_challenge = MathChallenge(); // 生成新题目
_answerController.clear();
});
}
}
Widget build(BuildContext context) {
return AlertDialog(
title: const Text('解答数学题关闭闹钟'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
_challenge.question,
style: const TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
TextField(
controller: _answerController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
labelText: '答案',
border: OutlineInputBorder(),
),
),
if (_message.isNotEmpty) ...[
const SizedBox(height: 8),
Text(_message, style: const TextStyle(color: Colors.red)),
],
],
),
actions: [
FilledButton(
onPressed: _checkAnswer,
child: const Text('提交'),
),
],
);
}
}
Q3: 如何实现摇一摇关闭闹钟?
A: 使用sensors_plus包:
import 'package:sensors_plus/sensors_plus.dart';
class ShakeDetector {
StreamSubscription? _accelerometerSubscription;
int _shakeCount = 0;
DateTime? _lastShakeTime;
void startListening(Function onShakeComplete) {
_accelerometerSubscription = accelerometerEvents.listen((event) {
final acceleration = event.x.abs() + event.y.abs() + event.z.abs();
// 检测到强烈晃动
if (acceleration > 30) {
final now = DateTime.now();
// 1秒内的晃动计数
if (_lastShakeTime == null ||
now.difference(_lastShakeTime!).inSeconds > 1) {
_shakeCount = 0;
}
_shakeCount++;
_lastShakeTime = now;
// 摇动5次关闭闹钟
if (_shakeCount >= 5) {
onShakeComplete();
_shakeCount = 0;
}
}
});
}
void stopListening() {
_accelerometerSubscription?.cancel();
}
}
// 使用
class AlarmRingingPage extends StatefulWidget {
State<AlarmRingingPage> createState() => _AlarmRingingPageState();
}
class _AlarmRingingPageState extends State<AlarmRingingPage> {
final ShakeDetector _shakeDetector = ShakeDetector();
void initState() {
super.initState();
_shakeDetector.startListening(() {
_dismissAlarm();
});
}
void dispose() {
_shakeDetector.stopListening();
super.dispose();
}
}
Q4: 如何导出/导入闹钟数据?
A: 实现数据导入导出:
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';
class AlarmBackupService {
// 导出闹钟数据
Future<void> exportAlarms(List<Alarm> alarms) async {
final json = jsonEncode(alarms.map((a) => a.toJson()).toList());
final directory = await getApplicationDocumentsDirectory();
final file = File('${directory.path}/alarms_backup.json');
await file.writeAsString(json);
// 分享文件
await Share.shareXFiles([XFile(file.path)], text: '闹钟备份');
}
// 导入闹钟数据
Future<List<Alarm>> importAlarms(String filePath) async {
final file = File(filePath);
final json = await file.readAsString();
final List<dynamic> decoded = jsonDecode(json);
return decoded.map((json) => Alarm.fromJson(json)).toList();
}
// 自动备份
Future<void> autoBackup(List<Alarm> alarms) async {
final prefs = await SharedPreferences.getInstance();
final lastBackup = prefs.getString('last_backup');
final now = DateTime.now();
// 每周自动备份一次
if (lastBackup == null ||
now.difference(DateTime.parse(lastBackup)).inDays >= 7) {
await exportAlarms(alarms);
await prefs.setString('last_backup', now.toIso8601String());
}
}
}
Q5: 如何实现闹钟同步到云端?
A: 使用Firebase Firestore:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
class AlarmCloudSync {
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
final FirebaseAuth _auth = FirebaseAuth.instance;
// 上传闹钟到云端
Future<void> uploadAlarms(List<Alarm> alarms) async {
final user = _auth.currentUser;
if (user == null) return;
final batch = _firestore.batch();
final collection = _firestore
.collection('users')
.doc(user.uid)
.collection('alarms');
for (var alarm in alarms) {
final doc = collection.doc(alarm.id);
batch.set(doc, alarm.toJson());
}
await batch.commit();
}
// 从云端下载闹钟
Future<List<Alarm>> downloadAlarms() async {
final user = _auth.currentUser;
if (user == null) return [];
final snapshot = await _firestore
.collection('users')
.doc(user.uid)
.collection('alarms')
.get();
return snapshot.docs
.map((doc) => Alarm.fromJson(doc.data()))
.toList();
}
// 实时同步
Stream<List<Alarm>> syncAlarms() {
final user = _auth.currentUser;
if (user == null) return Stream.value([]);
return _firestore
.collection('users')
.doc(user.uid)
.collection('alarms')
.snapshots()
.map((snapshot) => snapshot.docs
.map((doc) => Alarm.fromJson(doc.data()))
.toList());
}
}
项目总结
实现的功能
✅ 闹钟管理(增删改查)
✅ 24小时制时间选择
✅ 多种重复模式(每天/工作日/周末/自定义)
✅ 8种铃声选择
✅ 振动开关设置
✅ 贪睡时长调节(5-30分钟)
✅ 开关控制启用/禁用
✅ 自动时间排序
✅ 状态统计显示
✅ 数据本地持久化
技术亮点
- TimeOfDay:专业的时间选择和显示
- FilterChip:快速重复模式选择
- 圆形按钮:直观的星期选择
- Switch开关:便捷的启用控制
- Slider滑块:贪睡时长调节
- 自动排序:按时间智能排序
- 大字号显示:清晰的时间展示
- 数据持久化:完整的JSON序列化
应用场景
- ⏰ 日常起床闹钟
- 💼 工作提醒
- 🏃 运动计时
- 💊 吃药提醒
- 📚 学习计划
- 🍳 烹饪计时
- 🚌 出行提醒
- 💤 午休闹钟
架构设计
数据流程
手机闹钟管理器是一款功能完善的闹钟应用,提供了专业的时间管理功能。通过直观的界面设计和丰富的自定义选项,让用户能够精确管理每一个闹钟,确保不错过任何重要时刻。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)