Flutter for OpenHarmony 闹钟时钟应用开发实战
社区引导信息
欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net
作者:maaath
前言
在移动应用开发领域,跨平台框架一直是开发者们关注的焦点。Flutter作为Google推出的UI框架,凭借其高性能和一致的用户体验,已经成为跨平台开发的首选方案之一。而当Flutter遇上OpenHarmony,会碰撞出怎样的火花?本文将通过一个完整的闹钟时钟应用实例,带大家深入了解Flutter for OpenHarmony的实际开发流程。
一、项目概述
本文将手把手教你使用Flutter for OpenHarmony开发一个功能完善的闹钟时钟应用。该应用具备以下核心功能:
- 闹钟创建、编辑、删除与开关控制
- 重复周期设置(单次、每天、工作日、周末、自定义)
- 闹钟铃声选择与震动模式设置
- 世界时钟切换
- 秒表计时功能
- 倒计时功能
- 睡眠记录统计
1.1 技术栈
- 框架:Flutter 4.0+ (适配OpenHarmony)
- 状态管理:Riverpod / Provider
- 本地存储:SharedPreferences
- 通知服务:Flutter Local Notifications
- 平台通道:MethodChannel(用于原生能力调用)
二、项目结构设计
良好的项目结构是大型应用开发的基础。我们采用功能模块化的设计理念:
lib/
├── main.dart # 应用入口
├── models/ # 数据模型
│ ├── alarm_model.dart
│ ├── world_clock_model.dart
│ └── sleep_record_model.dart
├── providers/ # 状态管理
│ ├── alarm_provider.dart
│ ├── stopwatch_provider.dart
│ └── countdown_provider.dart
├── screens/ # 页面
│ ├── home_screen.dart
│ ├── alarm_screen.dart
│ ├── world_clock_screen.dart
│ ├── stopwatch_screen.dart
│ ├── countdown_screen.dart
│ └── sleep_screen.dart
├── widgets/ # 通用组件
│ ├── time_picker.dart
│ └── circular_timer.dart
└── services/ # 服务层
├── notification_service.dart
└── storage_service.dart
三、核心数据模型
3.1 闹钟模型
dart
// models/alarm_model.dart
enum RepeatType {
once, // 单次
daily, // 每天
weekdays, // 工作日
weekend, // 周末
custom // 自定义
}
class AlarmModel {
final String id;
final int hour;
final int minute;
final String label;
final bool isEnabled;
final RepeatType repeatType;
final List<int> customRepeatDays; // 0=周日,1=周一...6=周六
final String ringtoneId;
final String ringtoneName;
final bool vibrate;
AlarmModel({
required this.id,
required this.hour,
required this.minute,
this.label = '',
this.isEnabled = true,
this.repeatType = RepeatType.once,
this.customRepeatDays = const [],
this.ringtoneId = 'default',
this.ringtoneName = '默认铃声',
this.vibrate = true,
});
// 复制并修改
AlarmModel copyWith({
String? id,
int? hour,
int? minute,
String? label,
bool? isEnabled,
RepeatType? repeatType,
List<int>? customRepeatDays,
String? ringtoneId,
String? ringtoneName,
bool? vibrate,
}) {
return AlarmModel(
id: id ?? this.id,
hour: hour ?? this.hour,
minute: minute ?? this.minute,
label: label ?? this.label,
isEnabled: isEnabled ?? this.isEnabled,
repeatType: repeatType ?? this.repeatType,
customRepeatDays: customRepeatDays ?? this.customRepeatDays,
ringtoneId: ringtoneId ?? this.ringtoneId,
ringtoneName: ringtoneName ?? this.ringtoneName,
vibrate: vibrate ?? this.vibrate,
);
}
// 格式化时间显示
String get formattedTime {
final h = hour.toString().padLeft(2, '0');
final m = minute.toString().padLeft(2, '0');
return '$h:$m';
}
// 获取重复描述
String get repeatDescription {
switch (repeatType) {
case RepeatType.once:
return '不重复';
case RepeatType.daily:
return '每天';
case RepeatType.weekdays:
return '工作日';
case RepeatType.weekend:
return '周末';
case RepeatType.custom:
return _formatCustomDays();
}
}
String _formatCustomDays() {
if (customRepeatDays.isEmpty) return '不重复';
final dayNames = ['日', '一', '二', '三', '四', '五', '六'];
return customRepeatDays.map((d) => dayNames[d]).join('、');
}
// 序列化
Map<String, dynamic> toJson() {
return {
'id': id,
'hour': hour,
'minute': minute,
'label': label,
'isEnabled': isEnabled,
'repeatType': repeatType.index,
'customRepeatDays': customRepeatDays,
'ringtoneId': ringtoneId,
'ringtoneName': ringtoneName,
'vibrate': vibrate,
};
}
// 反序列化
factory AlarmModel.fromJson(Map<String, dynamic> json) {
return AlarmModel(
id: json['id'] as String,
hour: json['hour'] as int,
minute: json['minute'] as int,
label: json['label'] as String? ?? '',
isEnabled: json['isEnabled'] as bool? ?? true,
repeatType: RepeatType.values[json['repeatType'] as int? ?? 0],
customRepeatDays: (json['customRepeatDays'] as List<dynamic>?)
?.map((e) => e as int)
.toList() ??
[],
ringtoneId: json['ringtoneId'] as String? ?? 'default',
ringtoneName: json['ringtoneName'] as String? ?? '默认铃声',
vibrate: json['vibrate'] as bool? ?? true,
);
}
}
3.2 世界时钟模型
dart
// models/world_clock_model.dart
class WorldClockModel {
final String id;
final String cityName;
final String cityNameZh;
final String timezone;
final int utcOffset; // 相对于UTC的偏移小时数
final bool isLocalZone;
WorldClockModel({
required this.id,
required this.cityName,
required this.cityNameZh,
required this.timezone,
required this.utcOffset,
this.isLocalZone = false,
});
// 获取该城市当前时间
DateTime get currentTime {
final now = DateTime.now();
final utc = now.toUtc();
return utc.add(Duration(hours: utcOffset));
}
// 格式化时间
String get formattedTime {
final time = currentTime;
final h = time.hour.toString().padLeft(2, '0');
final m = time.minute.toString().padLeft(2, '0');
return '$h:$m';
}
// 格式化时区偏移
String get offsetText {
if (utcOffset == 0) return 'GMT';
final sign = utcOffset > 0 ? '+' : '';
return 'GMT$sign$utcOffset';
}
}
// 预设的世界城市列表
class WorldClockPresets {
static final List<WorldClockModel> presets = [
WorldClockModel(
id: 'beijing',
cityName: 'Beijing',
cityNameZh: '北京',
timezone: 'Asia/Shanghai',
utcOffset: 8,
isLocalZone: true,
),
WorldClockModel(
id: 'tokyo',
cityName: 'Tokyo',
cityNameZh: '东京',
timezone: 'Asia/Tokyo',
utcOffset: 9,
),
WorldClockModel(
id: 'newyork',
cityName: 'New York',
cityNameZh: '纽约',
timezone: 'America/New_York',
utcOffset: -5,
),
WorldClockModel(
id: 'london',
cityName: 'London',
cityNameZh: '伦敦',
timezone: 'Europe/London',
utcOffset: 0,
),
WorldClockModel(
id: 'paris',
cityName: 'Paris',
cityNameZh: '巴黎',
timezone: 'Europe/Paris',
utcOffset: 1,
),
WorldClockModel(
id: 'sydney',
cityName: 'Sydney',
cityNameZh: '悉尼',
timezone: 'Australia/Sydney',
utcOffset: 10,
),
WorldClockModel(
id: 'dubai',
cityName: 'Dubai',
cityNameZh: '迪拜',
timezone: 'Asia/Dubai',
utcOffset: 4,
),
WorldClockModel(
id: 'singapore',
cityName: 'Singapore',
cityNameZh: '新加坡',
timezone: 'Asia/Singapore',
utcOffset: 8,
),
WorldClockModel(
id: 'seoul',
cityName: 'Seoul',
cityNameZh: '首尔',
timezone: 'Asia/Seoul',
utcOffset: 9,
),
WorldClockModel(
id: 'losangeles',
cityName: 'Los Angeles',
cityNameZh: '洛杉矶',
timezone: 'America/Los_Angeles',
utcOffset: -8,
),
];
}
四、状态管理实现
4.1 闹钟状态管理
dart
// providers/alarm_provider.dart
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../models/alarm_model.dart';
class AlarmProvider extends ChangeNotifier {
static const String _storageKey = 'alarms_data';
List<AlarmModel> _alarms = [];
SharedPreferences? _prefs;
List<AlarmModel> get alarms => List.unmodifiable(_alarms);
// 初始化
Future<void> init() async {
_prefs = await SharedPreferences.getInstance();
await _loadAlarms();
}
// 加载闹钟数据
Future<void> _loadAlarms() async {
final data = _prefs?.getString(_storageKey);
if (data != null) {
final List<dynamic> jsonList = json.decode(data);
_alarms = jsonList.map((e) => AlarmModel.fromJson(e)).toList();
_sortAlarms();
notifyListeners();
}
}
// 保存闹钟数据
Future<void> _saveAlarms() async {
final jsonList = _alarms.map((e) => e.toJson()).toList();
await _prefs?.setString(_storageKey, json.encode(jsonList));
}
// 添加闹钟
Future<void> addAlarm(AlarmModel alarm) async {
_alarms.add(alarm);
_sortAlarms();
await _saveAlarms();
notifyListeners();
}
// 更新闹钟
Future<void> updateAlarm(AlarmModel alarm) async {
final index = _alarms.indexWhere((a) => a.id == alarm.id);
if (index != -1) {
_alarms[index] = alarm;
_sortAlarms();
await _saveAlarms();
notifyListeners();
}
}
// 删除闹钟
Future<void> deleteAlarm(String id) async {
_alarms.removeWhere((a) => a.id == id);
await _saveAlarms();
notifyListeners();
}
// 切换闹钟开关
Future<void> toggleAlarm(String id) async {
final index = _alarms.indexWhere((a) => a.id == id);
if (index != -1) {
_alarms[index] = _alarms[index].copyWith(
isEnabled: !_alarms[index].isEnabled,
);
await _saveAlarms();
notifyListeners();
}
}
// 获取下一个闹钟
AlarmModel? getNextAlarm() {
final enabled = _alarms.where((a) => a.isEnabled).toList();
if (enabled.isEmpty) return null;
final now = DateTime.now();
final currentMinutes = now.hour * 60 + now.minute;
enabled.sort((a, b) {
final aTime = a.hour * 60 + a.minute;
final bTime = b.hour * 60 + b.minute;
return aTime.compareTo(bTime);
});
for (final alarm in enabled) {
final alarmMinutes = alarm.hour * 60 + alarm.minute;
if (alarmMinutes > currentMinutes) {
return alarm;
}
}
return enabled.first;
}
// 按时间排序
void _sortAlarms() {
_alarms.sort((a, b) {
final aTime = a.hour * 60 + a.minute;
final bTime = b.hour * 60 + b.minute;
return aTime.compareTo(bTime);
});
}
}
4.2 秒表状态管理
dart
// providers/stopwatch_provider.dart
import 'package:flutter/foundation.dart';
class LapRecord {
final int lapNumber;
final int elapsedTime; // 毫秒
final int lapTime; // 单圈时间
LapRecord({
required this.lapNumber,
required this.elapsedTime,
required this.lapTime,
});
}
class StopwatchProvider extends ChangeNotifier {
bool _isRunning = false;
int _elapsedTime = 0;
final List<LapRecord> _laps = [];
DateTime? _startTime;
bool get isRunning => _isRunning;
int get elapsedTime => _elapsedTime;
List<LapRecord> get laps => List.unmodifiable(_laps);
// 格式化时间
String get formattedTime {
final totalSeconds = _elapsedTime ~/ 1000;
final minutes = totalSeconds ~/ 60;
final seconds = totalSeconds % 60;
final centiseconds = (_elapsedTime % 1000) ~/ 10;
return '${minutes.toString().padLeft(2, '0')}:'
'${seconds.toString().padLeft(2, '0')}.'
'${centiseconds.toString().padLeft(2, '0')}';
}
// 开始计时
void start() {
if (_isRunning) return;
_isRunning = true;
_startTime = DateTime.now();
notifyListeners();
}
// 暂停
void pause() {
if (!_isRunning) return;
_isRunning = false;
notifyListeners();
}
// 重置
void reset() {
_isRunning = false;
_elapsedTime = 0;
_laps.clear();
_startTime = null;
notifyListeners();
}
// 计次
void lap() {
if (!_isRunning || _startTime == null) return;
final now = DateTime.now();
_elapsedTime = now.difference(_startTime!).inMilliseconds;
final lastLapTime = _laps.isEmpty ? 0 : _laps.last.elapsedTime;
_laps.add(LapRecord(
lapNumber: _laps.length + 1,
elapsedTime: _elapsedTime,
lapTime: _elapsedTime - lastLapTime,
));
notifyListeners();
}
// 更新计时(由Timer调用)
void tick() {
if (_isRunning && _startTime != null) {
_elapsedTime = DateTime.now().difference(_startTime!).inMilliseconds;
notifyListeners();
}
}
// 格式化单圈时间
String formatLapTime(int milliseconds) {
final totalSeconds = milliseconds ~/ 1000;
final minutes = totalSeconds ~/ 60;
final seconds = totalSeconds % 60;
final centiseconds = (milliseconds % 1000) ~/ 10;
return '${minutes.toString().padLeft(2, '0')}:'
'${seconds.toString().padLeft(2, '0')}.'
'${centiseconds.toString().padLeft(2, '0')}';
}
}
五、UI界面实现
5.1 主页面框架
dart
// screens/home_screen.dart
import 'package:flutter/material.dart';
import 'alarm_screen.dart';
import 'world_clock_screen.dart';
import 'stopwatch_screen.dart';
import 'sleep_screen.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int _currentIndex = 0;
final List<Widget> _screens = const [
AlarmScreen(),
WorldClockScreen(),
StopwatchScreen(),
SleepScreen(),
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: _currentIndex,
children: _screens,
),
bottomNavigationBar: NavigationBar(
selectedIndex: _currentIndex,
onDestinationSelected: (index) {
setState(() => _currentIndex = index);
},
destinations: const [
NavigationDestination(
icon: Icon(Icons.alarm_outlined),
selectedIcon: Icon(Icons.alarm),
label: '闹钟',
),
NavigationDestination(
icon: Icon(Icons.public_outlined),
selectedIcon: Icon(Icons.public),
label: '世界时钟',
),
NavigationDestination(
icon: Icon(Icons.timer_outlined),
selectedIcon: Icon(Icons.timer),
label: '秒表',
),
NavigationDestination(
icon: Icon(Icons.nightlight_outlined),
selectedIcon: Icon(Icons.nightlight),
label: '睡眠',
),
],
),
);
}
}
5.2 闹钟页面
dart
// screens/alarm_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import '../providers/alarm_provider.dart';
import '../models/alarm_model.dart';
import 'alarm_edit_screen.dart';
class AlarmScreen extends StatefulWidget {
const AlarmScreen({super.key});
@override
State<AlarmScreen> createState() => _AlarmScreenState();
}
class _AlarmScreenState extends State<AlarmScreen> {
String _currentTime = '';
String _currentDate = '';
@override
void initState() {
super.initState();
_updateTime();
// 每秒更新时间
Future.doWhile(() async {
await Future.delayed(const Duration(seconds: 1));
if (mounted) {
_updateTime();
return true;
}
return false;
});
}
void _updateTime() {
final now = DateTime.now();
setState(() {
_currentTime = '${now.hour.toString().padLeft(2, '0')}:'
'${now.minute.toString().padLeft(2, '0')}';
final weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
_currentDate = '${weekdays[now.weekday % 7]} '
'${now.month}月${now.day}日';
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF5F5F5),
body: SafeArea(
child: Column(
children: [
_buildHeader(),
_buildTimeDisplay(),
Expanded(child: _buildAlarmList()),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => _navigateToEdit(null),
backgroundColor: const Color(0xFF00B4D8),
child: const Icon(Icons.add, color: Colors.white),
),
);
}
Widget _buildHeader() {
return Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
const Text(
'闹钟',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Color(0xFF333333),
),
),
const Spacer(),
Text(
_currentTime,
style: const TextStyle(
fontSize: 16,
color: Color(0xFF666666),
),
),
],
),
);
}
Widget _buildTimeDisplay() {
return Container(
margin: const EdgeInsets.all(16),
padding: const EdgeInsets.all(30),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.08),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Column(
children: [
Text(
_currentTime,
style: const TextStyle(
fontSize: 72,
fontWeight: FontWeight.w300,
color: Color(0xFF333333),
),
),
const SizedBox(height: 10),
Text(
_currentDate,
style: const TextStyle(
fontSize: 16,
color: Color(0xFF999999),
),
),
],
),
);
}
Widget _buildAlarmList() {
return Consumer<AlarmProvider>(
builder: (context, provider, child) {
final alarms = provider.alarms;
if (alarms.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'🔔',
style: TextStyle(fontSize: 64),
),
const SizedBox(height: 16),
const Text(
'暂无闹钟',
style: TextStyle(
fontSize: 18,
color: Color(0xFF666666),
),
),
const SizedBox(height: 8),
const Text(
'点击下方按钮添加闹钟',
style: TextStyle(
fontSize: 14,
color: Color(0xFF999999),
),
),
],
),
);
}
return ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 16),
itemCount: alarms.length,
itemBuilder: (context, index) {
final alarm = alarms[index];
return _buildAlarmCard(alarm, provider);
},
);
},
);
}
Widget _buildAlarmCard(AlarmModel alarm, AlarmProvider provider) {
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Row(
children: [
Expanded(
child: GestureDetector(
onTap: () => _navigateToEdit(alarm),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
alarm.formattedTime,
style: TextStyle(
fontSize: 36,
fontWeight: FontWeight.w300,
color: alarm.isEnabled
? const Color(0xFF333333)
: const Color(0xFF999999),
),
),
if (alarm.label.isNotEmpty) ...[
const SizedBox(height: 4),
Text(
alarm.label,
style: const TextStyle(
fontSize: 14,
color: Color(0xFF666666),
),
),
],
const SizedBox(height: 4),
Text(
alarm.repeatDescription,
style: const TextStyle(
fontSize: 12,
color: Color(0xFF999999),
),
),
],
),
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Row(
children: [
Text(
alarm.ringtoneName,
style: const TextStyle(
fontSize: 12,
color: Color(0xFF999999),
),
),
if (alarm.vibrate) ...[
const SizedBox(width: 8),
const Icon(
Icons.vibration,
size: 16,
color: Color(0xFF999999),
),
],
],
),
const SizedBox(height: 10),
Row(
children: [
Switch(
value: alarm.isEnabled,
activeColor: const Color(0xFF00B4D8),
onChanged: (value) {
provider.toggleAlarm(alarm.id);
},
),
IconButton(
icon: const Icon(Icons.more_vert),
color: const Color(0xFF999999),
onPressed: () => _showAlarmOptions(alarm, provider),
),
],
),
],
),
],
),
);
}
void _navigateToEdit(AlarmModel? alarm) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AlarmEditScreen(alarm: alarm),
),
);
}
void _showAlarmOptions(AlarmModel alarm, AlarmProvider provider) {
showModalBottomSheet(
context: context,
builder: (context) => SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: const Icon(Icons.edit),
title: const Text('编辑'),
onTap: () {
Navigator.pop(context);
_navigateToEdit(alarm);
},
),
ListTile(
leading: const Icon(Icons.delete, color: Colors.red),
title: const Text('删除', style: TextStyle(color: Colors.red)),
onTap: () {
Navigator.pop(context);
provider.deleteAlarm(alarm.id);
},
),
],
),
),
);
}
}
5.3 秒表页面
dart
// screens/stopwatch_screen.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/stopwatch_provider.dart';
class StopwatchScreen extends StatefulWidget {
const StopwatchScreen({super.key});
@override
State<StopwatchScreen> createState() => _StopwatchScreenState();
}
class _StopwatchScreenState extends State<StopwatchScreen> {
Timer? _timer;
@override
void initState() {
super.initState();
_startTimer();
}
void _startTimer() {
_timer = Timer.periodic(const Duration(milliseconds: 10), (_) {
if (mounted) {
context.read<StopwatchProvider>().tick();
}
});
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: Consumer<StopwatchProvider>(
builder: (context, provider, child) {
return Column(
children: [
_buildHeader(),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
provider.formattedTime,
style: const TextStyle(
fontSize: 72,
fontWeight: FontWeight.w300,
color: Color(0xFF333333),
),
),
],
),
),
_buildControls(provider),
if (provider.laps.isNotEmpty) _buildLapList(provider),
],
);
},
),
),
);
}
Widget _buildHeader() {
return Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: const [
Text(
'秒表',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Color(0xFF333333),
),
),
],
),
);
}
Widget _buildControls(StopwatchProvider provider) {
return Padding(
padding: const EdgeInsets.all(30),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
if (provider.laps.isNotEmpty || provider.elapsedTime > 0)
_buildButton(
label: '重置',
onPressed: provider.reset,
bgColor: const Color(0xFFF0F0F0),
textColor: const Color(0xFF666666),
),
if (provider.isRunning)
_buildButton(
label: '计次',
onPressed: provider.lap,
bgColor: const Color(0xFFE0F7FA),
textColor: const Color(0xFF00B4D8),
),
_buildButton(
label: provider.isRunning ? '暂停' : '开始',
onPressed: provider.isRunning ? provider.pause : provider.start,
bgColor: const Color(0xFF00B4D8),
textColor: Colors.white,
),
],
),
);
}
Widget _buildButton({
required String label,
required VoidCallback onPressed,
required Color bgColor,
required Color textColor,
}) {
return SizedBox(
width: 100,
height: 60,
child: ElevatedButton(
onPressed: onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: bgColor,
foregroundColor: textColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
),
child: Text(
label,
style: const TextStyle(fontSize: 18),
),
),
);
}
Widget _buildLapList(StopwatchProvider provider) {
return Container(
height: 200,
margin: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Column(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
decoration: const BoxDecoration(
color: Color(0xFFF5F5F5),
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
),
child: const Row(
children: [
Text('计次', style: TextStyle(color: Color(0xFF999999))),
SizedBox(width: 100),
Text('计时', style: TextStyle(color: Color(0xFF999999))),
Spacer(),
Text('计次时间', style: TextStyle(color: Color(0xFF999999))),
],
),
),
Expanded(
child: ListView.builder(
reverse: true,
itemCount: provider.laps.length,
itemBuilder: (context, index) {
final reversedIndex = provider.laps.length - 1 - index;
final lap = provider.laps[reversedIndex];
return Container(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 15,
),
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(color: Color(0xFFF0F0F0)),
),
),
child: Row(
children: [
Text(
'第${lap.lapNumber}次',
style: const TextStyle(
fontSize: 16,
color: Color(0xFF333333),
),
),
const SizedBox(width: 40),
Text(
provider.formatLapTime(lap.elapsedTime),
style: const TextStyle(
fontSize: 16,
color: Color(0xFF333333),
),
),
const Spacer(),
Text(
provider.formatLapTime(lap.lapTime),
style: const TextStyle(
fontSize: 16,
color: Color(0xFF00B4D8),
),
),
],
),
);
},
),
),
],
),
);
}
}
六、运行截图展示
6.1 应用运行界面
以下是闹钟时钟应用在鸿蒙设备上的运行截图:
图1:闹钟主界面

