Flutter 重构:NFileUploadBox组件重构

187 阅读2分钟

一、需求来源

NFileUploadBox 实现功能之后要同步支持到多个app,但是多个app的设计风格差异很大,必须将样式可以二次自定义;于是就有此篇文章,思路是通过基类实现功能,重写父类方法实现样式覆盖,状态复用。好处是共用逻辑,维护成本低。

二、重构调整

1、NFileUploadBox 修改

将选择按钮,点击跳转,子项显示抽离到 NFileUploadHandle类,通过继承重写方法可自定义界面,达到逻辑复用的目的;

...

/// 从文件存储系统选择文件
class NFileUploadBox extends StatefulWidget {
  const NFileUploadBox({
...
    this.fileUpload,
  });

...

  final NFileUploadHandle? fileUpload;

  @override
  State<NFileUploadBox> createState() => _NFileUploadBoxState();
}

class _NFileUploadBoxState extends State<NFileUploadBox> {

...

  @override
  Widget build(BuildContext context) {
    void urlBlock(String url) {
      final isAllFinished = selectedModels.where((e) => e.url == null).isEmpty;
      // debugPrint("isAllFinished: ${isAllFinished}");
      if (isAllFinished) {
        final urls = selectedModels.map((e) => e.url).toList();
        debugPrint("isAllFinished urls: $urls");
        widget.onChanged(selectedModels);
        isAllUploadFinished.value = true;
      }
    }

    onPick() async {
      await onPickFile(
        maxMB: widget.maxMB,
        maxCount: widget.maxCount,
        type: widget.type,
        allowMultiple: widget.allowMultiple,
        allowedExtensions: widget.allowedExtensions,
        onPermission: () async {
          //todo: 权限
          return true;
        },
      );
    }

    return Column(
      mainAxisSize: MainAxisSize.min,
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        widget.header ??
            Padding(
              padding: EdgeInsets.symmetric(vertical: 5),
              child: NText(
                widget.title,
                fontSize: 14,
                color: fontColor737373,
              ),
            ),
        ...selectedModels.map((e) {
          final index = selectedModels.indexOf(e);

          void deleteItem() {
            debugPrint(
                "onDelete: $index, length: ${selectedModels[index].assetFile?.path}");
            selectedModels.remove(e);
            setState(() {});
            widget.onChanged(selectedModels);
          }

          // final fileName = (e.assetFile?.path ?? "").split("/").last;
          return GestureDetector(
            onTap: () {
                FocusScope.of(context).unfocus();
                if (widget.fileUpload != null) {
                  widget.fileUpload?.onTapItem(e);
                  return;
                }
              onTapItem(e);
            },
            child: widget.fileUpload?.buildItem(
                    itemWidth: widget.itemWidth,
                    itemHeight: widget.itemHeight,
                    e: e,
                    urlBlock: urlBlock,
                    deleteItem: deleteItem,
                    canEdit: widget.canEdit,
                    showFileSize: widget.showFileSize) ??
                NFileUploadItem(
                  model: e,
                  canEdit: widget.canEdit,
                  urlBlock: urlBlock,
                  onDelete: widget.canEdit == false ? null : deleteItem,
                  showFileSize: widget.showFileSize,
                ),
          );
        }).toList(),
        if (selectedModels.length < widget.maxCount)
          widget.fileUpload?.buildUploadButton(onPressed: onPick) ??
              buildUploadButton(onPressed: onPick),
      
        ...
        
      ],
    );
  }
  
...

}

2、NFileUploadItem 修改

...

typedef NFileUploadItemBuilder = Widget Function(
  NFileUploadModel model,
  VoidCallback? onDelete,
  VoidCallback? onRefresh,
  ValueNotifier<bool> successVN,
  ValueNotifier<double> percentVN,
);

/// 上传图片单元(基于 wechat_assets_picker)
class NFileUploadItem extends StatefulWidget {
  const NFileUploadItem({
...

    this.builder,
  });

...

  final NFileUploadItemBuilder? builder;

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

class NFileUploadItemState extends State<NFileUploadItem>
    with AutomaticKeepAliveClientMixin {
...


  @override
  Widget build(BuildContext context) {
    super.build(context);

    final fileName = widget.model.fileName ?? "--";

    final fileNameNew = fileName.split(".").firstOrNull ?? "--";
    final ext = fileName.split(".").lastOrNull ?? "";

    if (widget.builder != null) {
      return widget.builder!(
        widget.model,
        widget.onDelete,
        onRefresh,
        _successVN,
        _percentVN,
      );
    }
...

  }
  
...

}

3、NFileUploadHandle 源码

/// 文件上传基类
class NFileUploadHandle {
  const NFileUploadHandle();

  /// 子项展示
  NFileUploadItem buildItem({
    double? itemWidth,
    double? itemHeight,
    required NFileUploadModel e,
    required ValueChanged<String> urlBlock,
    required VoidCallback deleteItem,
    required bool canEdit,
    required bool showFileSize,
  }) {
  
...

  }

  /// 子项点击
  void onTapItem(NFileUploadModel model) {
  
...

  }

  /// 发起选择按钮
  Widget buildUploadButton({required VoidCallback onPressed}) {
  
...

  }
}

最后

1、使用:比如第一个 app:OneApp,通过继承 NFileUploadHandle 重写 UI 即可:

class NFileUploadOneApp extends  NFileUploadHandle {
  /// 子项
  @override
  NFileUploadItem buildItem({
    double? itemWidth,
    double? itemHeight,
    required NFileUploadModel e,
    required ValueChanged<String> urlBlock,
    required VoidCallback deleteItem,
    required bool canEdit,
    required bool showFileSize,
  }) {
    return NFileUploadItem(
      model: e,
      canEdit: canEdit,
      urlBlock: urlBlock,
      onDelete: canEdit == false ? null : deleteItem,
      showFileSize: showFileSize,
      builder: (NFileUploadModel model,
          VoidCallback? onDelete,
          VoidCallback? onRefresh,
          ValueNotifier<bool> successVN,
          ValueNotifier<double> percentVN) {
        final validUrl = model.url?.startsWith("http") == true;

        String name = model.fileName ?? "--";
        String? fileDesc = model.fileDesc;

        //重写子项样式(builder已返回必须的逻辑参数)
  
      },
    );
  }

  @override
  void onTapItem(NFileUploadModel model) {
    super.onTapItem(model);
    //重写点击事件
  }

  /// 发起选择按钮
  @override
  Widget buildUploadButton({required VoidCallback onPressed}) {
    //重写上传按钮
}

2、本文只是根据前端公式 UI = F(state) 进行的一个文件上传组件的简单重构。

Flutter 封装:基于 file_picker 的上传组件 NFileUploadBox