🚀 从零到一:Flutter倒计时应用"圆时间"完整开发历程分享

13 阅读6分钟

一个集美观、实用、高度可定制于一体的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保存后无法正确返回首页
  • 页面间状态同步不完整
  • 核心组件功能缺失

解决方案:

  1. 重构导航逻辑
// 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; // 返回首页
    });
  }
}
  1. 完善状态管理
// 使用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个)
  • 创建后自动选中并关闭弹窗

核心实现:

  1. 自定义颜色管理
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;
  }
}
  1. 颜色选择器组件
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);
}

第四阶段:编辑功能重构

功能缺陷:

  • 编辑页面无法更改倒计时/纪念日模式
  • 时间选择器日期范围不正确
  • 缺少自定义颜色选择

重构亮点:

  1. 模式切换实现
// 动态模式选择
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;
            });
          }
        },
      ),
    ),
  ],
)
  1. 智能日期范围
// 根据模式动态设置日期范围
final firstDate = widget.isMemorial 
    ? DateTime(1900)  // 纪念日允许过去日期
    : DateTime.now(); // 倒计时只允许未来日期

final lastDate = DateTime.now().add(const Duration(days: 365 * 10));

第五阶段:设置页面功能完善

设计目标: 打造一个功能完整的应用设置中心,包含外观、功能、隐私、数据管理等。

核心功能实现:

  1. 字体大小调节
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),
      ),
    ),
  );
}
  1. 数据管理功能
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到完整功能,采用渐进式开发策略:

  1. 先实现核心功能
  2. 再完善用户体验
  3. 最后添加高级功能

🚀 项目成果

功能特性

  • 倒计时/纪念日:支持两种模式,满足不同需求
  • 自定义主题: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设计 #项目实战