Flutter文件选择插件file_picker_cross的使用

发布于 1周前 作者 sinazl 来自 Flutter

Flutter文件选择插件file_picker_cross的使用

file_picker_cross 插件允许你在Android、iOS、桌面端(使用go-flutter或FDE)和Web上选择、打开、编辑、保存文档、图片、视频或其他类型的文件。它为读取、写入、将文件作为字符串、字节列表或HTTP上传提供了统一的接口。

开始使用

首先,确保你已经在你的项目中添加了file_picker_cross依赖项。在你的pubspec.yaml文件中添加以下依赖:

dependencies:
  file_picker_cross: ^版本号

然后运行flutter pub get来获取新的依赖项。

示例代码

以下是一个完整的示例代码,展示了如何使用file_picker_cross插件进行文件选择、保存和分享操作。

import 'package:file_picker_cross/file_picker_cross.dart';
import 'package:flutter/material.dart';

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

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final GlobalKey exportKey = GlobalKey();
  FilePickerCross? filePickerCross;

  String _fileString = '';
  Set<String?>? lastFiles;
  FileQuotaCross quota = FileQuotaCross(quota: 0, usage: 0);

  @override
  void initState() {
    FilePickerCross.listInternalFiles()
        .then((value) => setState(() => lastFiles = value.toSet()));
    FilePickerCross.quota().then((value) => setState(() => quota = value));
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
          colorScheme: ColorScheme.fromSwatch().copyWith(
              primary: Colors.blueGrey, secondary: Colors.lightGreen)),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: ListView(
          padding: const EdgeInsets.all(8),
          children: <Widget>[
            Text(
              'Last files',
              style: Theme.of(context).textTheme.headline5,
            ),
            (lastFiles == null)
                ? const Center(
                    child: CircularProgressIndicator(),
                  )
                : ListView.builder(
                    shrinkWrap: true,
                    primary: false,
                    physics: const NeverScrollableScrollPhysics(),
                    itemBuilder: (context, index) => ListTile(
                      leading: Text('$index.'),
                      title: Text(lastFiles!.toList()[index]!),
                      onTap: () async => setFilePicker(
                          await FilePickerCross.fromInternalPath(
                              path: lastFiles!.toList()[index]!)),
                    ),
                    itemCount: lastFiles!.length,
                  ),
            Builder(
              builder: (context) => ElevatedButton(
                onPressed: () => _selectFile(context),
                child: const Text('Open File...'),
              ),
            ),
            (filePickerCross == null)
                ? const Text('Open a file first, to save')
                : ElevatedButton(
                    key: exportKey,
                    onPressed: _selectSaveFile,
                    child: const Text('Save as...'),
                  ),
            Text(
              'File system details',
              style: Theme.of(context).textTheme.headline5,
            ),
            Text('Quota: ${(quota.quota / 1e6).round()} MB'),
            Text(
                'Usage: ${(quota.usage / 1e6).round()}; Remaining: ${(quota.remaining / 1e6).round()}'),
            Text('Percentage: ${quota.relative.roundToDouble()}'),
            Text(
              'File details',
              style: Theme.of(context).textTheme.headline5,
            ),
            Text(
                'File path: ${filePickerCross?.path ?? 'unknown'} (Might cause issues on web)\n'),
            Text('File length: ${filePickerCross?.length ?? 0}\n'),
            Text('File as String: $_fileString\n'),
          ],
        ),
      ),
    );
  }

  void _selectFile(context) {
    FilePickerCross.importMultipleFromStorage().then((filePicker) {
      setFilePicker(filePicker[0]);
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text('You selected ${filePicker.length} file(s).'),
        ),
      );

      setState(() {});
    });
  }

  void _selectSaveFile() {
    RenderBox renderBox =
        exportKey.currentContext!.findRenderObject() as RenderBox;
    Offset position = renderBox.localToGlobal(Offset.zero);
    filePickerCross!.exportToStorage(
        subject: filePickerCross!.fileName,
        sharePositionOrigin: Rect.fromLTWH(
            //
            position.dx,
            position.dy,
            renderBox.size.width,
            renderBox.size.height));
  }

  setFilePicker(FilePickerCross filePicker) => setState(() {
        filePickerCross = filePicker;
        filePickerCross!.saveToPath(path: filePickerCross!.fileName!);
        FilePickerCross.quota().then((value) {
          setState(() => quota = value);
        });
        lastFiles!.add(filePickerCross!.fileName);
        try {
          _fileString = filePickerCross.toString();
        } catch (e) {
          _fileString = 'Not a text file. Showing base64.\n\n' +
              filePickerCross!.toBase64();
        }
      });
}

异常处理

不同的平台可能会抛出不同的异常,这可能是由于用户操作或平台限制引起的。例如,你可能想了解用户是否拒绝访问存储权限并据此采取行动。为此,你可以使用以下方法:

await FilePickerCross.importFromStorage().then(() {
  // ...
}).onError((error, _) {
  String _exceptionData = error.reason();
  print('----------------------');
  print('REASON: ${_exceptionData}');
  if (_exceptionData == 'read_external_storage_denied') {
    print('Permission was denied');
  } else if (_exceptionData == 'selection_canceled') {
    print('User canceled operation');
  } 
  print('----------------------');
});

