一、背景与动机
1.1 原有问题
home_page.dart 文件过于庞大(2000+ 行),包含了:
- 底部导航栏逻辑
- 文件列表完整实现
- 传输列表完整实现
- 设置页面入口
- 所有业务逻辑混在一起
问题:
- 可维护性差:修改任何功能都需要在巨大的文件中定位
- 可读性低:不同功能模块缺乏清晰边界
- 协作困难:多人修改同一文件容易冲突
1.2 重构目标
- 文件页面独立:
files_page.dart包含所有文件操作逻辑 - 传输页面独立:
transfers_page.dart包含所有传输任务逻辑 - 主页轻量化:
home_page.dart只负责导航容器和页面切换 - 清晰边界:每个页面只关注自己的职责
二、重构步骤
2.1 第一步:提取传输页面(2025-12-16 23:16)
创建 transfers_page.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../core/state/app_state.dart';
class TransfersPage extends StatefulWidget {
const TransfersPage({super.key});
@override
State<TransfersPage> createState() => _TransfersPageState();
}
class _TransfersPageState extends State<TransfersPage> {
int _currentTab = 0; // 0: 上传, 1: 下载
@override
Widget build(BuildContext context) {
final state = context.watch<AppState>();
return Scaffold(
appBar: AppBar(
title: const Text('传输管理'),
bottom: TabBar(
controller: _tabController,
tabs: const [
Tab(text: '上传'),
Tab(text: '下载'),
],
),
actions: [
// 清空按钮
IconButton(
icon: const Icon(Icons.delete_sweep),
onPressed: _showClearDialog,
),
],
),
body: TabBarView(
controller: _tabController,
children: [
_buildUploadList(state),
_buildDownloadList(state),
],
),
);
}
Widget _buildUploadList(AppState state) {
final uploads = state.transfers
.where((t) => t.type == TransferType.upload)
.toList();
if (uploads.isEmpty) {
return Center(child: Text('暂无上传任务'));
}
return ListView.builder(
itemCount: uploads.length,
itemBuilder: (context, index) {
final item = uploads[index];
return _buildTransferItem(item);
},
);
}
Widget _buildTransferItem(TransferItem item) {
return ListTile(
leading: _buildStatusIcon(item.status),
title: Text(item.name),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (item.size != null)
Text(_formatSize(item.size!)),
if (item.status == TransferStatus.running ||
item.status == TransferStatus.finishing)
LinearProgressIndicator(
value: item.status == TransferStatus.running
? item.progress
: null,
),
if (item.error != null)
Text(
item.error!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
),
trailing: _buildActions(item),
);
}
// ... 更多方法
}提取内容(~580 行):
- 完整的传输列表 UI
- 上传/下载任务分组
- 任务状态显示
- 取消/重试/清空功能
- 进度条显示
修改 home_page.dart
// 删除传输相关代码,替换为:
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int _currentIndex = 0;
final _pages = [
const FilesPage(), // 文件页面(待提取)
const TransfersPage(), // 传输页面(已提取)
const SettingsPage(), // 设置页面
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: _currentIndex,
children: _pages,
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (index) {
setState(() => _currentIndex = index);
},
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.folder),
label: '文件',
),
BottomNavigationBarItem(
icon: Icon(Icons.swap_vert),
label: '传输',
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: '设置',
),
],
),
);
}
}代码减少:home_page.dart 从 2000+ 行减少到 ~1400 行
2.2 第二步:添加标签切换动画(2025-12-16 23:53)
问题
传输页面的上传/下载标签切换时,内容切换生硬,没有动画过渡。
解决方案:PageController
class _TransfersPageState extends State<TransfersPage>
with SingleTickerProviderStateMixin {
late TabController _tabController;
late PageController _pageController;
@override
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
_pageController = PageController();
// 同步 TabBar 和 PageView
_tabController.addListener(() {
if (_tabController.indexIsChanging) {
_pageController.animateToPage(
_tabController.index,
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('传输管理'),
bottom: TabBar(
controller: _tabController,
tabs: const [
Tab(text: '上传'),
Tab(text: '下载'),
],
),
),
body: PageView(
controller: _pageController,
onPageChanged: (index) {
_tabController.animateTo(index);
},
children: [
_buildUploadList(),
_buildDownloadList(),
],
),
);
}
}效果:
- 点击 Tab 时,内容平滑滑动切换
- 支持左右滑动手势切换 Tab
- Tab 指示器自动跟随动画
2.3 第三步:提取文件页面(2025-12-18 00:10)
创建 files_page.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../core/state/app_state.dart';
class FilesPage extends StatefulWidget {
const FilesPage({super.key});
@override
State<FilesPage> createState() => _FilesPageState();
}
class _FilesPageState extends State<FilesPage> {
final Set<String> _selectedIds = {};
bool _isGridView = false;
@override
Widget build(BuildContext context) {
final state = context.watch<AppState>();
return Scaffold(
appBar: _buildAppBar(state),
body: _buildBody(state),
floatingActionButton: _buildFAB(state),
);
}
AppBar _buildAppBar(AppState state) {
if (_selectedIds.isNotEmpty) {
// 多选模式
return AppBar(
leading: IconButton(
icon: Icon(Icons.close),
onPressed: () {
setState(() => _selectedIds.clear());
},
),
title: Text('已选择 ${_selectedIds.length} 项'),
actions: [
IconButton(
icon: Icon(Icons.select_all),
onPressed: _selectAll,
),
IconButton(
icon: Icon(Icons.delete),
onPressed: _deleteSelected,
),
// ... 更多操作
],
);
}
// 正常模式
return AppBar(
title: Text(_getCurrentPathDisplay(state)),
leading: state.currentPath != '/'
? IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () => state.navigateUp(),
)
: null,
actions: [
IconButton(
icon: Icon(_isGridView ? Icons.view_list : Icons.grid_view),
onPressed: () {
setState(() => _isGridView = !_isGridView);
},
),
],
);
}
Widget _buildBody(AppState state) {
final files = state.currentFiles;
if (files.isEmpty && !state.isLoading) {
return _buildEmptyState(state);
}
return RefreshIndicator(
onRefresh: () => state.refreshFiles(),
child: _isGridView
? _buildGridView(files)
: _buildListView(files),
);
}
// ... 所有文件操作逻辑(~2400 行)
}提取内容(~2400 行):
- 完整的文件列表(列表/网格视图)
- 文件导航(进入文件夹、返回上级)
- 多选模式(全选、反选、批量操作)
- 文件操作(上传、下载、删除、重命名、移动、复制)
- 预览功能(图片、视频、PDF、文本)
- 系统文件夹处理(缩略图、游离文件)
- 初始化/解锁界面
最终的 home_page.dart
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int _currentIndex = 0;
static const _pages = [
FilesPage(),
TransfersPage(),
SettingsPage(),
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: _currentIndex,
children: _pages,
),
bottomNavigationBar: NavigationBar(
selectedIndex: _currentIndex,
onDestinationSelected: (index) {
setState(() => _currentIndex = index);
},
destinations: const [
NavigationDestination(
icon: Icon(Icons.folder_outlined),
selectedIcon: Icon(Icons.folder),
label: '文件',
),
NavigationDestination(
icon: Icon(Icons.swap_vert_outlined),
selectedIcon: Icon(Icons.swap_vert),
label: '传输',
),
NavigationDestination(
icon: Icon(Icons.settings_outlined),
selectedIcon: Icon(Icons.settings),
label: '设置',
),
],
),
);
}
}最终行数:home_page.dart 从 2000+ 行减少到 ~100 行
三、重构对比
3.1 文件结构
重构前:
lib/ui/├── home_page.dart (2000+ 行,包含所有逻辑)├── settings_page.dart└── ...重构后:
lib/ui/├── home_page.dart (~100 行,只负责导航)├── files_page.dart (~2400 行,文件管理)├── transfers_page.dart (~580 行,传输管理)├── settings_page.dart└── ...3.2 职责划分
| 组件 | 职责 | 行数 |
|---|---|---|
HomePage | 底部导航、页面切换 | ~100 |
FilesPage | 文件浏览、操作、预览 | ~2400 |
TransfersPage | 传输任务管理 | ~580 |
SettingsPage | 应用设置 | ~370 |
3.3 优势
- 可维护性:每个页面独立,修改互不影响
- 可读性:清晰的文件边界,易于理解
- 复用性:独立组件可以在其他地方复用
- 协作性:多人可以同时修改不同页面
- 测试性:可以单独测试每个页面组件
四、导航优化
4.1 使用 NavigationBar(Material 3)
// 旧版 BottomNavigationBar
BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (index) => setState(() => _currentIndex = index),
items: [...],
)
// 新版 NavigationBar(Material 3)
NavigationBar(
selectedIndex: _currentIndex,
onDestinationSelected: (index) => setState(() => _currentIndex = index),
destinations: [
NavigationDestination(
icon: Icon(Icons.folder_outlined),
selectedIcon: Icon(Icons.folder),
label: '文件',
),
// ...
],
)优势:
- Material 3 设计规范
- 更好的视觉效果(圆角、高亮)
- 区分选中/未选中图标
4.2 使用 IndexedStack
IndexedStack(
index: _currentIndex,
children: _pages,
)优势:
- 保持所有页面的状态(不会重新构建)
- 切换页面时不会丢失滚动位置
- 性能更好(只渲染当前页面)
五、文件变更清单
第一阶段(传输页面提取):
client/lib/ui/home_page.dart- 删除传输相关代码client/lib/ui/transfers_page.dart- 新建 传输页面
第二阶段(标签动画):
client/lib/ui/transfers_page.dart- 添加 PageController
第三阶段(文件页面提取):
client/lib/ui/home_page.dart- 简化为导航容器client/lib/ui/files_page.dart- 新建 文件页面
六、经验总结
6.1 组件化原则
- 单一职责:一个组件只做一件事
- 清晰边界:组件之间通过 Props 和 Callbacks 通信
- 状态提升:共享状态放在 Provider(AppState)
- 适度拆分:不要过度拆分(100 行以下的文件没必要再拆)
6.2 重构技巧
- 渐进式重构:先提取传输页面,再提取文件页面
- 保持功能完整:每一步重构后都要验证功能正常
- 使用 Git:每一步提交一次,方便回滚
- 测试先行:有自动化测试的情况下先写测试
6.3 何时重构
当文件满足以下条件时考虑重构:
- ✅ 文件超过 1000 行
- ✅ 包含多个不相关的功能
- ✅ 修改时需要大量滚动查找
- ✅ 多人协作频繁冲突
总结:通过三阶段重构,将庞大的 home_page.dart 拆分为职责清晰的独立组件,显著提升了代码的可维护性和可读性。