基础入门 Flutter for OpenHarmony:file_selector 文件选择详解

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
🎯 欢迎来到 Flutter for OpenHarmony 社区!本文将深入讲解 Flutter 中 file_selector 文件选择组件的使用方法,带你全面掌握在应用中选择单个文件或多个文件的功能。


一、file_selector 组件概述

在 Flutter 应用开发中,文件选择是一个非常常见的需求。比如用户需要上传头像、导入文档、批量处理图片等操作,都需要与设备的文件系统进行交互。不同操作系统(Android、iOS、Windows、macOS、OpenHarmony)的文件系统各不相同,API 也完全不同。如果为每个平台单独编写文件选择代码,工作量巨大且难以维护。

file_selector 就是为了解决这个问题而诞生的插件。它为开发者提供了一个统一的 API,屏蔽了不同平台的文件系统差异,让开发者可以用相同的代码在所有平台上实现文件选择功能。

📋 file_selector 组件特点

特点 说明
跨平台支持 支持 Android、iOS、Linux、macOS、Windows、OpenHarmony 六大平台
统一 API 不同平台使用相同的 API 进行文件操作,无需关心底层实现
文件类型过滤 支持按文件扩展名过滤选择,方便用户快速找到所需文件
多选支持 支持一次选择多个文件,提升用户体验
异步操作 所有文件操作都是异步的,不会阻塞 UI,保证流畅体验
类型安全 使用强类型的 XFile 对象封装文件信息,避免空指针错误

💡 核心价值:开发者只需要学习一套 API,就可以在所有平台上实现文件选择功能,大大提高了开发效率。


二、为什么需要 file_selector?

2.1 原生文件选择的痛点

在没有 file_selector 之前,开发者需要:

Android 平台:

java 复制代码
// 需要编写原生代码
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
startActivityForResult(intent, REQUEST_CODE);

iOS 平台:

swift 复制代码
// 需要编写 Swift/Objective-C 代码
let picker = UIImagePickerController()
picker.delegate = self
present(picker, animated: true)

OpenHarmony 平台:

typescript 复制代码
// 需要编写 ArkTS 代码
let documentPicker = new picker.DocumentViewPicker()
documentPicker.select(documentSelectOptions, (err, documentSelectResult) => {
  // 处理选择结果
})

2.2 file_selector 的优势

使用 file_selector 后,开发者只需要:

dart 复制代码
// 所有平台通用
const XTypeGroup typeGroup = XTypeGroup(
  label: '图片',
  extensions: ['jpg', 'png', 'gif'],
);
final XFile? file = await openFile(acceptedTypeGroups: [typeGroup]);

优势总结:

  • 开发效率高:一套代码适配所有平台
  • 维护成本低:底层实现由插件团队维护
  • 学习曲线平缓:只需掌握 Dart API,无需学习多门原生语言
  • Bug 修复快:底层 Bug 由插件团队统一修复

三、file_selector 核心概念

3.1 XFile - 文件对象

XFilefile_selector 和相关插件(如 image_picker)中广泛使用的文件对象,它是对系统文件的跨平台封装。

XFile 的核心属性:

属性 类型 说明 示例
name String 文件名 photo.jpg
path String 文件的完整路径 /storage/emulated/0/Download/photo.jpg
length() Future 获取文件大小(字节) await file.length()
readAsBytes() Future 读取文件为字节数组 await file.readAsBytes()
readAsString() Future 读取文件为字符串 await file.readAsString()
writeAsString() Future 写入字符串内容 await file.writeAsString('hello')
writeAsBytes() Future 写入字节数据 await file.writeAsBytes(bytes)

使用示例:

dart 复制代码
final XFile? file = await openFile();
if (file != null) {
  print('文件名: ${file.name}');
  print('文件路径: ${file.path}');
  
  // 读取文件大小
  final int size = await file.length();
  print('文件大小: ${size / 1024} KB');
  
  // 读取文件内容
  final String content = await file.readAsString();
  print('文件内容: $content');
}

