Flutter三方库适配OpenHarmony【habit_tracker】习惯追踪器项目完整实战
Flutter三方库适配OpenHarmony【habit_tracker】习惯追踪器项目完整实战
前言
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
habit_tracker 是一个基于 Flutter 的习惯追踪器项目,核心代码位于 lib/main.dart。项目默认展示 Exercise、Read、Meditate、Drink Water 四个习惯,每个习惯包含图标、名称、连续天数、一周 7 天打卡状态、周完成进度和删除按钮。用户可以点击每天的圆形按钮切换完成状态,也可以通过右下角 FloatingActionButton 打开新增习惯弹窗,输入习惯名称并选择图标后添加到列表。
这个项目适合讲解 Flutter 列表型工具应用在 OpenHarmony 上的适配过程。它覆盖了 数据模型 getter 派生状态、ListView.builder 动态渲染、GestureDetector 点击切换、AlertDialog 新增弹窗、StatefulBuilder 弹窗局部状态、LinearProgressIndicator 进度展示、FloatingActionButton 操作入口 和 Material 3 卡片布局。

图片说明:本文围绕 Flutter 动态列表、弹窗状态和 OpenHarmony 承载工程展开,所有关键代码均来自 habit_tracker 的真实源码。
习惯追踪器的关键不是“点亮一个圆点”,而是让模型、列表、进度、弹窗和状态刷新形成稳定闭环。
一、项目背景与目标
1.1 项目定位
habit_tracker 是一个轻量习惯打卡应用。页面以卡片列表展示多个习惯,每张卡片展示该习惯的一周完成情况。用户可以新增、删除习惯,也可以在每个习惯下方点击周一到周日的圆点切换完成状态。
当前项目真实支持的功能包括:
- 默认展示 4 个习惯。
- 每个习惯有名称、图标、周进度和连续天数。
- 每个习惯有 7 个布尔打卡状态。
- 点击任意一天圆点切换完成和未完成。
- 完成状态显示绿色圆点和白色 check 图标。
- 未完成状态显示浅灰圆点。
- 使用
completedDays统计本周完成天数。 - 使用
progress计算本周完成比例。 - 使用
LinearProgressIndicator展示每周进度。 - 使用删除按钮移除习惯。
- 使用
FloatingActionButton打开新增弹窗。 - 新增弹窗支持输入习惯名称。
- 新增弹窗支持从 10 个图标中选择。
- 弹窗内使用
StatefulBuilder刷新选中图标边框。
1.2 技术目标
本文围绕真实源码拆解以下内容:
- Flutter 应用入口和绿色 Material 3 主题。
Habit模型如何保存习惯名称、图标、周进度和连续天数。completedDays如何从weekProgress派生完成天数。progress如何计算 0 到 1 的进度值。_habits默认数据如何组织。_dayNames如何渲染一周标签。_toggleHabitDay如何切换某天打卡状态。_addHabit如何使用弹窗新增习惯。StatefulBuilder如何管理弹窗内部选中图标。ListView.builder如何渲染习惯卡片。- OpenHarmony 侧如何验证列表、弹窗、点击、进度和删除。
1.3 核心实现速览
| 能力 | 当前实现 | 适配关注点 |
|---|---|---|
| 应用入口 | runApp(const HabitTrackerApp()) |
确认首屏加载 |
| 主题 | ColorScheme.fromSeed(seedColor: Colors.green) |
确认绿色 Material 3 样式 |
| 数据模型 | Habit |
确认字段和 getter |
| 默认列表 | _habits |
确认 4 个默认习惯 |
| 一周状态 | List<bool> weekProgress |
确认 7 天打卡 |
| 打卡切换 | _toggleHabitDay |
确认点击刷新 |
| 新增习惯 | _addHabit + AlertDialog |
确认输入和图标选择 |
| 删除习惯 | _deleteHabit |
确认列表移除 |
| 周进度 | LinearProgressIndicator |
确认比例展示 |
| 弹窗局部状态 | StatefulBuilder |
确认选中图标边框 |
二、环境准备与工程结构
2.1 工程结构
项目保持 Flutter 标准结构,同时包含 OpenHarmony 平台工程。
| 文件或目录 | 作用 |
|---|---|
lib/main.dart |
应用入口、习惯模型、列表状态、弹窗和 UI |
pubspec.yaml |
SDK 约束、Flutter 依赖和 Material 图标配置 |
analysis_options.yaml |
Flutter lint 规则 |
test/ |
Flutter 测试目录 |
ohos/ |
OpenHarmony 平台承载工程 |
README.md |
项目说明文件 |
当前业务逻辑集中在 lib/main.dart,没有引入数据库、状态管理框架或持久化插件。
2.2 依赖配置
项目使用 Dart SDK ^3.9.2,依赖 Flutter SDK。
environment:
sdk: ^3.9.2
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.8
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
flutter:
uses-material-design: true
习惯列表、新增弹窗和进度展示全部由 Flutter Material 基础组件完成。
2.3 常用命令
flutter pub get
flutter analyze
flutter test
flutter run
| 命令 | 用途 |
|---|---|
flutter pub get |
获取依赖 |
flutter analyze |
执行静态分析 |
flutter test |
执行测试 |
flutter run |
在目标设备运行 |
OpenHarmony 调试时,还需要结合本地 Flutter OpenHarmony 工具链完成构建、安装和运行。
三、应用入口与主题配置
3.1 import 依赖
项目只引入 Flutter Material。
import 'package:flutter/material.dart';
material.dart 提供 MaterialApp、Scaffold、AppBar、Card、AlertDialog、FloatingActionButton、LinearProgressIndicator 等组件。
3.2 main 函数
入口函数启动根组件。
void main() {
runApp(const HabitTrackerApp());
}
3.3 HabitTrackerApp
根组件创建 MaterialApp。
class HabitTrackerApp extends StatelessWidget {
const HabitTrackerApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Habit Tracker',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.green),
useMaterial3: true,
),
home: const HabitTrackerHomePage(title: 'Habit Tracker'),
);
}
}
这段代码包含三个关键点:
- 应用标题为
Habit Tracker。 - 使用绿色作为主题种子色。
- 首页为
HabitTrackerHomePage。
四、Habit 数据模型
4.1 模型源码
项目定义了一个 Habit 类。
class Habit {
String name;
String icon;
List<bool> weekProgress;
int streak;
Habit({required this.name, required this.icon, this.streak = 0})
: weekProgress = List.generate(7, (_) => false);
int get completedDays => weekProgress.where((b) => b).length;
double get progress => completedDays / 7;
}
这个模型同时保存基础字段和派生进度。
4.2 字段说明
| 字段 | 类型 | 作用 |
|---|---|---|
name |
String |
习惯名称 |
icon |
String |
习惯图标文本 |
weekProgress |
List<bool> |
一周 7 天完成状态 |
streak |
int |
连续坚持天数 |
completedDays |
getter | 本周完成天数 |
progress |
getter | 本周完成比例 |
4.3 weekProgress 初始化
构造函数用 List.generate 创建 7 个 false。
Habit({required this.name, required this.icon, this.streak = 0})
: weekProgress = List.generate(7, (_) => false);
这表示新建习惯默认一周 7 天都未完成。
4.4 completedDays
完成天数从 weekProgress 中统计。
int get completedDays => weekProgress.where((b) => b).length;
如果一周中有 3 天为 true,completedDays 就是 3。
4.5 progress
周进度是完成天数除以 7。
double get progress => completedDays / 7;
LinearProgressIndicator 需要 0 到 1 的值,因此这个 getter 可以直接作为进度条输入。
五、默认习惯数据
5.1 _habits 列表
页面初始有 4 个习惯。
final List<Habit> _habits = [
Habit(name: 'Exercise', icon: '🏃', streak: 5),
Habit(name: 'Read', icon: '📚', streak: 3),
Habit(name: 'Meditate', icon: '🧘', streak: 7),
Habit(name: 'Drink Water', icon: '💧', streak: 2),
];
5.2 默认数据表
| 名称 | 图标 | streak |
|---|---|---|
| Exercise | 🏃 | 5 |
| Read | 📚 | 3 |
| Meditate | 🧘 | 7 |
| Drink Water | 💧 | 2 |
这 4 个习惯覆盖运动、阅读、冥想、喝水等常见习惯场景。
5.3 streak 的当前边界
当前 streak 是初始展示值。
int streak;
源码中的打卡切换只修改 weekProgress,不会自动重新计算 streak。因此 streak 更像是卡片展示字段,而不是根据当前周打卡动态推导的连续天数。
5.4 一周标签
项目定义了一周 7 天的简写。
final List<String> _dayNames = ['M', 'T', 'W', 'T', 'F', 'S', 'S'];
| 索引 | 文案 | 含义 |
|---|---|---|
| 0 | M | Monday |
| 1 | T | Tuesday |
| 2 | W | Wednesday |
| 3 | T | Thursday |
| 4 | F | Friday |
| 5 | S | Saturday |
| 6 | S | Sunday |
T 和 S 各出现两次,这是英文星期缩写的常见简化写法。
六、打卡切换逻辑
6.1 _toggleHabitDay
点击某一天时,项目切换对应布尔值。
void _toggleHabitDay(int habitIndex, int dayIndex) {
setState(() {
_habits[habitIndex].weekProgress[dayIndex] =
!_habits[habitIndex].weekProgress[dayIndex];
});
}
habitIndex 表示第几个习惯,dayIndex 表示一周中的第几天。
6.2 状态变化示例
weekProgress = [false, false, false, false, false, false, false]
点击 dayIndex = 0
weekProgress = [true, false, false, false, false, false, false]
再次点击同一天会切回 false。
6.3 UI 联动
一次打卡切换会影响三处 UI:
- 当天圆点颜色。
- 当天是否显示 check 图标。
- 底部周进度条和
x/7 days this week文案。
6.4 点击区域
每个日期单元使用 GestureDetector。
GestureDetector(
onTap: () => _toggleHabitDay(index, dayIndex),
child: Column(
children: [
Text(_dayNames[dayIndex]),
Container(width: 36, height: 36),
],
),
)
这让文字和圆点区域都可以作为点击入口。
七、删除习惯逻辑
7.1 _deleteHabit
删除方法直接从列表中移除指定索引。
void _deleteHabit(int index) {
setState(() {
_habits.removeAt(index);
});
}
ListView.builder 会根据新的 _habits.length 重新渲染列表。
7.2 删除按钮
卡片右上角使用红色删除图标。
IconButton(
onPressed: () => _deleteHabit(index),
icon: const Icon(Icons.delete, color: Colors.red),
)
7.3 删除后的状态
| 操作 | 列表变化 | 页面变化 |
|---|---|---|
| 删除第一个习惯 | _habits.removeAt(0) |
第一张卡片消失 |
| 删除最后一个习惯 | 列表长度减 1 | 底部列表收缩 |
| 删除全部习惯 | 列表为空 | 页面只剩 AppBar 和 FAB |
当前源码没有删除确认弹窗,点击删除会立即移除。
八、新增习惯弹窗
8.1 _addHabit 方法
新增习惯通过弹窗完成。
void _addHabit() async {
final nameController = TextEditingController();
String selectedIcon = '⭐';
await showDialog(
context: context,
builder: (context) => StatefulBuilder(
builder: (context, setDialogState) => AlertDialog(
title: const Text('Add New Habit'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
// 输入框和图标选择
],
),
actions: [
// Cancel 和 Add
],
),
),
);
}
nameController 管理弹窗输入,selectedIcon 管理当前选中的图标。
8.2 AlertDialog
弹窗标题是 Add New Habit。
AlertDialog(
title: const Text('Add New Habit'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [],
),
)
mainAxisSize: MainAxisSize.min 可以让弹窗内容按实际高度收缩。
8.3 名称输入框
弹窗内使用 TextField 输入习惯名称。
TextField(
controller: nameController,
decoration: const InputDecoration(
labelText: 'Habit Name',
border: OutlineInputBorder(),
),
)
如果输入为空,点击 Add 不会添加习惯。
九、弹窗图标选择
9.1 图标列表
弹窗内提供 10 个图标。
['⭐', '🏃', '📚', '💧', '🧘', '🍎', '😴', '✍️', '🎯', '💪']
这些图标覆盖星标、运动、阅读、喝水、冥想、饮食、睡眠、书写、目标和力量训练。
9.2 Wrap 布局
图标选择使用 Wrap。
Wrap(
spacing: 8,
children: ['⭐', '🏃', '📚', '💧', '🧘', '🍎', '😴', '✍️', '🎯', '💪'].map((icon) {
return GestureDetector(
onTap: () => setDialogState(() => selectedIcon = icon),
child: Container(
padding: const EdgeInsets.all(8),
),
);
}).toList(),
)
Wrap 可以在弹窗空间不足时自动换行。
9.3 StatefulBuilder
弹窗内部使用 StatefulBuilder。
StatefulBuilder(
builder: (context, setDialogState) => AlertDialog(
// 弹窗内容
),
)
点击图标时只需要刷新弹窗内部的选中状态,不必刷新整个页面。
9.4 选中边框
选中图标会显示蓝色边框。
border: Border.all(
color: selectedIcon == icon ? Colors.blue : Colors.transparent,
)
这给用户提供明确的选择反馈。
十、添加和取消动作
10.1 Cancel 按钮
取消按钮只关闭弹窗。
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Cancel'),
)
10.2 Add 按钮
添加按钮会校验名称非空。
ElevatedButton(
onPressed: () {
if (nameController.text.isNotEmpty) {
setState(() {
_habits.add(Habit(name: nameController.text, icon: selectedIcon));
});
Navigator.pop(context);
}
},
child: const Text('Add'),
)
10.3 新建 Habit
新增习惯只设置名称和图标。
Habit(name: nameController.text, icon: selectedIcon)
streak 使用默认值 0,weekProgress 自动初始化为 7 个 false。
10.4 新增流程表
| 步骤 | 操作 | 状态变化 |
|---|---|---|
| 1 | 点击 FAB | 打开弹窗 |
| 2 | 输入名称 | nameController.text 更新 |
| 3 | 选择图标 | selectedIcon 更新 |
| 4 | 点击 Add | _habits.add(...) |
| 5 | 关闭弹窗 | 列表显示新习惯 |
十一、列表渲染
11.1 ListView.builder
主体内容使用 ListView.builder。
body: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: _habits.length,
itemBuilder: (context, index) {
final habit = _habits[index];
return Card(
margin: const EdgeInsets.only(bottom: 12),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [],
),
),
);
},
)
动态列表适合习惯数量变化的场景。
11.2 卡片头部
每张卡片头部展示图标、名称、连续天数和删除按钮。
Row(
children: [
Text(habit.icon, style: const TextStyle(fontSize: 32)),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(habit.name),
Text('🔥 ${habit.streak} day streak'),
],
),
),
IconButton(
onPressed: () => _deleteHabit(index),
icon: const Icon(Icons.delete, color: Colors.red),
),
],
)
Expanded 保证习惯名称区域占据剩余空间,删除按钮固定在右侧。
11.3 连续天数展示
Text(
'🔥 ${habit.streak} day streak',
style: TextStyle(
color: Colors.orange.shade700,
fontSize: 12,
),
)
连续天数使用橙色强调,符合坚持、热度和成就感的视觉语义。
十二、七天打卡 UI
12.1 一周 Row
每张卡片中间使用 Row 展示 7 天。
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: List.generate(7, (dayIndex) {
return GestureDetector(
onTap: () => _toggleHabitDay(index, dayIndex),
child: Column(
children: [
Text(_dayNames[dayIndex]),
const SizedBox(height: 4),
Container(width: 36, height: 36),
],
),
);
}),
)
spaceAround 让 7 个日期均匀分布。
12.2 日期标签
Text(
_dayNames[dayIndex],
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.grey.shade600,
),
)
日期标签使用灰色,避免抢占打卡圆点的视觉重点。
12.3 圆点状态
Container(
width: 36,
height: 36,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: habit.weekProgress[dayIndex]
? Colors.green
: Colors.grey.shade200,
),
child: habit.weekProgress[dayIndex]
? const Icon(Icons.check, color: Colors.white, size: 20)
: null,
)
已完成状态为绿色圆点加白色 check,未完成状态为浅灰圆点。
十三、周进度展示
13.1 LinearProgressIndicator
卡片底部使用进度条。
LinearProgressIndicator(
value: habit.progress,
backgroundColor: Colors.grey.shade200,
valueColor: const AlwaysStoppedAnimation(Colors.green),
)
habit.progress 由 completedDays / 7 得到。
13.2 完成天数文案
Text(
'${habit.completedDays}/7 days this week',
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
),
)
用户可以同时看到进度条和具体完成天数。
13.3 进度示例
| 完成天数 | progress | 文案 |
|---|---|---|
| 0 | 0 | 0/7 days this week |
| 1 | 0.142857 | 1/7 days this week |
| 3 | 0.428571 | 3/7 days this week |
| 7 | 1 | 7/7 days this week |
进度条对小数值支持良好,不需要手动换算百分比。
十四、OpenHarmony 适配要点
14.1 基础组件验证
当前项目使用的 Flutter 组件包括:
| 组件 | 作用 | OpenHarmony 关注点 |
|---|---|---|
MaterialApp |
应用根组件 | 首屏加载 |
Scaffold |
页面骨架 | AppBar、Body、FAB |
ListView.builder |
习惯列表 | 滚动和动态刷新 |
Card |
习惯卡片 | 阴影、边距、圆角 |
GestureDetector |
打卡点击 | 点击响应 |
AlertDialog |
新增弹窗 | 弹窗布局 |
StatefulBuilder |
弹窗局部状态 | 图标选中刷新 |
FloatingActionButton |
新增入口 | 悬浮按钮点击 |
LinearProgressIndicator |
周进度 | 进度值展示 |
14.2 列表验证
OpenHarmony 上应重点验证:
- 默认 4 张卡片能完整显示。
- 列表可以纵向滚动。
- 删除任意习惯后列表立即刷新。
- 删除后剩余卡片索引不会错乱。
- 新增习惯后列表新增一张卡片。
14.3 打卡验证
打卡交互要覆盖:
- 点击周一圆点后变为绿色。
- 再次点击同一圆点后恢复灰色。
- 完成天数文案同步变化。
- 进度条同步变化。
- 多个习惯之间打卡状态互不影响。
14.4 弹窗验证
新增弹窗要验证:
- 点击 FAB 能打开弹窗。
- 输入框能正常输入习惯名称。
- 点击图标后蓝色边框切换。
- 点击 Cancel 关闭弹窗且不新增。
- 输入名称后点击 Add,列表新增习惯。
- 新增习惯 streak 为 0,周进度为空。
OpenHarmony 适配不能只看首屏。这个项目的重点在列表状态、弹窗局部状态和打卡状态三条链路。
十五、测试与验证
15.1 初始页面测试
Widget 测试可以验证默认习惯。
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('habit tracker shows default habits', (tester) async {
await tester.pumpWidget(const HabitTrackerApp());
expect(find.text('Habit Tracker'), findsWidgets);
expect(find.text('Exercise'), findsOneWidget);
expect(find.text('Read'), findsOneWidget);
expect(find.text('Meditate'), findsOneWidget);
expect(find.text('Drink Water'), findsOneWidget);
});
}
这类测试不依赖复杂交互,适合验证首屏数据。
15.2 打卡测试
可以点击第一张卡片的某个圆点,并观察完成文案。
testWidgets('tap habit day updates weekly progress', (tester) async {
await tester.pumpWidget(const HabitTrackerApp());
expect(find.text('0/7 days this week'), findsWidgets);
await tester.tap(find.byType(GestureDetector).first);
await tester.pump();
expect(find.text('1/7 days this week'), findsOneWidget);
});
实际测试中可以进一步收窄查找范围,避免误点其他 GestureDetector。
15.3 新增习惯测试
新增弹窗可以测试输入和添加。
testWidgets('add habit dialog creates new habit', (tester) async {
await tester.pumpWidget(const HabitTrackerApp());
await tester.tap(find.byType(FloatingActionButton));
await tester.pumpAndSettle();
await tester.enterText(find.byType(TextField), 'Sleep Early');
await tester.tap(find.text('Add'));
await tester.pumpAndSettle();
expect(find.text('Sleep Early'), findsOneWidget);
});
15.4 手工验证矩阵
| 场景 | 操作 | 预期 |
|---|---|---|
| 首次打开 | 启动应用 | 显示 4 个默认习惯 |
| 打卡 | 点击任意日期圆点 | 圆点变绿,进度更新 |
| 取消打卡 | 再点同一圆点 | 圆点变灰,进度回退 |
| 新增习惯 | 点击 FAB 并输入名称 | 列表新增习惯 |
| 图标选择 | 弹窗内点击图标 | 蓝色边框切换 |
| 删除习惯 | 点击删除按钮 | 对应卡片移除 |
| 空名称添加 | 弹窗不输入名称点击 Add | 不新增习惯 |
十六、常见问题与优化建议
16.1 为什么用 getter 计算完成天数
completedDays 是 weekProgress 的派生值。
int get completedDays => weekProgress.where((b) => b).length;
使用 getter 可以保证每次读取都是基于最新打卡状态计算,不需要额外维护一个计数字段。
16.2 为什么进度用 completedDays / 7
LinearProgressIndicator 接收 0 到 1 的进度值。
double get progress => completedDays / 7;
完成 7 天时值为 1,完成 0 天时值为 0。
16.3 为什么弹窗使用 StatefulBuilder
新增弹窗里的图标选择是局部状态。使用 StatefulBuilder 可以只刷新弹窗内容。
StatefulBuilder(
builder: (context, setDialogState) => AlertDialog(),
)
这比把弹窗拆成完整的独立 StatefulWidget 更轻量。
16.4 streak 为什么不会自动变化
当前 _toggleHabitDay 只修改 weekProgress。
_habits[habitIndex].weekProgress[dayIndex] =
!_habits[habitIndex].weekProgress[dayIndex];
它不会修改 streak。如果要让连续天数随打卡变化,需要定义连续天数计算规则,例如按日期、按自然周或按最近连续完成记录计算。
16.5 如何增加删除确认
当前删除是立即执行。可以增加确认弹窗。
Future<void> confirmDelete(int index) async {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('Delete Habit'),
content: const Text('Remove this habit from the list?'),
actions: [
TextButton(onPressed: () => Navigator.pop(context, false), child: const Text('Cancel')),
TextButton(onPressed: () => Navigator.pop(context, true), child: const Text('Delete')),
],
),
);
if (confirmed == true) {
_deleteHabit(index);
}
}
这能降低误删风险。
16.6 如何增加持久化
当前习惯数据只存在内存中,应用重启后会恢复默认列表。可以把习惯数据序列化成本地存储。
class HabitDto {
final String name;
final String icon;
final List<bool> weekProgress;
final int streak;
const HabitDto({
required this.name,
required this.icon,
required this.weekProgress,
required this.streak,
});
}
OpenHarmony 上实现持久化时,需要结合可用插件和平台存储能力。
十七、工程扩展方向
17.1 抽取 HabitCard
卡片代码可以拆成独立组件。
class HabitCard extends StatelessWidget {
final Habit habit;
final List<String> dayNames;
final void Function(int dayIndex) onToggleDay;
final VoidCallback onDelete;
const HabitCard({
super.key,
required this.habit,
required this.dayNames,
required this.onToggleDay,
required this.onDelete,
});
}
拆分后页面主体更清晰,也便于单独测试卡片。
17.2 抽取新增弹窗
新增弹窗也可以拆成组件或函数。
class AddHabitResult {
final String name;
final String icon;
const AddHabitResult({
required this.name,
required this.icon,
});
}
弹窗返回 AddHabitResult,页面负责添加到列表。
17.3 增加日期维度
当前 weekProgress 只表示一周 7 个布尔值,不包含真实日期。正式习惯应用通常需要保存日期。
class HabitCompletion {
final DateTime date;
final bool completed;
const HabitCompletion({
required this.date,
required this.completed,
});
}
加入日期后,可以跨周统计、计算连续天数和生成月历视图。
17.4 增加统计看板
可以基于当前数据增加统计区域。
int totalCompletedDays(List<Habit> habits) {
return habits.fold(0, (sum, habit) => sum + habit.completedDays);
}
统计看板可以显示总完成天数、平均完成率、最高 streak 等指标。
总结
habit_tracker 是一个完整的 Flutter 习惯追踪器案例。它用 Habit 模型保存名称、图标、周进度和连续天数,用 completedDays 和 progress 从 weekProgress 派生完成天数和进度值,用 ListView.builder 渲染动态习惯列表,用 GestureDetector 切换每天打卡状态,用 LinearProgressIndicator 展示本周完成比例,并通过 AlertDialog 和 StatefulBuilder 实现新增习惯弹窗。
从 OpenHarmony 适配角度看,这个项目适合验证 Flutter 动态列表、卡片布局、弹窗输入、弹窗局部刷新、图标文本、圆形打卡按钮、进度条和删除操作。排查路径也很清晰:列表不刷新看 _habits 和 setState,进度不对看 weekProgress,弹窗选择不刷新看 StatefulBuilder,删除异常看列表索引。
掌握这个项目后,可以继续扩展删除确认、数据持久化、日期维度、连续天数自动计算、统计看板和月历视图,让习惯追踪器从一周打卡 Demo 演进为更完整的跨平台习惯管理工具。
如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!
相关资源:
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)