likes
comments
collection
share

Flutter 匠心千刃 | 批量文件生成器

作者站长头像
站长
· 阅读数 32

Flutter 匠心千刃 | 批量文件生成器

最近需要测试文件发送,但是手上的文件不太多,自己的文件也不太适合测试。所以想为 匠心千刃 打造一个 批量随机文件生成器,来辅助生成批量的指定大小范围的测试文件。通过 Flutter 构建交互界面,进行表单输入配置项,点击按钮触发 生成逻辑 :

Flutter 匠心千刃 | 批量文件生成器


1. 如何生成指定大小的文件

由于测试文件对内容没有什么硬性要求,而文件本身就是字节数字组。所以只需要创建字节列表,通过 File 对象写入即可。 如下 FileWriter 类负责文件的写入,这里取随机 0~9 数字作为文件内容;通过 List.filled 构造指定长度的字节列表,然后通过 File#writeAsBytes 方法写入磁盘:

class FileWriter {
  Random _random = Random();

  Future<void> write(String path, int length) async {
    int value = _random.nextInt(10);
    List<int> result = List.filled(length, value);
    await File(path).writeAsBytes(result);
  }
}

在 mian 函数中测试一下,在项目根目录下,生成一个名为 test.gen 大小为 1024 的文件:

Flutter 匠心千刃 | 批量文件生成器

import 'dart:io';
import 'dart:math';
import 'package:path/path.dart' as path;

void main() async {
  FileWriter fileWriter = FileWriter();
  String filepath = path.join(Directory.current.path,'test.gen');
  await fileWriter.writeFile(filepath, 1024);
  print(filepath);
}

这就是工具的核心代码,是不是非常简单。下面就来看一下,如何基于它打造命令行和 UI 交互应用。


2. 业务逻辑: 批量生成文件

既然能生成一个文件,那批量生成自然也不在话下。如下所示,我希望在一个文件夹下,可以批量生成大小在一定范围内的文件,后缀名依次递增:

Flutter 匠心千刃 | 批量文件生成器

交互中可以选择文件大小单位,这里通过 ByteUnit 维护,包含 BKBMBGB 四个单位;另外通过 bytes 方法可以获取当前单位对应的字节数:

enum ByteUnit {
  b('B'),
  kb('KB'),
  mb('MB'),
  gb('GB'),
  ;

  final String label;

  int get bytes => switch (this) {
        ByteUnit.b => 1,
        ByteUnit.kb => 1024,
        ByteUnit.mb => 1024 * 1024,
        ByteUnit.gb => 1024 * 1024 * 1024,
      };

  const ByteUnit(this.label);
}

然后定义文件生成的配置数据,包括生成的文件数量、文件夹、单位、最大值、最小值六个可配置项:

class FileGenConfig {
  final int fileCount;
  final String filename;
  final String dir;
  final ByteUnit unit;
  final double min;
  final double max;

  FileGenConfig({
    this.fileCount = 10,
    required this.filename,
    required this.dir,
    this.unit = ByteUnit.mb,
    this.min = 10,
    this.max = 11,
  });

  (int, int) get bytesRange => (
        (min * unit.bytes).toInt(),
        (max * unit.bytes).toInt(),
      );
}

最后在 FileWriter 中定义 gen 方法,遍历 count 次,取最大最小字节数间的随机长度,通过 writeFile 函数写入即可:

Future<void> gen(FileGenConfig config) async {
  Directory directory = Directory(config.dir);
  if (!directory.existsSync()) {
    await directory.create(recursive: true);
  }
  var (min, max) = config.bytesRange;
  for (int i = 0; i < config.fileCount; i++) {
    int length = min + _random.nextInt(max - min);
    String name = path.basenameWithoutExtension(config.filename);
    String extension = path.extension(config.filename);
    String filepath = path.join(directory.path, '${name}_$i$extension');
    await writeFile(filepath, length);
  }
}

3. 视图层: 构建交互界面

业务逻辑已经在上面完成了,现在视图层的主要任务是,在交互过程中维护配置数据,并在运行按钮中执行核心业务逻辑 gen 方法。匠心千刃中的视图交互在文章开始已经展示了,其中组件基于TolyUI 进行构建。包括:

  • 文件输入选择器,选择文件夹的位置。
  • 数字输入增加器,交互时修改数字相关配置项。
  • 群组单值选择器,交互时修改可枚举的配置数据。

Flutter 匠心千刃 | 批量文件生成器


当前界面的结构分为三个部分:

  • 上方 FileGenTitleBar 是 工具标题操作按钮
  • 中间 FileGenForm 是交互 表单面板, 作用在于通过交互设置生成器的配置参数。
  • 下方 FileGenInfo 根据表单中的配置内容,实时生成介绍信息。

Flutter 匠心千刃 | 批量文件生成器

FileGenPage 负责渲染整个批量文件生成器的视图构建,三个部分的组合逻辑如下所示。其中 DisplayPanel 是一个白色的装饰盒:

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

  @override
  State<FileGenPage> createState() => _FileGenPageState();
}

class _FileGenPageState extends State<FileGenPage> {
  final FileGenConfigModel model = FileGenConfigModel();

  @override
  void dispose() {
    model.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 32.0, vertical: 12),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        mainAxisSize: MainAxisSize.min,
        children: [
          FileGenTitleBar(onTap: _onTap),
          DisplayPanel(
            children: [
              FileGenForm(model: model),
              const SizedBox(height: 24),
              FileGenInfo(tips: model.tips),
            ],
          )
        ],
      ),
    );
  }
}