3.2 XTypeGroup - 文件类型组

XTypeGroup 用于定义一组相关的文件类型,用于在文件选择对话框中显示过滤选项。

构造函数:

dart 复制代码
const XTypeGroup({
  required String label,    // 类型标签,如 "图片"、"文档"
  required List<String> extensions,  // 文件扩展名列表,如 ['jpg', 'png']
});

设计理念:

  • label:显示给用户看的友好名称
  • extensions:实际用于过滤的文件扩展名

不同平台的显示效果:

平台 显示方式
Windows 下拉菜单中显示为"图片"分组,展开后显示具体扩展名
macOS 将所有类型组的扩展名合并显示
Linux 类似 Windows,下拉菜单显示分组
OpenHarmony 类似 macOS,合并显示

最佳实践建议:

dart 复制代码
// ✅ 推荐:一个类型组包含相关类型
const XTypeGroup images = XTypeGroup(
  label: '图片文件',
  extensions: ['jpg', 'png', 'gif', 'jpeg', 'bmp', 'webp'],
);

// ❌ 不推荐:多个分散的类型组
const XTypeGroup jpg = XTypeGroup(label: 'JPG', extensions: ['jpg']);
const XTypeGroup png = XTypeGroup(label: 'PNG', extensions: ['png']);

四、OpenHarmony 平台适配说明

4.1 兼容性信息

本项目基于 file_selector@1.0.3 开发,适配 Flutter 3.27.5-ohos-1.2.0。

4.2 OpenHarmony 平台支持的功能

在 OpenHarmony 平台上,file_selector 目前只支持以下功能:

功能 方法 OpenHarmony 支持
打开单个文件 openFile() ✅ yes
打开多个文件 openFiles() ✅ yes
选择目录 getDirectoryPath() ❌ no
获取保存位置 getSaveLocation() ❌ no
选择多个目录 getDirectoryPaths() ❌ no

⚠️ 重要提示 :OpenHarmony 平台目前只支持 openFile()openFiles() 两个方法。选择目录、保存文件等功能尚未实现。

如果需要这些功能,可以:

  • 选择目录:使用 OpenHarmony 原生 API 或等待插件更新
  • 保存文件:使用 path_provider 获取应用目录,然后结合 dart:io 进行文件写入操作

4.3 OpenHarmony 文件路径特点

  • 路径分隔符使用 /(与 Linux/macOS 一致)
  • 应用沙盒机制限制了可访问的目录
  • 需要权限才能访问某些目录(如外部存储)

五、项目配置与安装

5.1 添加依赖配置

首先,需要在你的 Flutter 项目的 pubspec.yaml 文件中添加 file_selector 依赖。

打开项目根目录下的 pubspec.yaml 文件,找到 dependencies 部分,添加以下配置:

yaml 复制代码
dependencies:
  flutter:
    sdk: flutter

  # 添加 file_selector 依赖(OpenHarmony 适配版本)
  file_selector:
    git:
      url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
      path: "packages/file_selector/file_selector"

配置说明:

  • 使用 git 方式引用开源鸿蒙适配的 flutter_packages 仓库
  • url:指定 GitCode 托管的仓库地址
  • path:指定 file_selector 包的具体路径
  • 本项目基于 file_selector@1.0.3 开发,适配 Flutter 3.27.5-ohos-1.2.0

⚠️ 重要:对于 OpenHarmony 平台,必须使用 git 方式引用适配版本,不能直接使用 pub.dev 的版本号。

5.2 下载依赖

配置完成后,需要在项目根目录执行以下命令下载依赖:

bash 复制代码
flutter pub get

执行成功后,你会看到类似以下的输出:

复制代码
Running "flutter pub get" in my_cross_platform_app...
Resolving dependencies...
Got dependencies!

5.3 依赖自动配置说明

执行 flutter pub get 后,OpenHarmony 平台的依赖会自动配置到 ohos/entry/oh-package.json5 文件中。你不需要手动配置 OpenHarmony 端的依赖,Flutter 构建系统会自动处理。


