今天我们将通过一个待办清单 App 的实战开发,综合运用 Flutter 的核心知识。待办清单作为经典的入门项目,涵盖了页面跳转、数据展示、基础交互等多个关键知识点,非常适合用来巩固前期学习的内容。
一、需求分析与页面设计
1. 核心需求拆解
待办清单 App 的核心目标是帮助用户管理日常任务,我们需要实现的核心功能包括:
- 展示所有待办任务列表(已完成 / 未完成分类)
- 查看单个任务的详细信息
- 添加新任务
- 编辑现有任务
- 标记任务完成状态
- 删除任务
- 基本设置功能(如主题切换开关、关于页面等)
2. 页面架构设计
基于上述需求,我们将设计三个核心页面:
- 首页(任务列表页) :作为 App 的入口,顶部显示标题和添加按钮,中间区域用列表展示所有任务,支持下拉刷新和左滑删除操作。列表项需显示任务标题、创建时间和完成状态标记。
- 详情页:点击列表项进入,展示任务的完整信息(标题、内容、创建时间、截止时间等),底部提供编辑和删除按钮。
- 设置页:通过首页的设置图标进入,包含开关组件(如是否开启通知)、关于我们入口、清理缓存按钮等。
3. 原型草图构思
首页采用 "AppBar+ListView" 的经典结构,AppBar 右侧放置 "+" 图标按钮用于添加任务;详情页使用 ScrollView 避免内容溢出,顶部用 AppBar 提供返回按钮;设置页采用 ListView.builder 构建设置项列表,每个设置项由图标、文字和操作组件(开关 / 箭头)组成。
二、项目架构搭建:目录划分
合理的目录结构能让项目更易于维护,我们采用以下划分方式:
1. pages 目录
存放所有完整页面,每个页面单独创建一个文件夹,包含对应的 dart 文件。例如:
- home_page:首页相关代码
- detail_page:详情页相关代码
- setting_page:设置页相关代码
2. widgets 目录
存放可复用的自定义组件,按功能分类创建子目录:
- common:通用组件(如自定义按钮、输入框)
- task:与任务相关的组件(如任务列表项、任务状态标签)
3. utils 目录
存放工具类和辅助方法:
- router.dart:路由管理工具
- date_utils.dart:日期处理工具
- storage_utils.dart:本地存储工具(下节课详细实现)
4. models 目录
存放数据模型类,采用 Dart 的类来定义数据结构:
dart
// models/task_model.dart
class Task {
final String id; // 任务唯一标识
String title; // 任务标题
String content; // 任务内容
DateTime createTime; // 创建时间
DateTime? deadline; // 截止时间(可选)
bool isCompleted; // 是否完成
Task({
required this.id,
required this.title,
this.content = '',
DateTime? createTime,
this.deadline,
this.isCompleted = false,
}) : createTime = createTime ?? DateTime.now();
}
三、核心工具类实现
1. 日期处理工具(utils/date_utils.dart)
dart
import 'package:intl/intl.dart';
class DateUtils {
/// 格式化日期为"yyyy-MM-dd"格式
static String formatDate(DateTime date) {
return DateFormat('yyyy-MM-dd').format(date);
}
/// 格式化日期为"yyyy-MM-dd HH:mm"格式
static String formatDateTime(DateTime date) {
return DateFormat('yyyy-MM-dd HH:mm').format(date);
}
/// 格式化日期为友好显示(如"今天 14:30"、"昨天 10:15")
static String formatFriendly(DateTime date) {
final now = DateTime.now();
final today = DateTime(now.year, now.month, now.day);
final yesterday = today.subtract(const Duration(days: 1));
final dateOnly = DateTime(date.year, date.month, date.day);
if (dateOnly == today) {
return '今天 ${DateFormat('HH:mm').format(date)}';
} else if (dateOnly == yesterday) {
return '昨天 ${DateFormat('HH:mm').format(date)}';
} else if (date.year == now.year) {
return DateFormat('MM-dd HH:mm').format(date);
} else {
return DateFormat('yyyy-MM-dd HH:mm').format(date);
}
}
}
使用前需要在pubspec.yaml
中添加依赖:
yaml
dependencies:
intl: ^0.20.2
2. 路由管理工具(utils/router.dart)
dart
import 'package:flutter/material.dart';
import '../pages/home_page.dart';
import '../pages/detail_page.dart';
import '../pages/setting_page.dart';
class Router {
// 路由名称常量
static const String home = '/home';
static const String detail = '/detail';
static const String setting = '/setting';
// 路由配置
static Map<String, WidgetBuilder> routes = {
home: (context) => const HomePage(),
setting: (context) => const SettingPage(),
// 详情页需要动态参数,在跳转时处理
};
// 生成路由
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case detail:
final args = settings.arguments as Map<String, dynamic>?;
return MaterialPageRoute(
builder: (context) =>
DetailPage(task: args?['task'], onSave: args?['onSave']),
);
default:
return MaterialPageRoute(
builder: (context) =>
const Scaffold(body: Center(child: Text('页面不存在'))),
);
}
}
}
3. 应用入口(main.dart)
dart
import 'pages/home_page.dart';
import 'package:flutter/material.dart' hide Router;
import 'utils/router.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '待办清单',
// 应用主题配置
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
// 配置全局文本样式
textTheme: const TextTheme(
bodyLarge: TextStyle(fontSize: 16),
bodyMedium: TextStyle(fontSize: 14),
titleLarge: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
),
// 关闭调试模式标识
debugShowCheckedModeBanner: false,
// 初始路由
initialRoute: Router.home,
// 路由表
routes: Router.routes,
// 生成动态路由
onGenerateRoute: Router.generateRoute,
// 首页
home: const HomePage(),
);
}
}
四、基础页面实现
1. 首页实现(pages/home_page.dart)
首页是整个 App 的核心入口,我们需要实现任务列表展示、添加任务按钮和设置入口。
dart
import 'package:flutter/material.dart';
import '../../widgets/task/task_item.dart';
import '../../models/task_model.dart';
import 'detail_page.dart';
import 'setting_page.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
// 模拟任务数据
final List<Task> _tasks = [
Task(
id: '1',
title: '学习Flutter',
content: '完成第16课的实战项目',
deadline: DateTime.now().add(const Duration(days: 1)),
),
Task(id: '2', title: '购买生活用品', content: '牛奶、面包、水果', isCompleted: true),
];
// 跳转到详情页
void _navigateToDetail({Task? task}) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailPage(
task: task,
onSave: (newTask) {
setState(() {
if (task == null) {
// 添加新任务
_tasks.add(newTask);
} else {
// 编辑现有任务
final index = _tasks.indexWhere((t) => t.id == task.id);
if (index != -1) {
_tasks[index] = newTask;
}
}
});
},
),
),
);
}
// 切换任务完成状态
void _toggleTaskStatus(String id) {
setState(() {
final task = _tasks.firstWhere((t) => t.id == id);
task.isCompleted = !task.isCompleted;
});
}
// 删除任务
void _deleteTask(String id) {
setState(() {
_tasks.removeWhere((t) => t.id == id);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('待办清单'),
actions: [
// 设置按钮
IconButton(
icon: const Icon(Icons.settings),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SettingPage()),
);
},
),
],
),
body: _tasks.isEmpty
? const Center(child: Text('暂无任务,添加一个吧!'))
: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: _tasks.length,
itemBuilder: (context, index) {
final task = _tasks[index];
return TaskItem(
task: task,
onTap: () => _navigateToDetail(task: task),
onStatusChanged: _toggleTaskStatus,
onDelete: _deleteTask,
);
},
),
// 添加任务按钮
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () => _navigateToDetail(),
),
);
}
}
2. 详情页实现(pages/detail_page.dart)
详情页负责任务的查看、编辑和删除功能,需要实现表单输入和数据回传。
dart
import 'package:flutter/material.dart' hide DateUtils;
import '../../models/task_model.dart';
import '../../utils/date_utils.dart';
class DetailPage extends StatefulWidget {
final Task? task;
final Function(Task) onSave;
const DetailPage({super.key, this.task, required this.onSave});
@override
State<DetailPage> createState() => _DetailPageState();
}
class _DetailPageState extends State<DetailPage> {
late final TextEditingController _titleController;
late final TextEditingController _contentController;
DateTime? _deadline;
bool _isCompleted = false;
@override
void initState() {
super.initState();
// 初始化表单数据(编辑模式)
if (widget.task != null) {
_titleController = TextEditingController(text: widget.task!.title);
_contentController = TextEditingController(text: widget.task!.content);
_deadline = widget.task!.deadline;
_isCompleted = widget.task!.isCompleted;
} else {
// 新增模式
_titleController = TextEditingController();
_contentController = TextEditingController();
}
}
@override
void dispose() {
_titleController.dispose();
_contentController.dispose();
super.dispose();
}
// 保存任务
void _saveTask() {
if (_titleController.text.isEmpty) {
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('请输入任务标题')));
return;
}
final task = Task(
id: widget.task?.id ?? DateTime.now().microsecondsSinceEpoch.toString(),
title: _titleController.text,
content: _contentController.text,
createTime: widget.task?.createTime ?? DateTime.now(),
deadline: _deadline,
isCompleted: _isCompleted,
);
widget.onSave(task);
Navigator.pop(context);
}
// 选择截止日期
Future<void> _selectDate() async {
final picked = await showDatePicker(
context: context,
initialDate: _deadline ?? DateTime.now(),
firstDate: DateTime.now(),
lastDate: DateTime.now().add(const Duration(days: 365)),
);
if (picked != null) {
setState(() => _deadline = picked);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.task == null ? '添加任务' : '编辑任务'),
actions: [
// 删除按钮(仅编辑模式显示)
if (widget.task != null)
IconButton(
icon: const Icon(Icons.delete, color: Colors.red),
onPressed: () {
Navigator.pop(context);
// 这里可以添加删除确认对话框
},
),
],
),
body: Padding(
padding: const EdgeInsets.all(16),
child: ListView(
children: [
// 任务标题输入
TextField(
controller: _titleController,
decoration: const InputDecoration(
labelText: '任务标题',
border: OutlineInputBorder(),
),
style: const TextStyle(fontSize: 18),
),
const SizedBox(height: 16),
// 任务内容输入
TextField(
controller: _contentController,
decoration: const InputDecoration(
labelText: '任务内容',
border: OutlineInputBorder(),
),
maxLines: 4,
),
const SizedBox(height: 16),
// 截止日期选择
ListTile(
title: const Text('截止日期'),
subtitle: Text(
_deadline != null ? DateUtils.formatDate(_deadline!) : '未设置',
),
trailing: const Icon(Icons.calendar_today),
onTap: _selectDate,
),
// 完成状态切换
SwitchListTile(
title: const Text('完成状态'),
value: _isCompleted,
onChanged: (value) => setState(() => _isCompleted = value),
),
const SizedBox(height: 20),
// 保存按钮
ElevatedButton(
onPressed: _saveTask,
child: const Padding(
padding: EdgeInsets.all(12),
child: Text('保存任务'),
),
),
],
),
),
);
}
}
3. 设置页实现(pages/setting_page.dart)
设置页提供 App 的基础配置选项,采用列表形式展示各个设置项。
dart
import 'package:flutter/material.dart';
class SettingPage extends StatelessWidget {
const SettingPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('设置')),
body: ListView(
children: [
// 通知设置
SwitchListTile(
title: const Text('开启通知提醒'),
value: true, // 模拟开启状态
onChanged: (value) {
// 这里将在状态管理部分实现实际逻辑
},
),
// 深色模式设置(下节课实现)
ListTile(
title: const Text('深色模式'),
trailing: const Icon(Icons.arrow_forward_ios),
onTap: () {
// 跳转到深色模式设置页面
},
),
// 关于我们
ListTile(
title: const Text('关于我们'),
trailing: const Icon(Icons.arrow_forward_ios),
onTap: () {
showAboutDialog(
context: context,
applicationName: '待办清单',
applicationVersion: '1.0.0',
children: [
const Padding(
padding: EdgeInsets.only(top: 16),
child: Text('一款简洁高效的任务管理工具,帮助你更好地规划生活和工作。'),
),
],
);
},
),
// 清理缓存
ListTile(
title: const Text('清理缓存'),
trailing: const Icon(Icons.delete),
onTap: () {
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('缓存已清理')));
},
),
],
),
);
}
}
五、自定义组件实现(widgets/task/task_item.dart)
实现任务列表项组件,提高代码复用性
dart
import 'package:flutter/material.dart' hide DateUtils;
import '../../models/task_model.dart';
import '../../utils/date_utils.dart';
class TaskItem extends StatelessWidget {
final Task task;
final VoidCallback onTap;
final Function(String) onStatusChanged;
final Function(String) onDelete;
const TaskItem({
super.key,
required this.task,
required this.onTap,
required this.onStatusChanged,
required this.onDelete,
});
@override
Widget build(BuildContext context) {
return Card(
elevation: 2,
margin: const EdgeInsets.only(bottom: 12),
child: ListTile(
onTap: onTap,
leading: Checkbox(
value: task.isCompleted,
onChanged: (value) => onStatusChanged(task.id),
),
title: Text(
task.title,
style: TextStyle(
decoration: task.isCompleted ? TextDecoration.lineThrough : null,
color: task.isCompleted ? Colors.grey : null,
),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
if (task.deadline != null)
Text(
'截止:${DateUtils.formatDate(task.deadline!)}',
style: const TextStyle(fontSize: 12, color: Colors.grey),
),
Text(
'创建:${DateUtils.formatDate(task.createTime)}',
style: const TextStyle(fontSize: 12, color: Colors.grey),
),
],
),
// 左滑删除功能
trailing: const Icon(Icons.arrow_forward_ios, size: 18),
onLongPress: () => onDelete(task.id),
),
);
}
}