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:设置为圆形
  • widthheight必须相等
  • 配合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();
  }
}

排序逻辑

  1. 将时间转换为分钟数(hour * 60 + minute)
  2. 比较分钟数大小
  3. 从早到晚排序

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分钟)
✅ 开关控制启用/禁用
✅ 自动时间排序
✅ 状态统计显示
✅ 数据本地持久化

技术亮点

  1. TimeOfDay:专业的时间选择和显示
  2. FilterChip:快速重复模式选择
  3. 圆形按钮:直观的星期选择
  4. Switch开关:便捷的启用控制
  5. Slider滑块:贪睡时长调节
  6. 自动排序:按时间智能排序
  7. 大字号显示:清晰的时间展示
  8. 数据持久化:完整的JSON序列化

应用场景

  • ⏰ 日常起床闹钟
  • 💼 工作提醒
  • 🏃 运动计时
  • 💊 吃药提醒
  • 📚 学习计划
  • 🍳 烹饪计时
  • 🚌 出行提醒
  • 💤 午休闹钟

架构设计

闹钟管理器

闹钟列表

添加闹钟

编辑闹钟

显示闹钟

开关控制

删除闹钟

时间选择

重复设置

铃声选择

振动设置

贪睡设置

SharedPreferences

数据流程

SharedPreferences 状态管理 界面 用户 SharedPreferences 状态管理 界面 用户 添加闹钟 显示添加页面 设置时间和选项 创建闹钟对象 添加到列表 按时间排序 保存数据 保存成功 更新界面 显示新闹钟

手机闹钟管理器是一款功能完善的闹钟应用,提供了专业的时间管理功能。通过直观的界面设计和丰富的自定义选项,让用户能够精确管理每一个闹钟,确保不错过任何重要时刻。

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

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

更多推荐