Flutter文件拖拽上传插件flutter_dropzone的使用

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

Flutter Dropzone 插件的使用

简介

flutter_dropzone 是一个用于Flutter Web项目的插件,它允许用户通过拖放文件到指定区域来上传文件。如果你对在Flutter应用内部实现拖放功能感兴趣,请查看其他插件如dnd

主要组件

flutter_dropzone 提供了一个平台视图 DropzoneView,用于处理文件的拖放事件。这个视图本身没有显示内容,只是一个拖放区。通常会结合 Stack 使用,将 DropzoneView 放置在背景层,而前景层可以放置文本或其他UI元素来引导用户进行操作。

示例代码

下面是一个完整的示例代码,演示了如何使用 flutter_dropzone 插件创建两个不同的拖放区,并且展示了如何获取文件数据、处理字符串拖放以及从浏览器中选择文件的功能。

// 忽略未使用的导入和弃用警告
import 'dart:async' show Completer;
import 'dart:math' show min;
import 'dart:typed_data' show Uint8List, BytesBuilder;

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

void main() => runApp(const MyApp());

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late DropzoneViewController controller1;
  late DropzoneViewController controller2;
  String message1 = 'Drop something here';
  String message2 = 'Drop something here';
  bool highlighted1 = false;

  @override
  Widget build(BuildContext context) => MaterialApp(
        home: Scaffold(
          appBar: AppBar(
            title: const Text('Dropzone example'),
          ),
          body: Column(
            children: [
              Expanded(
                child: Container(
                  color: highlighted1 ? Colors.red : Colors.transparent,
                  child: Stack(
                    children: [
                      buildZone1(context),
                      Center(child: Text(message1)),
                    ],
                  ),
                ),
              ),
              Expanded(
                child: Stack(
                  children: [
                    buildZone2(context),
                    Center(child: Text(message2)),
                  ],
                ),
              ),
              ElevatedButton(
                onPressed: () async {
                  print(await controller1.pickFiles(mime: ['image/jpeg', 'image/png']));
                },
                child: const Text('Pick file'),
              ),
            ],
          ),
        ),
      );

  // 构建第一个拖放区
  Widget buildZone1(BuildContext context) => Builder(
        builder: (context) => DropzoneView(
          operation: DragOperation.copy,
          cursor: CursorType.grab,
          onCreated: (ctrl) => controller1 = ctrl,
          onLoaded: () => print('Zone 1 loaded'),
          onError: (error) => print('Zone 1 error: $error'),
          onHover: () {
            setState(() => highlighted1 = true);
            print('Zone 1 hovered');
          },
          onLeave: () {
            setState(() => highlighted1 = false);
            print('Zone 1 left');
          },
          onDropFile: (file) async {
            print('Zone 1 drop: ${file.name}');
            setState(() {
              message1 = '${file.name} dropped';
              highlighted1 = false;
            });
            final bytes = await controller1.getFileData(file);
            print('Read bytes with length ${bytes.length}');
            print(bytes.sublist(0, min(bytes.length, 20)));
          },
          onDropString: (s) {
            print('Zone 1 drop: $s');
            setState(() {
              message1 = 'text dropped';
              highlighted1 = false;
            });
            print(s.substring(0, min(s.length, 20)));
          },
          onDropInvalid: (mime) => print('Zone 1 invalid MIME: $mime'),
          onDropFiles: (files) => print('Zone 1 drop multiple: $files'),
          onDropStrings: (strings) => print('Zone 1 drop multiple: $strings'),
        ),
      );

  // 构建第二个拖放区
  Widget buildZone2(BuildContext context) => Builder(
        builder: (context) => DropzoneView(
          operation: DragOperation.move,
          mime: const ['image/jpeg'],
          onCreated: (ctrl) => controller2 = ctrl,
          onLoaded: () => print('Zone 2 loaded'),
          onError: (error) => print('Zone 2 error: $error'),
          onHover: () => print('Zone 2 hovered'),
          onLeave: () => print('Zone 2 left'),
          onDropFile: (DropzoneFileInterface file) async {
            print('Zone 2 drop: ${file.name}');
            setState(() {
              message2 = '${file.name} dropped';
            });
            final fileStream = controller2.getFileStream(file);
            final bytes = await collectBytes(fileStream);
            print('Streamed bytes with length ${bytes.length}');
            print(bytes.sublist(0, min(bytes.length, 20)));
          },
          onDropString: (s) {
            print('Zone 2 drop: $s');
            setState(() {
              message2 = 'text dropped';
            });
            print(s.substring(0, min(s.length, 20)));
          },
          onDropInvalid: (mime) => print('Zone 2 invalid MIME: $mime'),
          onDropFiles: (files) => print('Zone 2 drop multiple: $files'),
          onDropStrings: (strings) => print('Zone 2 drop multiple: $strings'),
        ),
      );

  Future<Uint8List> collectBytes(Stream<List<int>> source) {
    var bytes = BytesBuilder(copy: false);
    var completer = Completer<Uint8List>.sync();
    source.listen(
      bytes.add,
      onError: completer.completeError,
      onDone: () => completer.complete(bytes.takeBytes()),
      cancelOnError: true,
    );
    return completer.future;
  }
}

