去年我用 Flutter 做了个专注番茄 APP,一套代码同时支持 Android 和 iOS。今天分享完整开发过程,包括踩过的坑。

一、为什么选 Flutter

做之前对比了几个方案:
在这里插入图片描述

最后选 Flutter,因为:

一套代码,Android+iOS 同时上线
Dart 语言像 Java,有 Java 基础 2 天就能上手
热重载开发快,改代码秒看效果

二、环境搭建

我的环境:

Windows 11
Android Studio
Flutter 3.19
安装步骤:

# 1. 下载 Flutter SDK
git clone https://github.com/flutter/flutter.git -b stable

# 2. 配置环境变量
# 把 flutter/bin 加到 PATH

# 3. 检查环境
flutter doctor

踩坑:第一次运行 flutter doctor 提示 Android SDK 找不到,需要手动指定路径。

三、项目结构

focus_tomato/
├── lib/
│   ├── main.dart          # 入口
│   ├── screens/
│   │   └── home_screen.dart   # 主页面
│   ├── models/
│   │   └── timer_model.dart   # 数据模型
│   ├── providers/
│   │   └── timer_provider.dart # 状态管理
│   └── utils/
│       └── storage.dart       # 本地存储
├── pubspec.yaml           # 依赖
└── android/ / ios/        # 平台配置

四、核心代码

  1. 数据模型(timer_model.dart)
class TimerModel {
  int duration;      // 专注时长(秒)
  int remaining;     // 剩余时间
  bool isRunning;    // 是否运行中
  bool isBreak;      // 是否休息中

  TimerModel({
    this.duration = 25 * 60,  // 默认25分钟
    this.remaining = 25 * 60,
    this.isRunning = false,
    this.isBreak = false,
  });

  // 格式化显示 25\:00
  String get displayTime {
    int minutes = remaining ~/ 60;
    int seconds = remaining % 60;
    return '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
  }
}
  1. 状态管理(timer_provider.dart)
import 'dart:async';
import 'package:flutter/foundation.dart';

class TimerProvider extends ChangeNotifier {
  TimerModel _timer = TimerModel();
  Timer? _countdownTimer;

  TimerModel get timer => _timer;

  // 开始计时
  void start() {
    if (_timer.isRunning) return;
  
    _timer.isRunning = true;
    notifyListeners();
  
    _countdownTimer = Timer.periodic(Duration(seconds: 1), (timer) {
      if (_timer.remaining > 0) {
        _timer.remaining--;
        notifyListeners();
      } else {
        // 时间到
        complete();
      }
    });
  }

  // 暂停
  void pause() {
    _timer.isRunning = false;
    _countdownTimer?.cancel();
    notifyListeners();
  }

  // 重置
  void reset() {
    _timer.isRunning = false;
    _timer.remaining = _timer.duration;
    _countdownTimer?.cancel();
    notifyListeners();
  }

  // 完成
  void complete() {
    _timer.isRunning = false;
    _countdownTimer?.cancel();
    // 切换休息/专注
    _timer.isBreak = !_timer.isBreak;
    _timer.duration = _timer.isBreak ? 5 * 60 : 25 * 60;  // 休息5分钟,专注25分钟
    _timer.remaining = _timer.duration;
    notifyListeners();
  }

  @override
  void dispose() {
    _countdownTimer?.cancel();
    super.dispose();
  }
}
  1. 主页面(home_screen.dart)
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/timer_provider.dart';

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Color(0xFF1A1A2E),  // 深蓝背景
      body: Center(
        child: Consumer<TimerProvider>(
          builder: (context, provider, child) {
            return Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                // 状态标签
                Text(
                  provider.timer.isBreak ? '休息时间' : '专注时间',
                  style: TextStyle(
                    color: Colors.white70,
                    fontSize: 18,
                  ),
                ),
                SizedBox(height: 20),
              
                // 倒计时显示
                Text(
                  provider.timer.displayTime,
                  style: TextStyle(
                    color: Colors.white,
                    fontSize: 80,
                    fontWeight: FontWeight.bold,
                    fontFamily: 'monospace',
                  ),
                ),
                SizedBox(height: 40),
              
                // 控制按钮
                Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    _buildButton(
                      icon: provider.timer.isRunning ? Icons.pause : Icons.play_arrow,
                      onPressed: () {
                        if (provider.timer.isRunning) {
                          provider.pause();
                        } else {
                          provider.start();
                        }
                      },
                    ),
                    SizedBox(width: 20),
                    _buildButton(
                      icon: Icons.refresh,
                      onPressed: () => provider.reset(),
                    ),
                  ],
                ),
              ],
            );
          },
        ),
      ),
    );
  }

  Widget _buildButton({required IconData icon, required VoidCallback onPressed}) {
    return ElevatedButton(
      onPressed: onPressed,
      style: ElevatedButton.styleFrom(
        backgroundColor: Color(0xFF16213E),
        foregroundColor: Colors.white,
        padding: EdgeInsets.all(20),
        shape: CircleBorder(),
      ),
      child: Icon(icon, size: 32),
    );
  }
}
  1. 入口文件(main.dart)
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'providers/timer_provider.dart';
import 'screens/home_screen.dart';

void main() {
  runApp(FocusTomatoApp());
}

class FocusTomatoApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => TimerProvider(),
      child: MaterialApp(
        title: '专注番茄',
        debugShowCheckedModeBanner: false,
        theme: ThemeData(
          primarySwatch: Colors.blue,
          scaffoldBackgroundColor: Color(0xFF1A1A2E),
        ),
        home: HomeScreen(),
      ),
    );
  }
}
  1. 依赖配置(pubspec.yaml)
name: focus_tomato
description: 专注番茄钟APP

publish_to: 'none'

version: 1.0.0+1

environment:
  sdk: '>=3.0.0 <4.0.0'

dependencies:
  flutter:
    sdk: flutter
  provider: ^6.1.1
  shared_preferences: ^2.2.2  # 本地存储

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^3.0.1

flutter:
  uses-material-design: true

五、踩坑记录

坑1:Timer 内存泄漏
刚开始没取消 Timer,退出页面后还在后台跑。解决:在 dispose() 里 cancel()。

坑2:状态不更新
修改数据后页面没变化。解决:必须用 notifyListeners()。

坑3:iOS 打包失败
第一次打 iOS 包提示证书问题。解决:需要 Apple Developer 账号配置证书。

六、效果展示

界面很简单:

深蓝背景
大号倒计时
播放/暂停/重置按钮
专注25分钟 → 休息5分钟 → 循环

七、打包发布

# Android
flutter build apk --release

# iOS(需要 Mac)
flutter build ios --release

APK 大小:15MB 左右。

八、总结

用 Flutter 做番茄钟,2 天完成 MVP,1 周上线双平台。核心就 3 个文件,200 行代码搞定。

如果你也想做跨平台 APP,Flutter 是个不错的选择。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