六、file_selector 基础用法详解

6.1 导入包

在使用 file_selector 之前,首先需要导入相关包:

dart 复制代码
import 'package:file_selector/file_selector.dart';

导入内容说明:

  • openFileopenFiles - 打开文件的方法
  • XFileXTypeGroup - 核心类型定义

6.2 打开单个文件 - openFile()

这是最常用的功能,用于让用户选择一个文件。

方法签名:

dart 复制代码
Future<XFile?> openFile({
  List<XTypeGroup> acceptedTypeGroups = const [],
  String? initialDirectory,
  String? confirmButtonText,
})

参数详解:

参数 类型 必填 说明 示例
acceptedTypeGroups List 文件类型过滤器,不传则可选择所有类型 [XTypeGroup(label: '图片', extensions: ['jpg', 'png'])]
initialDirectory String? 打开对话框时显示的初始目录 '/storage/emulated/0/Download'
confirmButtonText String? 确认按钮的文字 '选择文件'

返回值:

  • 成功:返回 XFile 对象,包含文件名和路径信息
  • 用户取消:返回 null

完整示例:

dart 复制代码
// 示例 1:选择任意类型文件
final XFile? file = await openFile();
if (file != null) {
  print('选择了: ${file.name}');
}

// 示例 2:只选择图片文件
const XTypeGroup images = XTypeGroup(
  label: '图片文件',
  extensions: ['jpg', 'png', 'gif', 'jpeg'],
);
final XFile? imageFile = await openFile(
  acceptedTypeGroups: [images],
);
if (imageFile != null) {
  print('选择了图片: ${imageFile.name}');
}

// 示例 3:指定初始目录和按钮文字
final XFile? file = await openFile(
  initialDirectory: '/storage/emulated/0/Download',
  confirmButtonText: '导入',
);

使用场景:

  • 用户上传头像
  • 导入配置文件
  • 选择要打开的文档
  • 读取本地数据文件

6.3 打开多个文件 - openFiles()

当需要一次选择多个文件时使用此方法。

方法签名:

dart 复制代码
Future<List<XFile>> openFiles({
  List<XTypeGroup> acceptedTypeGroups = const [],
  String? initialDirectory,
  String? confirmButtonText,
})

参数详解:

openFile 相同

返回值:

  • 成功:返回 List<XFile>,包含所有选择的文件
  • 用户取消:返回空列表 []

完整示例:

dart 复制代码
// 示例 1:选择多个图片
const XTypeGroup images = XTypeGroup(
  label: '图片文件',
  extensions: ['jpg', 'png', 'gif'],
);
final List<XFile> files = await openFiles(
  acceptedTypeGroups: [images],
);

if (files.isNotEmpty) {
  print('选择了 ${files.length} 个文件:');
  for (var file in files) {
    print('  - ${file.name}');
  }
} else {
  print('用户取消了选择');
}

// 示例 2:选择所有类型的多个文件
final List<XFile> allFiles = await openFiles();

使用场景:

  • 批量上传图片
  • 导入多个配置文件
  • 选择多张照片
  • 批量处理文档

七、常用文件类型参考

7.1 图片文件

类型 扩展名 说明
常见图片 jpg, jpeg, png, gif, bmp, webp 大多数图片格式
高清图片 tif, tiff 用于印刷和出版
矢阵图 ico 图标文件

示例:

dart 复制代码
const XTypeGroup images = XTypeGroup(
  label: '图片文件',
  extensions: ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'],
);

7.2 文档文件

类型 扩展名 说明
文本文件 txt 纯文本文件
PDF 文档 pdf 便携式文档格式
Word 文档 doc, docx Microsoft Word 文档
Excel 表格 xls, xlsx, csv Microsoft Excel 表格
演示文稿 ppt, pptx Microsoft PowerPoint
Markdown md Markdown 文档

示例:

