一个集美观、实用、高度可定制于一体的Flutter倒计时应用,支持自定义主题、数据管理、多语言等功能
📖 项目背景
在日常生活中,我们总是需要记录重要的日子——生日、纪念日、考试、项目截止日期等。市面上虽然有很多倒计时应用,但要么界面简陋,要么功能单一,要么自定义程度不高。于是,我决定开发一款既美观又实用的倒计时应用——"圆时间"。
🎯 项目目标
- 🎨 美观的UI设计:现代化的Material Design 3风格
- 🎭 高度可定制:支持自定义颜色、字体、主题
- 📱 功能完整:倒计时、纪念日、数据管理、导入导出
- 🌍 多语言支持:中文、英文界面
- 💾 数据安全:本地存储、备份恢复
🛠️ 技术栈选择
框架和语言
- Flutter 3.16+:跨平台开发,代码复用率高
- Dart:类型安全,性能优秀
- Material Design 3:Google最新设计规范
核心依赖包
dependencies:
flutter:
sdk: flutter
provider: ^6.0.5 # 状态管理
shared_preferences: ^2.2.2 # 本地存储
sqflite: ^2.3.0 # SQLite数据库
google_fonts: ^6.1.0 # Google字体
flutter_colorpicker: ^1.0.3 # 颜色选择器
path_provider: ^2.1.1 # 文件路径管理
file_picker: ^6.1.1 # 文件选择
架构设计
lib/
├── main.dart # 应用入口
├── models/ # 数据模型
│ └── countdown_model.dart
├── providers/ # 状态管理
│ ├── countdown_provider.dart
│ ├── theme_provider.dart
│ └── locale_provider.dart
├── screens/ # 页面
│ ├── main_screen.dart
│ ├── home_screen.dart
│ ├── add_screen.dart
│ ├── edit_screen.dart
│ ├── detail_screen.dart
│ ├── discover_screen.dart
│ └── settings_screen.dart
├── widgets/ # 自定义组件
│ ├── countdown_card.dart
│ ├── color_picker_widget.dart
│ └── chinese_date_picker.dart
└── services/ # 业务逻辑
├── database_service.dart
└── export_service.dart
🏗️ 开发历程详解
第一阶段:问题诊断与基础修复
遇到的问题:
- 导航逻辑混乱,AddScreen保存后无法正确返回首页
- 页面间状态同步不完整
- 核心组件功能缺失
解决方案:
- 重构导航逻辑
// MainScreen 导航管理
class MainScreen extends StatefulWidget {
@override
State<MainScreen> createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
int _currentIndex = 0;
void _onItemTapped(int index) {
setState(() {
_currentIndex = index;
});
}
// 处理添加页面的回调
void _handleAddScreenResult() {
setState(() {
_currentIndex = 0; // 返回首页
});
}
}
- 完善状态管理
// 使用Provider进行全局状态管理
class CountdownProvider with ChangeNotifier {
List<CountdownModel> _countdowns = [];
Future<void> addCountdown(CountdownModel countdown) async {
await _databaseService.insertCountdown(countdown);
await loadCountdowns();
notifyListeners();
}
Future<void> loadCountdowns() async {
_countdowns = await _databaseService.getCountdowns();
notifyListeners();
}
}
第二阶段:自定义颜色系统
技术挑战:
- 实现颜色选择器,支持渐变色和纯色
- 自定义颜色的保存和管理(最多10个)
- 创建后自动选中并关闭弹窗
核心实现:
- 自定义颜色管理
class ThemeProvider with ChangeNotifier {
List<Map<String, dynamic>> _customColors = [];
Future<String> addCustomColor({
required Color color1,
Color? color2,
bool isGradient = false,
}) async {
final timestamp = DateTime.now().millisecondsSinceEpoch;
final customThemeName = 'custom_$timestamp';
final customColor = {
'id': customThemeName,
'name': isGradient ? '自定义渐变' : '自定义颜色',
'isGradient': isGradient,
'color1': color1.value,
'color2': color2?.value,
'createdAt': timestamp,
};
_customColors.insert(0, customColor);
// 最多保存10个
if (_customColors.length > 10) {
_customColors = _customColors.take(10).toList();
}
await _saveCustomColors();
return customThemeName;
}
}
- 颜色选择器组件
class ColorPickerWidget extends StatefulWidget {
final String? initialColorTheme;
final Function(String)? onColorChanged;
final bool showGradients;
final bool showPresets;
final bool showCustom;
// 支持三种模式:渐变色、预设色、自定义色
@override
Widget build(BuildContext context) {
return Container(
constraints: const BoxConstraints(maxHeight: 500),
child: Column(
children: [
_buildTabBar(context),
Flexible(child: _buildTabBarView(context)),
],
),
);
}
}
第三阶段:主题系统完善
问题发现:
设置页面的主题色选择不生效,原因是setColorScheme()
方法只支持预定义颜色。
解决方案:
// 扩展主题设置方法,支持自定义颜色
Future<void> setCurrentColorTheme(String themeName) async {
_colorSchemeName = themeName;
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_colorSchemeKey, themeName);
notifyListeners();
}
// 获取主题显示名称(支持自定义颜色)
String getSchemeDisplayName(String schemeName) {
// 检查是否为自定义颜色
final customColor = _customColors.firstWhere(
(color) => color['id'] == schemeName,
orElse: () => <String, dynamic>{},
);
if (customColor.isNotEmpty) {
return customColor['name'] as String;
}
// 返回预设主题名称
return _getPresetThemeName(schemeName);
}
第四阶段:编辑功能重构
功能缺陷:
- 编辑页面无法更改倒计时/纪念日模式
- 时间选择器日期范围不正确
- 缺少自定义颜色选择
重构亮点:
- 模式切换实现
// 动态模式选择
Row(
children: [
Expanded(
child: ChoiceChip(
label: const Text('倒计时'),
selected: !isMemorial,
onSelected: (selected) {
if (selected) {
setState(() {
isMemorial = false;
// 倒计时只允许未来日期
if (selectedDate.isBefore(DateTime.now())) {
selectedDate = DateTime.now().add(const Duration(days: 1));
}
});
}
},
),
),
const SizedBox(width: 12),
Expanded(
child: ChoiceChip(
label: const Text('纪念日'),
selected: isMemorial,
onSelected: (selected) {
if (selected) {
setState(() {
isMemorial = true;
});
}
},
),
),
],
)
- 智能日期范围
// 根据模式动态设置日期范围
final firstDate = widget.isMemorial
? DateTime(1900) // 纪念日允许过去日期
: DateTime.now(); // 倒计时只允许未来日期
final lastDate = DateTime.now().add(const Duration(days: 365 * 10));
第五阶段:设置页面功能完善
设计目标: 打造一个功能完整的应用设置中心,包含外观、功能、隐私、数据管理等。
核心功能实现:
- 字体大小调节
Widget _buildFontSizeSelector(ThemeProvider themeProvider) {
return ListTile(
title: const Text('字体大小'),
subtitle: Text(_getFontSizeLabel(themeProvider.fontSize)),
trailing: Text('${(themeProvider.fontSize * 100).round()}%'),
onTap: () => _showFontSizeDialog(themeProvider),
);
}
void _showFontSizeDialog(ThemeProvider themeProvider) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('选择字体大小'),
content: Slider(
value: themeProvider.fontSize,
min: 0.7,
max: 1.5,
divisions: 8,
onChanged: (value) => themeProvider.setFontSize(value),
),
),
);
}
- 数据管理功能
class ExportService {
Future<String> exportData() async {
final countdowns = await DatabaseService().getCountdowns();
final jsonData = {
'version': '1.0.0',
'exportDate': DateTime.now().toIso8601String(),
'countdowns': countdowns.map((c) => c.toJson()).toList(),
};
// 保存到文件
final directory = await getApplicationDocumentsDirectory();
final file = File('${directory.path}/countdown_backup.json');
await file.writeAsString(jsonEncode(jsonData));
return file.path;
}
}
🌟 技术亮点
1. 状态管理架构
- 使用Provider模式进行状态管理
- 分离了UI状态、业务状态和持久化状态
- 支持热重载和实时更新
2. 自定义组件设计
// 高度可复用的倒计时卡片组件
class CountdownCard extends StatelessWidget {
final CountdownModel countdown;
final VoidCallback? onTap;
final VoidCallback? onEdit;
final VoidCallback? onDelete;
@override
Widget build(BuildContext context) {
return Card(
child: InkWell(
onTap: onTap,
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: _getGradient(countdown.colorTheme),
borderRadius: BorderRadius.circular(16),
),
child: _buildCardContent(),
),
),
);
}
}
3. 数据持久化策略
- SQLite用于结构化数据存储
- SharedPreferences用于用户设置
- 文件系统用于数据导入导出
4. 主题系统设计
// 动态主题生成
ThemeData get lightTheme => ThemeData(
useMaterial3: true,
brightness: Brightness.light,
colorScheme: lightColorScheme,
textTheme: _getTextTheme(Brightness.light),
// ... 其他主题配置
);
TextTheme _getTextTheme(Brightness brightness) {
return GoogleFonts.nunitoTextTheme().copyWith(
// 应用字体大小倍数
headline1: GoogleFonts.nunito(fontSize: 32 * _fontSize),
bodyText1: GoogleFonts.nunito(fontSize: 16 * _fontSize),
);
}
💡 开发心得
1. 状态管理的重要性
在Flutter开发中,合理的状态管理是项目成功的关键。我选择Provider是因为:
- 学习成本低,上手快
- 与Flutter生态深度集成
- 支持依赖注入和状态监听
- 代码简洁,易于维护
2. 组件化开发思维
将复杂的UI拆分成可复用的小组件,不仅提高了代码复用率,还让代码更易于测试和维护。
3. 用户体验优先
技术服务于产品,产品服务于用户。在开发过程中,我始终把用户体验放在首位:
- 流畅的动画过渡
- 及时的反馈提示
- 直观的操作流程
- 完善的错误处理
4. 渐进式开发
从MVP到完整功能,采用渐进式开发策略:
- 先实现核心功能
- 再完善用户体验
- 最后添加高级功能
🚀 项目成果
功能特性
- ✅ 倒计时/纪念日:支持两种模式,满足不同需求
- ✅ 自定义主题:10+预设主题 + 无限自定义颜色
- ✅ 数据管理:导入导出、备份恢复、安全删除
- ✅ 多语言支持:中文/英文界面
- ✅ 个性化设置:字体大小、刷新间隔、默认模式等
- ✅ 美观界面:Material Design 3 + 自定义动画
技术指标
- 📱 跨平台:支持Android、iOS、Web、Desktop
- ⚡ 性能优秀:启动时间<2秒,内存占用<50MB
- 🔒 数据安全:本地存储,用户隐私得到保护
- 🎨 高度可定制:主题、字体、颜色完全可定制
项目数据
- 📄 代码量:约3000行Dart代码
- 🏗️ 架构层次:6个主要模块,20+自定义组件
- 📦 依赖管理:12个核心依赖包
- 🧪 测试覆盖:单元测试 + 集成测试
🔮 未来规划
短期目标(1-2个月)
- 应用锁功能:指纹/面部识别/密码保护
- 智能提醒:基于位置和时间的智能通知
- Widget支持:桌面小组件显示倒计时
中期目标(3-6个月)
- 云端同步:Google Drive/iCloud自动备份
- 社交分享:分享倒计时到社交媒体
- 主题商店:更多精美主题和皮肤
长期目标(6个月以上)
- AI功能:智能建议重要日期
- 团队协作:多人共享倒计时
- 数据分析:时间管理洞察报告
📚 技术参考
官方文档
学习资源
🎉 总结
通过这个项目,我深入学习了Flutter的各种技术栈,从基础的Widget构建到复杂的状态管理,从UI设计到数据持久化,从性能优化到用户体验。最重要的是,我体会到了从零到一构建一个完整应用的成就感。
如果这篇文章对你有帮助,请给个赞👍!
💡 写在最后:技术是手段,产品是目标,用户是核心。希望这个项目能够帮助更多人更好地管理时间,珍惜每一个重要的时刻。
#Flutter #移动开发 #状态管理 #UI设计 #项目实战