Flutter文件选择插件reactive_file_selector的使用
Flutter文件选择插件reactive_file_selector的使用
reactive_file_selector 是一个用于在 Flutter 中选择文件的插件。它基于 file_selector 插件,并与 reactive_forms 集成以实现更方便的表单管理。
示例代码
下面是一个完整的示例代码,展示了如何使用 reactive_file_selector 插件来选择多个文件并验证文件数量。
import 'package:flutter/material.dart';
import 'package:reactive_file_selector/reactive_file_selector.dart';
import 'package:reactive_forms/reactive_forms.dart';
void main() {
  runApp(const MyApp());
}
class MyApp extends StatelessWidget {
  const MyApp({super.key});
  // 构建表单
  FormGroup buildForm() => fb.group({
        'input': FormControl<MultiFile<String>>(
            value: const MultiFile(),
            validators: [
              Validators.required,
              FileSelectorValidators.limit(min: 5, max: 10),
            ]),
      });
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: Scaffold(
        appBar: AppBar(),
        body: SafeArea(
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: ReactiveFormBuilder(
              form: buildForm,
              builder: (context, form, child) {
                return Column(
                  children: [
                    Expanded(
                      child: ReactiveFileSelector<String>(
                        formControlName: 'input',
                        allowMultiple: true,
                        distinctPickedFiles: true,
                        filePickerBuilder: (pickImage, files, onChange) {
                          final items = [
                            ...files.files
                                .asMap()
                                .map((key, value) => MapEntry(
                                    key,
                                    ListTile(
                                      onTap: () {
                                        onChange(files.copyWith(
                                            files:
                                                List<String>.from(files.files)
                                                  ..removeAt(key)));
                                      },
                                      leading: const Icon(Icons.delete),
                                      title: FileListItem(value).build(context),
                                    )))
                                .values,
                            ...files.platformFiles
                                .asMap()
                                .map((key, value) => MapEntry(
                                      key,
                                      ListTile(
                                        onTap: () {
                                          onChange(files.copyWith(
                                              platformFiles: List<XFile>.from(
                                                  files.platformFiles)
                                                ..removeAt(key)));
                                        },
                                        leading: const Icon(Icons.delete),
                                        title: PlatformFileListItem(value)
                                            .build(context),
                                      ),
                                    ))
                                .values,
                          ];
                          return Column(
                            children: [
                              Expanded(
                                child: ListView.builder(
                                  itemCount: items.length,
                                  itemBuilder: (_, i) {
                                    return items[i];
                                  },
                                ),
                              ),
                              ElevatedButton(
                                onPressed: pickImage,
                                child: const Text("Pick images"),
                              ),
                            ],
                          );
                        },
                        validationMessages: {
                          ValidationMessage.min: (error) {
                            if (error is! Map<String, Object?>) {
                              return ValidationMessage.min;
                            }
                            return "应至少有 ${error['min']} 个文件,实际有: ${error['actual']}";
                          },
                          ValidationMessage.max: (error) {
                            if (error is! Map<String, Object?>) {
                              return ValidationMessage.max;
                            }
                            return "应最多有 ${error['max']} 个文件,实际有: ${error['actual']}";
                          }
                        },
                        decoration: const InputDecoration(
                          labelText: '多文件选择器',
                          border: OutlineInputBorder(),
                          helperText: '',
                        ),
                      ),
                    ),
                    ElevatedButton(
                      child: const Text('提交'),
                      onPressed: () {
                        if (form.valid) {
                          debugPrint(form.value.toString());
                        }
                      },
                    ),
                  ],
                );
              },
            ),
          ),
        ),
      ),
    );
  }
}
// 抽象类,定义列表项接口
abstract class ListItem {
  Widget build(BuildContext context);
}
// 文件列表项
class FileListItem extends ListItem {
  final String url;
  FileListItem(this.url);
  [@override](/user/override)
  Widget build(context) {
    return Text(url);
  }
}
// 平台文件列表项
class PlatformFileListItem extends ListItem {
  final XFile platformFile;
  PlatformFileListItem(this.platformFile);
  [@override](/user/override)
  Widget build(context) {
    return Text(platformFile.name);
  }
}
代码解释
- 
导入必要的库:
import 'package:flutter/material.dart'; import 'package:reactive_file_selector/reactive_file_selector.dart'; import 'package:reactive_forms/reactive_forms.dart'; - 
构建表单:
FormGroup buildForm() => fb.group({ 'input': FormControl<MultiFile<String>>( value: const MultiFile(), validators: [ Validators.required, FileSelectorValidators.limit(min: 5, max: 10), ]), }); - 
构建UI:
[@override](/user/override) Widget build(BuildContext context) { return MaterialApp( theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: Scaffold( appBar: AppBar(), body: SafeArea( child: Padding( padding: const EdgeInsets.all(8.0), child: ReactiveFormBuilder( form: buildForm, builder: (context, form, child) { return Column( children: [ Expanded( child: ReactiveFileSelector<String>( formControlName: 'input', allowMultiple: true, distinctPickedFiles: true, filePickerBuilder: (pickImage, files, onChange) { // 构建文件列表 }, validationMessages: { ValidationMessage.min: (error) { // 自定义最小文件数错误信息 }, ValidationMessage.max: (error) { // 自定义最大文件数错误信息 } }, decoration: const InputDecoration( labelText: '多文件选择器', border: OutlineInputBorder(), helperText: '', ), ), ), ElevatedButton( child: const Text('提交'), onPressed: () { if (form.valid) { debugPrint(form.value.toString()); } }, ), ], ); }, ), ), ), ), ); } - 
自定义列表项:
abstract class ListItem { Widget build(BuildContext context); } class FileListItem extends ListItem { final String url; FileListItem(this.url); [@override](/user/override) Widget build(context) { return Text(url); } } class PlatformFileListItem extends ListItem { final XFile platformFile; PlatformFileListItem(this.platformFile); [@override](/user/override) Widget build(context) { return Text(platformFile.name); } } 
更多关于Flutter文件选择插件reactive_file_selector的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter文件选择插件reactive_file_selector的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
reactive_file_selector 是一个 Flutter 插件,用于在 Flutter 应用程序中选择文件。它提供了一个响应式的文件选择器,可以与 Flutter 的响应式编程模型(如 Stream 或 RxDart)无缝集成。
安装插件
首先,你需要在 pubspec.yaml 文件中添加 reactive_file_selector 插件的依赖:
dependencies:
  flutter:
    sdk: flutter
  reactive_file_selector: ^1.0.0  # 请使用最新版本
然后,运行 flutter pub get 来安装依赖。
使用 reactive_file_selector
1. 导入插件
在你的 Dart 文件中导入 reactive_file_selector 插件:
import 'package:reactive_file_selector/reactive_file_selector.dart';
2. 创建文件选择器
你可以使用 ReactiveFileSelector 小部件来创建一个文件选择器。它通常与 StreamController 或 BehaviorSubject 一起使用,以便在文件选择时触发事件。
import 'package:flutter/material.dart';
import 'package:reactive_file_selector/reactive_file_selector.dart';
import 'package:rxdart/rxdart.dart';
class FileSelectorExample extends StatefulWidget {
  @override
  _FileSelectorExampleState createState() => _FileSelectorExampleState();
}
class _FileSelectorExampleState extends State<FileSelectorExample> {
  final BehaviorSubject<File> _fileSubject = BehaviorSubject<File>();
  @override
  void dispose() {
    _fileSubject.close();
    super.dispose();
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Reactive File Selector Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ReactiveFileSelector(
              onFileSelected: _fileSubject.add,
              child: ElevatedButton(
                onPressed: () {
                  // 这里不需要手动处理按钮点击事件,ReactiveFileSelector 会自动处理
                },
                child: Text('Select File'),
              ),
            ),
            StreamBuilder<File>(
              stream: _fileSubject.stream,
              builder: (context, snapshot) {
                if (snapshot.hasData) {
                  return Text('Selected File: ${snapshot.data!.path}');
                } else {
                  return Text('No file selected');
                }
              },
            ),
          ],
        ),
      ),
    );
  }
}
3. 处理文件选择事件
在上面的代码中,ReactiveFileSelector 小部件会在用户选择文件时触发 onFileSelected 回调,并将选中的文件添加到 _fileSubject 中。然后,StreamBuilder 会监听 _fileSubject 的变化,并在 UI 中显示选中的文件路径。
4. 自定义文件选择器
你可以通过传递不同的参数来自定义文件选择器的行为。例如,你可以指定允许选择的文件类型:
ReactiveFileSelector(
  onFileSelected: _fileSubject.add,
  allowedExtensions: ['.txt', '.pdf'],  // 只允许选择 .txt 和 .pdf 文件
  child: ElevatedButton(
    onPressed: () {},
    child: Text('Select File'),
  ),
),
5. 处理错误
你还可以通过 onError 回调来处理文件选择过程中可能发生的错误:
ReactiveFileSelector(
  onFileSelected: _fileSubject.add,
  onError: (error) {
    print('File selection error: $error');
  },
  child: ElevatedButton(
    onPressed: () {},
    child: Text('Select File'),
  ),
),
        
      
            
            
            