dart 复制代码
const XTypeGroup documents = XTypeGroup(
  label: '文档文件',
  extensions: ['txt', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'],
);

7.3 媒体文件

类型 扩展名 说明
视频文件 mp4, mov, avi, mkv, flv 常见视频格式
音频文件 mp3, wav, aac, flac, m4a 常见音频格式

示例:

dart 复制代码
const XTypeGroup videos = XTypeGroup(
  label: '视频文件',
  extensions: ['mp4', 'mov', 'avi', 'mkv'],
);

const XTypeGroup audios = XTypeGroup(
  label: '音频文件',
  extensions: ['mp3', 'wav', 'aac', 'flac', 'm4a'],
);

7.4 压缩包

类型 扩展名 说明
压缩包 zip, rar, 7z, tar, gz 各种压缩格式

示例:

dart 复制代码
const XTypeGroup archives = XTypeGroup(
  label: '压缩包',
  extensions: ['zip', 'rar', '7z', 'tar', 'gz'],
);

八、完整示例代码

下面是一个完整的示例应用,展示了 file_selector 在 OpenHarmony 平台上的用法:

dart 复制代码
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:file_selector/file_selector.dart';

void main() {
  runApp(const FileSelectorDemo());
}

class FileSelectorDemo extends StatelessWidget {
  const FileSelectorDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'File Selector 示例',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const FileSelectorPage(),
    );
  }
}

class FileSelectorPage extends StatefulWidget {
  const FileSelectorPage({super.key});

  @override
  State<FileSelectorPage> createState() => _FileSelectorPageState();
}

class _FileSelectorPageState extends State<FileSelectorPage> {
  XFile? _selectedFile;
  List<XFile> _selectedFiles = [];
  String? _error;

  Future<void> _openSingleFile() async {
    try {
      const XTypeGroup typeGroup = XTypeGroup(
        label: '图片文件',
        extensions: <String>['jpg', 'png', 'gif', 'jpeg'],
      );
      final XFile? file = await openFile(
        acceptedTypeGroups: <XTypeGroup>[typeGroup],
      );
      
      if (file != null) {
        setState(() {
          _selectedFile = file;
          _selectedFiles.clear();
          _error = null;
        });
      }
    } catch (e) {
      setState(() {
        _error = '选择文件失败: $e';
      });
    }
  }

  Future<void> _openMultipleFiles() async {
    try {
      const XTypeGroup typeGroup = XTypeGroup(
        label: '图片文件',
        extensions: <String>['jpg', 'png', 'gif'],
      );
      final List<XFile> files = await openFiles(
        acceptedTypeGroups: <XTypeGroup>[typeGroup],
      );
      
      if (files.isNotEmpty) {
        setState(() {
          _selectedFiles = files;
          _selectedFile = null;
          _error = null;
        });
      }
    } catch (e) {
      setState(() {
        _error = '选择多个文件失败: $e';
      });
    }
  }

