【Flutter实战】开发专注番茄APP:跨平台番茄钟从0到1
·
去年我用 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/ # 平台配置
四、核心代码
- 数据模型(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')}';
}
}
- 状态管理(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();
}
}
- 主页面(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),
);
}
}
- 入口文件(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(),
),
);
}
}
- 依赖配置(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 是个不错的选择。
更多推荐
所有评论(0)