控制器的使用

由于返回的文件是HTML File API引用,具有一定的限制,不能转换为常规的Dart File 对象。它们作为 DropzoneFileInterface 对象返回,控制器提供了以下函数来提取这些对象的信息:

  • Future<String> getFilename(DropzoneFileInterface file);
  • Future<int> getFileSize(DropzoneFileInterface file);
  • Future<String> getFileMIME(DropzoneFileInterface file);
  • Future<DateTime> getFileLastModified(DropzoneFileInterface file);
  • Future<Uint8List> getFileData(DropzoneFileInterface file);
  • Stream<List<int>> getFileStream(DropzoneFileInterface file);

此外,还有两个与临时URL相关的函数:

  • Future<String> createFileUrl(DropzoneFileInterface file);
  • Future<bool> releaseFileUrl(String fileUrl);

如果需要保留文件的全部数据,可以使用 getFileData() 获取实际内容并自行存储。也可以通过 createFileUrl() 创建一个临时的URL来预览文件(例如图片),但这个URL仅在当前会话有效,使用完毕后应调用 releaseFileUrl() 释放资源。

跨平台应用中的使用

这是一个联合插件,意味着它可以编译进包含Android/iOS和Web代码的跨平台应用程序中。不过请注意,它只在Web端有效,在移动端会返回错误信息而不是拖放区。因此建议使用 if (kIsWeb) 来确保只在Flutter Web中启用此功能。

注意事项

版本4.2.0引入了一些破坏性变更,主要体现在文件接口的变化上。之前的版本直接返回 web.File,而现在改为返回 DropzoneFileInterface。另外,onDrop 方法已被弃用,推荐使用 onDropFileonDropString 代替。

希望以上信息能帮助您更好地理解和使用 flutter_dropzone 插件!如果您有任何问题或需要进一步的帮助,请随时提问。


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

1 回复

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


当然,以下是一个关于如何在Flutter中使用flutter_dropzone插件来实现文件拖拽上传功能的代码案例。这个插件允许用户通过拖拽文件到指定的区域来上传文件。

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

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

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

接下来,在你的Flutter应用中实现文件拖拽上传功能。以下是一个完整的示例代码:

import 'package:flutter/material.dart';
import 'package:flutter_dropzone/flutter_dropzone.dart';
import 'dart:html' as html; // 注意:此导入仅适用于Web平台

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter Dropzone Example'),
        ),
        body: Center(
          child: DropzoneArea(
            onDrop: (List<html.File> files) async {
              // 处理上传的文件
              for (var file in files) {
                print('File name: ${file.name}');
                // 你可以在这里添加文件上传逻辑,例如通过HTTP请求发送到服务器
              }
            },
            acceptedFiles: ['image/*', 'application/pdf'], // 只接受图片和PDF文件
            maxFiles: 5, // 最大文件数量
            maxFileSize: 2, // 最大文件大小(MB)
            dropzoneBorder: 2,
            dropzoneActiveStyle: DropzoneActiveStyle(
              borderColor: Colors.blueAccent,
            ),
            dropzoneInactiveStyle: DropzoneInactiveStyle(
              borderColor: Colors.grey,
            ),
            builder: (context, state) {
              return Container(
                width: 300,
                height: 200,
                decoration: BoxDecoration(
                  border: Border.all(
                    color: state.isActive
                        ? Colors.blueAccent
                        : Colors.grey,
                    width: state.border,
                  ),
                ),
                child: Center(
                  child: Text(
                    state.files.isEmpty
                        ? 'Drag and drop files here or click to select'
                        : '${state.files.length} files selected',
                    style: TextStyle(color: Colors.black),
                  ),
                ),
              );
            },
          ),
        ),
      ),
    );
  }
}

注意事项:

  1. 平台支持flutter_dropzone插件目前主要支持Web平台。如果你需要在移动平台上实现类似功能,可能需要寻找其他插件或者自定义实现。
  2. 文件处理:在onDrop回调中,你可以处理上传的文件,例如读取文件内容或者通过HTTP请求发送到服务器。
  3. 样式定制:你可以通过DropzoneActiveStyleDropzoneInactiveStyle来自定义拖拽区域的样式。

额外提示:

  • 在实际项目中,你可能需要添加更多的错误处理和用户反馈机制,例如显示上传进度、处理上传失败的情况等。
  • 如果你需要在移动平台上实现文件拖拽上传功能,可能需要结合平台特定的API或者插件来实现。

希望这个示例代码能帮助你快速上手flutter_dropzone插件的使用!

回到顶部