  void _clearAll() {
    setState(() {
      _selectedFile = null;
      _selectedFiles = [];
      _error = null;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('File Selector 示例'),
        actions: [
          if (_selectedFile != null || _selectedFiles.isNotEmpty)
            IconButton(
              icon: const Icon(Icons.clear),
              onPressed: _clearAll,
              tooltip: '清除所有',
            ),
        ],
      ),
      body: Container(
        decoration: BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topCenter,
            end: Alignment.bottomCenter,
            colors: [
              Colors.blue.shade50,
              Colors.indigo.shade50,
            ],
          ),
        ),
        child: ListView(
          padding: const EdgeInsets.all(16),
          children: [
            // 文件选择卡片
            _buildSectionCard(
              title: '文件选择',
              icon: Icons.file_open,
              color: Colors.blue,
              description: '选择单个或多个文件,支持按类型过滤',
              child: Column(
                children: [
                  ElevatedButton.icon(
                    onPressed: _openSingleFile,
                    icon: const Icon(Icons.insert_drive_file),
                    label: const Text('选择单个文件'),
                  ),
                  const SizedBox(height: 8),
                  ElevatedButton.icon(
                    onPressed: _openMultipleFiles,
                    icon: const Icon(Icons.file_copy),
                    label: const Text('选择多个文件'),
                  ),
                ],
              ),
            ),

            const SizedBox(height: 16),

            // 显示错误信息
            if (_error != null)
              Container(
                padding: const EdgeInsets.all(12),
                decoration: BoxDecoration(
                  color: Colors.red.shade50,
                  borderRadius: BorderRadius.circular(8),
                  border: Border.all(color: Colors.red.shade200),
                ),
                child: Row(
                  children: [
                    const Icon(Icons.error, color: Colors.red),
                    const SizedBox(width: 8),
                    Expanded(
                      child: Text(
                        _error!,
                        style: const TextStyle(color: Colors.red),
                      ),
                    ),
                  ],
                ),
              ),

            const SizedBox(height: 16),

            // 显示选中的单个文件
            if (_selectedFile != null)
              _buildSectionCard(
                title: '选中的文件',
                icon: Icons.description,
                color: Colors.purple,
                description: '显示用户选择的单个文件的详细信息',
                child: Column(
                  children: [
                    const Icon(Icons.file_present, size: 48, color: Colors.purple),
                    const SizedBox(height: 12),
                    Text(
                      _selectedFile!.name,
                      style: const TextStyle(
                        fontSize: 18,
                        fontWeight: FontWeight.w600,
                      ),
                    ),
                    const SizedBox(height: 8),
                    Container(
                      padding: const EdgeInsets.all(12),
                      decoration: BoxDecoration(
                        color: Colors.grey.shade100,
                        borderRadius: BorderRadius.circular(8),
                      ),
                      child: Text(
                        _selectedFile!.path,
                        style: TextStyle(
                          fontSize: 12,
                          color: Colors.grey.shade700,
                          fontFamily: 'monospace',
                        ),
                        maxLines: 3,
                        overflow: TextOverflow.ellipsis,
                      ),
                    ),
                  ],
                ),
              ),

            // 显示选中的多个文件
            if (_selectedFiles.isNotEmpty)
              _buildSectionCard(
                title: '选中的文件 (${_selectedFiles.length})',
                icon: Icons.folder_zip,
                color: Colors.teal,
                description: '显示用户选择的多个文件的列表',
                child: ListView.builder(
                  shrinkWrap: true,
                  physics: const NeverScrollableScrollPhysics(),
                  itemCount: _selectedFiles.length,
                  itemBuilder: (context, index) {
                    return ListTile(
                      leading: const Icon(Icons.insert_drive_file),
                      title: Text(_selectedFiles[index].name),
                      subtitle: Text(
                        _selectedFiles[index].path,
                        style: TextStyle(
                          fontSize: 12,
                          color: Colors.grey.shade600,
                        ),
                        maxLines: 1,
                        overflow: TextOverflow.ellipsis,
                      ),
                      dense: true,
                    );
                  },
                ),
              ),

            const SizedBox(height: 32),
          ],
        ),
      ),
    );
  }

  Widget _buildSectionCard({
    required String title,
    required IconData icon,
    required Color color,
    required Widget child,
    required String description,
  }) {
    return Card(
      elevation: 2,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(16),
      ),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                Container(
                  padding: const EdgeInsets.all(8),
                  decoration: BoxDecoration(
                    color: color.withOpacity(0.1),
                    borderRadius: BorderRadius.circular(8),
                  ),
                  child: Icon(
                    icon,
                    color: color,
                    size: 24,
                  ),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        title,
                        style: const TextStyle(
                          fontSize: 18,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      const SizedBox(height: 2),
                      Text(
                        description,
                        style: TextStyle(
                          fontSize: 12,
                          color: Colors.grey.shade600,
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            ),
            const SizedBox(height: 16),
            child,
          ],
        ),
      ),
    );
  }
}

九、常见问题与最佳实践

9.1 常见问题

Q1: 为什么文件选择失败?