当抛出FileSelectionCanceledError异常时,你可以通过调用reason()方法来收集底层异常信息。该方法返回一个String类型的结果。

文件存储位置

有两个重要的方法用于导出/保存文件:exportToStoragesaveToPath

  • exportToStorage 显示一个对话框,允许用户选择保存文件的位置。
  • saveToPath 适用于自动化保存的情况,即应用程序自动创建文件以供后续在应用内部使用。对于Web平台,这意味着文件存储在localStorage中;在Windows平台上,路径是%LOCALAPPDATA%\your_app_name\;在其他平台上,文件存储在${getApplicationDocumentsDirectory()}/your_app_name/

目录选择

目前,移动设备和Web设备的安全机制不允许直接选择目录。不过,你可以使用以下方法来保存文件到指定路径:

// 保存文件到指定路径
filePickerCross.saveToPath('/my/awesome/folder/' + filePickerCross.fileName);

如果你需要在桌面端选择一次目录,并持续保存和读取文件,可以使用以下方法:

// 第一次选择文件时显示导出对话框
FilePickerCross myFile = ...;

String pathForExports = await myFile.exportToStorage(); // <- 将返回文件的路径(仅限桌面端)

// 解析文件的目录并用于后续的自动导出
pathForExports = pathForExports.substring(0, pathForExports.lastIndexOf(r'/'));
print(pathForExports);

// 然后保存路径以便后续使用
...

// 下次检查是否覆盖现有文件或只是写入文件
print(await File(pathForExports + '/myNextFile.csv').exists());
File myCsvFile = await File(pathForExports + '/myNextFile.csv').writeAsString('comma,separated,values'); // <- 这只在桌面端有效,其他设备会阻止此操作。

更多关于Flutter文件选择插件file_picker_cross的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter文件选择插件file_picker_cross的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何在Flutter项目中使用file_picker_cross插件来选择文件的示例代码。file_picker_cross是一个跨平台的文件选择器插件,它允许用户在移动设备和桌面平台上选择文件。

首先,确保你已经在pubspec.yaml文件中添加了file_picker_cross依赖项:

dependencies:
  flutter:
    sdk: flutter
  file_picker_cross: ^x.y.z  # 请替换为最新版本号

然后运行flutter pub get来安装依赖项。

接下来,你可以在你的Flutter项目中按照以下步骤使用file_picker_cross插件:

  1. 导入插件

在你的Dart文件中导入file_picker_cross插件:

import 'package:file_picker_cross/file_picker_cross.dart';
import 'package:flutter/material.dart';
  1. 创建文件选择器按钮

在你的UI中创建一个按钮来触发文件选择对话框:

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('File Picker Cross Example'),
        ),
        body: Center(
          child: FilePickerButton(),
        ),
      ),
    );
  }
}

class FilePickerButton extends StatefulWidget {
  @override
  _FilePickerButtonState createState() => _FilePickerButtonState();
}

class _FilePickerButtonState extends State<FilePickerButton> {
  FilePickerCrossResult? _result;

  Future<void> _pickFiles() async {
    FilePickerCrossResult? result = await FilePickerCross.importFromStorage(
      type: FileTypeCross.any, // 可以选择 'image', 'video', 'audio', 'file', 或 'any'
      allowMultiple: true, // 是否允许选择多个文件
    );

    if (result != null) {
      setState(() {
        _result = result;
      });

      // 打印文件路径
      result.files!.forEach((file) {
        print(file.path);
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: _pickFiles,
      child: Text('选择文件'),
    );
  }
}
  1. 显示选择的文件(可选):

如果你想在UI中显示选择的文件,可以修改_FilePickerButtonState类来包括一个显示文件路径的列表:

class _FilePickerButtonState extends State<FilePickerButton> {
  FilePickerCrossResult? _result;
  List<String> _filePaths = [];

  Future<void> _pickFiles() async {
    FilePickerCrossResult? result = await FilePickerCross.importFromStorage(
      type: FileTypeCross.any,
      allowMultiple: true,
    );

    if (result != null) {
      setState(() {
        _result = result;
        _filePaths = result.files!.map((file) => file.path).toList();
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        ElevatedButton(
          onPressed: _pickFiles,
          child: Text('选择文件'),
        ),
        SizedBox(height: 20),
        if (_filePaths.isNotEmpty)
          Expanded(
            child: ListView.builder(
              itemCount: _filePaths.length,
              itemBuilder: (context, index) {
                return ListTile(
                  title: Text(_filePaths[index]),
                );
              },
            ),
          ),
      ],
    );
  }
}

这个示例展示了如何使用file_picker_cross插件来选择文件,并在UI中显示选择的文件路径。请确保你已经按照插件的文档配置了必要的权限(例如,在Android的AndroidManifest.xml中添加存储权限)。

注意:由于插件和Flutter框架可能会更新,建议查看file_picker_cross的官方文档以获取最新的使用指南和API变更信息。

回到顶部