【maaath】 Flutter for OpenHarmony 文件管理器应用开发实战
随着 Flutter for OpenHarmony 生态的持续发展,越来越多的 Flutter 应用成功适配到鸿蒙设备上。本文将通过一个完整的文件管理器应用实例,带领读者深入了解如何使用 Flutter 开发适用于 OpenHarmony 设备的跨平台应用,从项目架构设计到核心功能实现,全方位展示 Flutter 跨平台开发的魅力。文件管理器是移动设备中最常用的系统应用之一,也是展示跨平台开发能
Flutter for OpenHarmony 文件管理器应用开发实战
作者:maaath
社区引导信息
欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net
前言
随着 Flutter for OpenHarmony 生态的持续发展,越来越多的 Flutter 应用成功适配到鸿蒙设备上。本文将通过一个完整的文件管理器应用实例,带领读者深入了解如何使用 Flutter 开发适用于 OpenHarmony 设备的跨平台应用,从项目架构设计到核心功能实现,全方位展示 Flutter 跨平台开发的魅力。
一、项目概述
文件管理器是移动设备中最常用的系统应用之一,也是展示跨平台开发能力的经典案例。本文将基于 Flutter 框架,开发一个功能完备的文件管理器应用,涵盖文件浏览、搜索、复制移动删除、文件夹管理、排序切换、详情查看、存储统计和文件分类展示等核心功能。
1.1 技术栈
- 框架:Flutter 3.x for OpenHarmony
- 语言:Dart
- 状态管理: StatefulWidget(适合本应用复杂度)
- 架构模式:三层架构(UI层 / 业务层 / 数据层)
1.2 功能清单
| 功能模块 | 描述 |
|---|---|
| 文件浏览与导航 | 支持前进、后退、跳转父目录、面包屑导航 |
| 文件搜索与筛选 | 实时搜索文件、隐藏文件显示切换 |
| 文件复制/移动/删除 | 完整的文件操作支持 |
| 文件夹创建与管理 | 新建文件夹、重命名 |
| 文件排序 | 按名称、日期、大小、类型排序 |
| 文件详情查看 | 侧边栏显示文件属性 |
| 存储空间统计 | 存储概览页面 |
| 文件类型分类 | 图片、视频、文档等分类展示 |
二、项目结构设计
良好的项目结构是应用可维护性的基础。本文采用清晰的分层架构:
lib/
├── main.dart // 应用入口
├── models/ // 数据模型层
│ ├── file_item.dart // 文件/文件夹模型
│ └── storage_info.dart // 存储信息模型
├── services/ // 业务服务层
│ └── file_service.dart // 文件系统服务
├── screens/ // 页面层
│ └── file_manager_screen.dart // 主页面
├── widgets/ // 组件层
│ ├── file_list_item.dart // 文件列表项
│ ├── storage_card.dart // 存储卡片
│ ├── category_card.dart // 分类卡片
│ ├── file_detail_panel.dart // 详情面板
│ ├── breadcrumb_navigation.dart// 面包屑导航
│ ├── search_bar_widget.dart // 搜索组件
│ └── sort_options_sheet.dart // 排序选项
└── utils/ // 工具层
└── format_utils.dart // 格式化工具
这种分层结构的优势在于:
- 模型层:定义数据结构,与 UI 完全解耦
- 服务层:封装文件系统操作,便于测试和复用
- 组件层:原子化组件,提高复用性
- 页面层:组合组件,处理业务逻辑
三、核心模型设计
3.1 文件模型
文件模型是整个应用的基础数据结构,需要包含文件的所有关键属性:
enum FileItemType {
file,
directory,
}
enum SortType {
name,
date,
size,
type,
}
enum SortOrder {
ascending,
descending,
}
class FileItem {
final String name;
final String path;
final FileItemType type;
final int size;
final DateTime modifiedTime;
final DateTime createdTime;
final bool isHidden;
FileItem({
required this.name,
required this.path,
required this.type,
required this.size,
required this.modifiedTime,
required this.createdTime,
this.isHidden = false,
});
}
3.2 存储信息模型
用于展示设备存储状态:
class StorageInfo {
final int totalSpace;
final int usedSpace;
final int freeSpace;
double get usedPercentage {
if (totalSpace == 0) return 0;
return usedSpace / totalSpace;
}
}
四、文件系统服务实现
服务层是连接应用逻辑与系统底层的桥梁。以下是文件服务的核心实现:
class FileService {
static final FileService _instance = FileService._internal();
factory FileService() => _instance;
FileService._internal();
String _currentPath = '';
Future<List<FileItem>> listDirectory(String path) async {
final directory = Directory(path);
if (!await directory.exists()) {
throw Exception('Directory does not exist: $path');
}
_currentPath = path;
final items = <FileItem>[];
await for (final entity in directory.list(followLinks: false)) {
try {
final stat = await entity.stat();
final name = entity.path.split(Platform.pathSeparator).last;
items.add(FileItem(
name: name,
path: entity.path,
type: entity is Directory ? FileItemType.directory : FileItemType.file,
size: stat.size,
modifiedTime: stat.modified,
createdTime: stat.changed,
isHidden: name.startsWith('.'),
));
} catch (e) {
continue;
}
}
return items;
}
}
4.1 文件操作方法
服务层封装了常用的文件操作:
// 复制文件
Future<void> copyFile(String sourcePath, String destinationPath) async {
final sourceFile = File(sourcePath);
await sourceFile.copy(destinationPath);
}
// 移动文件
Future<void> moveFile(String sourcePath, String destinationPath) async {
final sourceFile = File(sourcePath);
await sourceFile.rename(destinationPath);
}
// 删除文件
Future<void> deleteFile(String path) async {
final file = File(path);
await file.delete();
}
// 创建目录
Future<void> createDirectory(String path) async {
final directory = Directory(path);
await directory.create(recursive: true);
}
4.2 文件搜索实现
搜索功能支持递归遍历目录:
Future<List<FileItem>> searchFiles(String query, {String? path}) async {
final searchPath = path ?? _currentPath;
final directory = Directory(searchPath);
final results = <FileItem>[];
await for (final entity in directory.list(recursive: true, followLinks: false)) {
final name = entity.path.split(Platform.pathSeparator).last;
if (name.toLowerCase().contains(query.toLowerCase())) {
final stat = await entity.stat();
results.add(FileItem(
name: name,
path: entity.path,
type: entity is Directory ? FileItemType.directory : FileItemType.file,
size: stat.size,
modifiedTime: stat.modified,
createdTime: stat.changed,
));
}
}
return results;
}
4.3 文件类型分类
根据扩展名将文件分类统计:
Future<List<FileTypeCategory>> getFileTypeCategories({String? path}) async {
final searchPath = path ?? _currentPath;
final directory = Directory(searchPath);
final categories = <String, List<FileItem>>{};
final categoryMapping = {
'Images': ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'],
'Videos': ['mp4', 'avi', 'mkv', 'mov', 'wmv'],
'Audio': ['mp3', 'wav', 'flac', 'aac', 'ogg'],
'Documents': ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'txt', 'md'],
'Archives': ['zip', 'rar', '7z', 'tar', 'gz'],
'Code': ['dart', 'java', 'py', 'js', 'ts', 'json', 'xml'],
};
await for (final entity in directory.list(recursive: true, followLinks: false)) {
if (entity is File) {
final name = entity.path.split(Platform.pathSeparator).last;
final ext = _getExtension(name);
final category = _getCategoryForExt(ext, categoryMapping);
categories.putIfAbsent(category, () => []).add(FileItem(...));
}
}
return categories.entries.map((entry) {
return FileTypeCategory(
name: entry.key,
count: entry.value.length,
totalSize: entry.value.fold(0, (sum, item) => sum + item.size),
);
}).toList();
}
五、UI 组件开发
5.1 文件列表项组件
采用简洁的列表设计,带有选中态反馈:
class FileListItem extends StatelessWidget {
final FileItem item;
final bool isSelected;
final VoidCallback onTap;
final VoidCallback onLongPress;
const FileListItem({
super.key,
required this.item,
required this.isSelected,
required this.onTap,
required this.onLongPress,
});
Widget build(BuildContext context) {
return Material(
color: isSelected
? Theme.of(context).colorScheme.primaryContainer.withOpacity(0.5)
: Colors.transparent,
child: InkWell(
onTap: onTap,
onLongPress: onLongPress,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(
children: [
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: item.iconColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(item.icon, color: item.iconColor, size: 24),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(item.name, style: const TextStyle(fontSize: 15)),
Text(
item.type == FileItemType.directory ? 'Folder' : item.displaySize,
style: TextStyle(fontSize: 13, color: Colors.grey[600]),
),
],
),
),
],
),
),
),
);
}
}
5.2 存储卡片组件
可视化展示存储空间使用情况:
class StorageCard extends StatelessWidget {
final StorageInfo storageInfo;
const StorageCard({super.key, required this.storageInfo});
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: [
Row(
children: [
const Icon(Icons.storage, size: 32),
const SizedBox(width: 16),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Storage', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
Text('${storageInfo.usedFormatted} / ${storageInfo.totalFormatted}'),
],
),
],
),
const SizedBox(height: 20),
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: LinearProgressIndicator(
value: storageInfo.usedPercentage,
minHeight: 8,
),
),
],
),
),
);
}
}
5.3 文件详情面板
侧边栏展示选中文件的完整信息:
class FileDetailPanel extends StatelessWidget {
final FileItem? selectedFile;
const FileDetailPanel({super.key, this.selectedFile});
Widget build(BuildContext context) {
if (selectedFile == null) {
return const Center(child: Text('Select a file to view details'));
}
return Container(
width: 300,
padding: const EdgeInsets.all(16),
child: Column(
children: [
Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: selectedFile!.iconColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
),
child: Icon(selectedFile!.icon, size: 50, color: selectedFile!.iconColor),
),
const SizedBox(height: 20),
Text(selectedFile!.name, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
const SizedBox(height: 24),
_buildInfoRow('Location', selectedFile!.path),
_buildInfoRow('Modified', FormatUtils.formatDate(selectedFile!.modifiedTime)),
_buildInfoRow('Created', FormatUtils.formatDate(selectedFile!.createdTime)),
],
),
);
}
Widget _buildInfoRow(String label, String value) {
return Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Row(
children: [
Text('$label: ', style: const TextStyle(color: Colors.grey)),
Expanded(child: Text(value)),
],
),
);
}
}
5.4 面包屑导航组件
支持快速跳转到任意父目录:
class BreadcrumbNavigation extends StatelessWidget {
final String currentPath;
final Function(String) onNavigate;
const BreadcrumbNavigation({
super.key,
required this.currentPath,
required this.onNavigate,
});
Widget build(BuildContext context) {
final parts = currentPath.split('/').where((p) => p.isNotEmpty).toList();
return ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: parts.length,
separatorBuilder: (_, __) => const Icon(Icons.chevron_right),
itemBuilder: (context, index) {
final path = '/${parts.sublist(0, index + 1).join('/')}';
final isLast = index == parts.length - 1;
return Center(
child: InkWell(
onTap: isLast ? null : () => onNavigate(path),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Text(
index == 0 ? 'Home' : parts[index],
style: TextStyle(
fontWeight: isLast ? FontWeight.bold : FontWeight.normal,
color: isLast ? Theme.of(context).colorScheme.primary : null,
),
),
),
),
);
},
);
}
}
六、主页面业务逻辑
主页面整合所有组件,处理核心业务逻辑:
class FileManagerScreen extends StatefulWidget {
const FileManagerScreen({super.key});
State<FileManagerScreen> createState() => _FileManagerScreenState();
}
class _FileManagerScreenState extends State<FileManagerScreen> {
final FileService _fileService = FileService();
String _currentPath = '/';
List<FileItem> _files = [];
List<FileItem> _filteredFiles = [];
bool _isLoading = true;
// 排序状态
SortType _sortType = SortType.name;
SortOrder _sortOrder = SortOrder.ascending;
// 搜索状态
String _searchQuery = '';
// 选中状态
Set<String> _selectedFiles = {};
FileItem? _selectedFile;
void initState() {
super.initState();
_loadDirectory(_currentPath);
}
void _applyFiltersAndSort() {
List<FileItem> result = List.from(_files);
// 搜索过滤
if (_searchQuery.isNotEmpty) {
result = result.where((f) =>
f.name.toLowerCase().contains(_searchQuery.toLowerCase())
).toList();
}
// 排序
result.sort((a, b) {
// 文件夹优先
if (a.type != b.type) {
return a.type == FileItemType.directory ? -1 : 1;
}
int comparison;
switch (_sortType) {
case SortType.name:
comparison = a.name.compareTo(b.name);
break;
case SortType.date:
comparison = a.modifiedTime.compareTo(b.modifiedTime);
break;
case SortType.size:
comparison = a.size.compareTo(b.size);
break;
case SortType.type:
comparison = a.extension.compareTo(b.extension);
break;
}
return _sortOrder == SortOrder.ascending ? comparison : -comparison;
});
_filteredFiles = result;
}
Future<void> _loadDirectory(String path) async {
setState(() => _isLoading = true);
try {
final files = await _fileService.listDirectory(path);
setState(() {
_currentPath = path;
_files = files;
_applyFiltersAndSort();
_isLoading = false;
});
} catch (e) {
setState(() => _isLoading = false);
}
}
void _onFileTap(FileItem item) {
if (_selectedFiles.isNotEmpty) {
_toggleSelection(item);
return;
}
if (item.type == FileItemType.directory) {
_loadDirectory(item.path);
} else {
setState(() => _selectedFile = item);
}
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('File Manager')),
body: _isLoading
? const Center(child: CircularProgressIndicator())
: ListView.builder(
itemCount: _filteredFiles.length,
itemBuilder: (context, index) {
final item = _filteredFiles[index];
return FileListItem(
item: item,
isSelected: _selectedFiles.contains(item.path),
onTap: () => _onFileTap(item),
onLongPress: () => _toggleSelection(item),
);
},
),
);
}
}
七、关键功能实现
7.1 批量选择与操作
长按进入多选模式,支持批量复制、移动、删除:
void _toggleSelection(FileItem item) {
setState(() {
if (_selectedFiles.contains(item.path)) {
_selectedFiles.remove(item.path);
} else {
_selectedFiles.add(item.path);
}
});
}
void _deleteSelected() async {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('Delete'),
content: Text('Delete ${_selectedFiles.length} items?'),
actions: [
TextButton(onPressed: () => Navigator.pop(context, false), child: const Text('Cancel')),
FilledButton(
onPressed: () => Navigator.pop(context, true),
style: FilledButton.styleFrom(backgroundColor: Colors.red),
child: const Text('Delete'),
),
],
),
);
if (confirmed == true) {
for (final path in _selectedFiles) {
await _fileService.deleteFile(path);
}
setState(() => _selectedFiles.clear());
_loadDirectory(_currentPath);
}
}
7.2 排序功能
支持多种排序方式和升降序切换:
void _showSortOptions() {
showModalBottomSheet(
context: context,
builder: (context) => SortOptionsSheet(
currentSortType: _sortType,
currentSortOrder: _sortOrder,
onSortChanged: (type, order) {
setState(() {
_sortType = type;
_sortOrder = order;
_applyFiltersAndSort();
});
},
),
);
}
7.3 新建文件夹
Future<void> _createFolder() async {
final nameController = TextEditingController();
final result = await showDialog<String>(
context: context,
builder: (context) => AlertDialog(
title: const Text('Create New Folder'),
content: TextField(
controller: nameController,
decoration: const InputDecoration(labelText: 'Folder Name'),
autofocus: true,
),
actions: [
TextButton(onPressed: () => Navigator.pop(context), child: const Text('Cancel')),
FilledButton(
onPressed: () => Navigator.pop(context, nameController.text),
child: const Text('Create'),
),
],
),
);
if (result != null && result.isNotEmpty) {
final newPath = '$_currentPath/${result}';
await _fileService.createDirectory(newPath);
_loadDirectory(_currentPath);
}
}
八、应用主题配置
采用 Material Design 3 设计语言,支持浅色/深色主题自动切换:
MaterialApp(
title: 'File Manager',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF6750A4),
brightness: Brightness.light,
),
useMaterial3: true,
),
darkTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF6750A4),
brightness: Brightness.dark,
),
useMaterial3: true,
),
themeMode: ThemeMode.system,
home: const FileManagerScreen(),
)
九、截图运行
以下为应用在鸿蒙设备上的运行截图:
9.1 主界面

9.3 文件创建页面

十、总结与展望
本文通过一个完整的文件管理器应用,详细介绍了 Flutter for OpenHarmony 开发的核心技术要点:
-
分层架构设计:清晰的模型层、服务层、组件层、页面层划分,提高了代码的可维护性和可测试性
-
文件系统操作:封装了文件浏览、搜索、复制、移动、删除等核心操作
-
丰富的 UI 组件:文件列表、存储卡片、分类卡片、详情面板等组件,支持灵活组合
-
完善的交互体验:多选模式、排序切换、面包屑导航等特性,提升了用户操作效率
10.1 可扩展方向
- 添加文件预览功能(图片缩略图、文本预览)
- 支持文件压缩与解压
- 实现云存储同步
- 增加文件加密功能
- 支持外接存储设备(USB、SD卡)
10.2 开源仓库
本文完整代码已开源至 AtomGit 平台:
仓库地址:https://atomgit.com/maaath/flutter_file_manager
更多推荐



所有评论(0)