A: 可能的原因和解决方法:

原因 1:文件类型扩展名格式不正确

dart 复制代码
// ❌ 错误:格式不一致
extensions: ['jpg', '.png', 'JPEG']

// ✅ 正确:统一格式,不需要点号
extensions: ['jpg', 'png', 'jpeg']

原因 2:设备没有对应的文件管理器

  • OpenHarmony 设备需要确保有文件管理器应用
  • 某些模拟器可能不支持文件选择

原因 3:权限不足

  • 应用需要适当的存储权限
  • 某些目录可能需要特殊权限才能访问
Q2: 如何处理用户取消选择的情况?

A: 检查返回值并给出友好提示

dart 复制代码
final XFile? file = await openFile();

if (file == null) {
  // 用户取消了选择
  ScaffoldMessenger.of(context).showSnackBar(
    const SnackBar(content: Text('已取消选择')),
  );
  return;
}

// 继续处理选择的文件
print('选择了: ${file.name}');
Q3: 如何获取文件的大小?

A: 使用 XFile.length() 方法

dart 复制代码
final XFile? file = await openFile();
if (file != null) {
  final int size = await file.length();
  
  if (size < 1024) {
    print('文件大小: ${size} 字节');
  } else if (size < 1024 * 1024) {
    print('文件大小: ${size / 1024} KB');
  } else {
    print('文件大小: ${size / (1024 * 1024)} MB');
  }
}
Q4: 如何读取文件内容?

A: 根据文件类型使用不同的方法

dart 复制代码
final XFile? file = await openFile();
if (file != null) {
  // 如果是文本文件,可以直接读取为字符串
  if (file.path.endsWith('.txt') || file.path.endsWith('.md')) {
    final String content = await file.readAsString();
    print('文件内容: $content');
  } else {
    // 如果是二进制文件,读取为字节数组
    final Uint8List bytes = await file.readAsBytes();
    print('文件大小: ${bytes.length} 字节');
  }
}
Q5: OpenHarmony 平台支持哪些功能?

A: OpenHarmony 平台目前只支持

  • openFile() - 打开单个文件
  • openFiles() - 打开多个文件

不支持:

  • getDirectoryPath() - 选择目录
  • getSaveLocation() - 获取保存位置
  • getDirectoryPaths() - 选择多个目录
Q6: OpenHarmony 平台如何保存文件?

A: OpenHarmony 平台不支持 getSaveLocation(),需要使用 path_provider 获取应用目录,然后用 dart:io 写入文件。

dart 复制代码
import 'package:path_provider/path_provider.dart';
import 'dart:io';

Future<void> _saveFile(String content) async {
  try {
    // 获取应用文档目录
    final Directory appDocDir = await getApplicationDocumentsDirectory();
    final String filePath = '${appDocDir.path}/my_file.txt';
    
    // 写入文件
    final File file = File(filePath);
    await file.writeAsString(content);
    
    print('文件已保存到: $filePath');
  } catch (e) {
    print('保存文件失败: $e');
  }
}

9.2 最佳实践

1. 始终处理异常

文件操作涉及与系统交互,总是有失败的可能。良好的错误处理是必须的。

dart 复制代码
Future<void> _handleFileSelection() async {
  try {
    final XFile? file = await openFile(
      acceptedTypeGroups: [typeGroup],
    );
    
    if (file != null) {
      setState(() {
        _selectedFile = file;
        _error = null;
      });
    } else {
      setState(() {
        _error = '用户取消了选择';
      });
    }
  } catch (e) {
    setState(() {
      _error = '文件选择失败: $e';
    });
    // 记录错误日志用于调试
    print('文件选择异常: $e');
  }
}
2. 检查返回值
dart 复制代码
// ❌ 错误:直接使用可能导致空指针异常
final XFile? file = await openFile();
print(file.name);  // 如果 file 为 null 会报错

// ✅ 正确:先检查返回值
final XFile? file = await openFile();
if (file != null) {
  print('选择了文件: ${file.name}');
} else {
  print('用户取消了选择');
}
3. 提供合理的文件类型过滤