显示当前时间、日期以及已设置的闹钟列表,支持开关控制快速启用/禁用。
图2:添加闹钟界面

通过上下滑动调节时间,支持设置重复周期(单次、每天、工作日、周末、自定义)。
图3:世界时钟界面

展示多个城市的时间,点击可添加更多城市,支持查看不同时区的当前时间。
图4:秒表界面

实时计时显示,支持计次功能,可记录多个分段成绩。
图5:睡眠记录界面

记录睡眠时长和质量评分,生成周统计图表。
七、代码托管
本文涉及的完整代码已托管至AtomGit平台:
仓库地址:https://atomgit.com/maaath/flutter_clock_app
仓库结构说明:
flutter_clock_app/
├── lib/
│ ├── main.dart
│ ├── models/
│ ├── providers/
│ ├── screens/
│ ├── services/
│ └── widgets/
├── screenshots/ # 运行截图
├── README.md # 项目说明
└── pubspec.yaml # 依赖配置
八、总结与展望
通过本文的实战演练,我们完整地学习了如何使用Flutter for OpenHarmony开发一个功能丰富的闹钟时钟应用。项目的核心要点包括:
- 模块化架构设计:合理的项目结构有助于代码维护和团队协作
- 状态管理:采用Provider模式实现应用状态的统一管理
- 本地存储:使用SharedPreferences实现数据的持久化
- 跨平台适配:Flutter的跨平台特性使得代码可以在鸿蒙设备上无缝运行
Flutter for OpenHarmony的生态正在快速发展,越来越多的Flutter插件已经完成鸿蒙化适配。未来,我们可以进一步扩展应用功能,如:
- 添加云同步功能
- 集成语音提醒
- 支持表盘样式自定义
- 增加健康数据联动
期待更多开发者加入Flutter for OpenHarmony的行列,共同推动跨平台技术的发展!
参考资料:
- Flutter官方文档:https://docs.flutter.dev
- OpenHarmony开发者文档:https://developer.harmonyos.com
- Flutter for OpenHarmony适配指南