4. 业务逻辑与状态变化

在输入面板的交互过程中,配置信息会不断变化。变化之后,需要通知底部文案界面发生变化;以及顶部按钮也需要感知最新的配置数据,进行正确的生成。

Flutter 匠心千刃 | 批量文件生成器

在 Flutter 中建立通知更新的关系是非常简单的,对于输入框来说通过 TextEditingController 可以绑定输入框内容的展示;像文件单位的监听通知,可以通过 ValueNotifier 对象进行处理: 这里通过 FileGenConfigModel 统一维护这些可具体对象,比如处理初始化、销毁等逻辑:

class FileGenConfigModel {
  TextEditingController dirPathCtrl = TextEditingController();
  TextEditingController nameCtrl = TextEditingController(text: 'test.txt');
  TextEditingController countCtrl = TextEditingController(text: '10');
  TextEditingController minCtrl = TextEditingController(text: '10');
  TextEditingController maxCtrl = TextEditingController(text: '11');
  ValueNotifier<ByteUnit> unitCtrl = ValueNotifier(ByteUnit.mb);

  FileGenConfigModel() {
    reset();
  }

  void reset() {
    nameCtrl.text = 'test.txt';
    countCtrl.text = '10';
    maxCtrl.text = '11';
    minCtrl.text = '10';
    unitCtrl.value = ByteUnit.mb;
    _initDir();
  }

  void _initDir() async {
    Directory directory = await getTemporaryDirectory();
    dirPathCtrl.text = p.join(directory.path, 'toly_gen');
  }

  void dispose() {
    dirPathCtrl.dispose();
    nameCtrl.dispose();
    countCtrl.dispose();
    minCtrl.dispose();
    maxCtrl.dispose();
    unitCtrl.dispose();
  }
}

然后,可以通过 ValueListenableBuilder 组件,来观察可监听对象。在对象变化通知后,触发局部区域的重新构建。下面以 CupertinoSlidingSegmentedControl 组件为例,可以监听 FileGenConfigModel 中的unitCtrl 对象,实现变化更新通知。

Flutter 匠心千刃 | 批量文件生成器

Widget _buildUnitSelector() => Align(
    alignment: Alignment.centerRight,
    child: SizedBox(
        width: 240,
        child: ValueListenableBuilder<ByteUnit>(
          valueListenable: model.unitCtrl,
          builder: (_, v, __) => CupertinoSlidingSegmentedControl<ByteUnit>(
            groupValue: v,
            onValueChanged: model.changeUnit,
            backgroundColor: const Color(0xfff5f7fa),
            padding: const EdgeInsets.all(5),
            children: const {
              ByteUnit.b: Text("B"),
              ByteUnit.kb: Text("KB"),
              ByteUnit.mb: Text("MB"),
              ByteUnit.gb: Text("GB"),
            },
          ),
        )));

其他的输入类型的组件,使用的都是 TolyUI 中的 TolyInput 组件,包括输入文字选择器、数字输入选择器。感兴趣的可以详见 TolyUI 的组件部分。


对于底部的文字,在表单数据发生改变时,都要进行更新。可以在 FileGenConfigModel 中,通过 Listenable.merge 合并多个可监听对象,添加监听器,来更新文字提示的内容。视图层同样可以通过 ValueListenableBuilder 进行局部构建:

Flutter 匠心千刃 | 批量文件生成器

ValueNotifier<String> tips = ValueNotifier('');

void _initListener() {
  Listenable.merge([dirPathCtrl, nameCtrl, countCtrl, minCtrl, maxCtrl, unitCtrl])
      .addListener(_updateInfo);
}

void _updateInfo() {
  String info = '将在 ${dirPathCtrl.text} 文件夹下生成 ${countCtrl.text} 个,'
      '大小在 ${minCtrl.text}${unitCtrl.value.label}${maxCtrl.text}${unitCtrl.value.label} '
      '之间名称为 ${nameCtrl.text} 系列的文件,点击右上角按钮运行生成。';
  tips.value = info;
}

5.点击按钮与生成结果

顶部栏在封装中,提供三个 ActionType 表示三种按钮类型,通过 onTap 回调点击事件。所以在外界可以根据 ActionType 来处理不同的事件。

Flutter 匠心千刃 | 批量文件生成器

void _onTap(ActionType value) async {
  switch (value) {
    case ActionType.refresh:
      model.reset();
      $message.success(message: '已重置配置');
      break;
    case ActionType.openDir:
      String dir = model.dirPathCtrl.text;
      bool exist = Directory(dir).existsSync();
      if (exist) {
        fxOpen(dir);
      } else {
        $message.warning(message: '当前文件夹不存在!');
      }
      break;
    case ActionType.start:
      await FileWriter().gen(model.config);
      $message.success(message: '生成成功!');
  }
}

最重要的生成按钮,只要触发之前的 FileWriter#gen 方法,根据 FileGenConfigModel 创建 FileConfig 配置即可:

Flutter 匠心千刃 | 批量文件生成器


到这里,一个简单的批量随机文件生成器就完成啦,后面还可以继续优化。比如超大文件如何优雅地生成,执行过程中的状态、进度、生成内容的选择等等。那本就到这里,感谢观看,下次再见~

转载自:https://juejin.cn/post/7399325667256811571
评论
请登录