原则:

  • 不要过滤得太严格,让用户无法找到文件
  • 不要过滤得太宽松,让用户选择到不合适的文件
  • 根据实际业务需求设置合理的过滤条件

示例:

dart 复制代码
// ✅ 推荐:适度的过滤
const XTypeGroup images = XTypeGroup(
  label: '图片文件',
  extensions: ['jpg', 'png', 'gif', 'jpeg', 'bmp', 'webp'],
);

// ❌ 不推荐:只支持一种格式
const XTypeGroup jpgOnly = XTypeGroup(
  label: 'JPG 图片',
  extensions: ['jpg'],
);

// ❌ 不推荐:过滤范围太宽
const XTypeGroup allFiles = XTypeGroup(
  label: '所有文件',
  extensions: ['*'],
);
4. 使用异步操作避免阻塞 UI
dart 复制代码
// ❌ 错误:在主线程进行耗时操作
void _openFileWrong() {
  openFile().then((file) {
    setState(() {
      _selectedFile = file;
    });
  });
}

// ✅ 正确:使用 async/await 语法
Future<void> _openFileCorrect() async {
  try {
    final XFile? file = await openFile();
    setState(() {
      _selectedFile = file;
    });
  } catch (e) {
    print('错误: $e');
  }
}
5. 提供用户友好的提示
dart 复制代码
Future<void> _loadFile() async {
  try {
    final XFile? file = await openFile(
      acceptedTypeGroups: [textFiles],
    );
    
    if (file != null) {
      final String content = await file.readAsString();
      
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text('文件加载成功: ${file.name}'),
          duration: const Duration(seconds: 3),
        ),
      );
    }
  } catch (e) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text('加载失败: $e'),
        backgroundColor: Colors.red,
      ),
    );
  }
}

十、API 参考速查表

11.1 OpenHarmony 平台支持的方法

方法 用途 返回类型 OpenHarmony 支持
openFile() 打开单个文件 Future<XFile?> ✅ yes
openFiles() 打开多个文件 Future<List<XFile>> ✅ yes
getDirectoryPath() 选择单个目录 Future<String?> ❌ no
getSaveLocation() 获取保存位置 Future<FileSaveLocation?> ❌ no
getDirectoryPaths() 选择多个目录 Future<List<String?>> ❌ no

11.2 常用文件类型速查表

用途 推荐的扩展名
通用图片 jpg, jpeg, png, gif, webp, bmp
文档 txt, pdf, doc, docx, xls, xlsx
视频 mp4, mov, avi, mkv
音频 mp3, wav, aac, flac, m4a
压缩包 zip, rar, 7z, tar, gz

相关推荐
左手厨刀右手茼蒿2 小时前
Flutter for OpenHarmony 实战:DartX — 极致简练的开发超能力集
android·flutter·ui·华为·harmonyos
空白诗2 小时前
基础入门 Flutter for OpenHarmony:TabBar 标签栏组件详解
flutter·harmonyos
早點睡3902 小时前
基础入门 Flutter for OpenHarmony:RefreshIndicator 下拉刷新详解
flutter·harmonyos
哈__2 小时前
基础入门 Flutter for OpenHarmony:path_provider 目录路径获取详解
flutter
不爱吃糖的程序媛2 小时前
Flutter-OH 3.35.7 环境配置与插件开发指南
flutter
空白诗2 小时前
基础入门 Flutter for OpenHarmony:Chip 标签组件详解
flutter·harmonyos
加农炮手Jinx2 小时前
Flutter for OpenHarmony 实战:Injectable — 自动化依赖注入大师
网络·flutter·华为·harmonyos·鸿蒙
空白诗2 小时前
基础入门 Flutter for OpenHarmony:Stack 堆叠布局详解
flutter·harmonyos
空白诗2 小时前
基础入门 Flutter for OpenHarmony:Slider 滑块组件详解
flutter·harmonyos