美文网首页
file_drag_and_drop一个Flutter桌面版拖动

file_drag_and_drop一个Flutter桌面版拖动

作者: JerryFans | 来源:发表于2022-07-30 10:43 被阅读0次

    前言

    我的上一篇文章手把手教你实战Flutter 桌面版-Tinypng(熊猫图片压缩)GUI工具基于Flutter Deskstop 实现初版的图片压缩功能,可以支持macOS、以及windows。但是美中不足的是,macOS下依然要点击选择文件去压缩,而不是像Finder一样随意拖动文件。在文末我也是立了Flag要支持,经过一周时间的调研,顺利实现并且开源了此插件file_drag_and_drop。目前仅支持macOS,由于此功能非常依赖原生桌面,我对Windows Visual Studio编程是在是不熟,Flutter接口已经写好,期待有缘人可以贡献。话不多说,基于此插件,我也对我的图片压缩工具macOS版本做了版本更新,效果如下。

    c126edd73491463a81c8fa8c941c94e4~tplv-k3u1fbpfcp-watermark.image.gif

    插件实现的代码过程解析

    第一步等待初始化window

    由于macOS桌面不像iOS原生可以使用PlatforView. 实际拖动接受文件和iOS差不多,要实现NSView的一个drag协议。 这里用了个取巧的方法,先在flutter端main函数 await一个 initializedMainView初始化方法。我们直接盖一个drop view到 NSWindow上即可。由于用户可能放大缩小窗口,布局就不用frame了,直接用原生约束,也不要SnapKit了,还要导入库,很简单的约束而已。

    Flutter代码

    
    void main() async {
      WidgetsFlutterBinding.ensureInitialized();
      await dragAndDropChannel.initializedMainView();
      runApp(GetMaterialApp(
        navigatorKey: Get.key,
        home: OKToast(
          child: MyApp(),
        ),
      ));
    }
    
    
    

    macOS 原生 Swift代码

    private var mainWindow: NSWindow {
            get {
                return (self.registrar.view?.window)!;
            }
        }
    
    private var mainView: NSView {
            get {
                return self.registrar.view!
            }
        }
        
    private func _initializedMainView() {
            if (!_initialized) {
                _initialized = true
                mainView.addSubview(mainDropView)
                mainDropView.frame = mainView.bounds
                mainDropView.translatesAutoresizingMaskIntoConstraints = false
                mainView.addConstraints(
                    [
                        NSLayoutConstraint(item: mainDropView, attribute: .leading, relatedBy: .equal, toItem: mainView, attribute: .leading, multiplier: 1, constant: 0),
                        NSLayoutConstraint(item: mainDropView, attribute: .trailing, relatedBy: .equal, toItem: mainView, attribute: .trailing, multiplier: 1, constant: 0),
                        NSLayoutConstraint(item: mainDropView, attribute: .top, relatedBy: .equal, toItem: mainView, attribute: .top, multiplier: 1, constant: 0),
                        NSLayoutConstraint(item: mainDropView, attribute: .bottom, relatedBy: .equal, toItem: mainView, attribute: .bottom, multiplier: 1, constant: 0)
                  ]
                )
            }
        }
    

    第二步实现协议

    
    Swift
    
    protocol FlutterDragContainerDelegate {
        func draggingFileEntered()
        func draggingFileExit()
        func prepareForDragFileOperation()
        func performDragFileOperation(_ results : [FileResult])
    }
    
    Flutter 添加监听
    
    abstract class DragContainerListener {
        void draggingFileEntered() {}
        void draggingFileExit() {}
        void prepareForDragFileOperation() {}
        void performDragFileOperation(List<DragFileResult> fileResults) {}
    }
       
    
    

    原生几个重要协议方法,通过Channel 转为Flutter的监听

    Swift

    
    override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
            if let delegate = self.delegate {
                delegate.draggingFileEntered();
            }
            return NSDragOperation.generic
        }
        
        override func draggingExited(_ sender: NSDraggingInfo?) {
            if let delegate = self.delegate {
                delegate.draggingFileExit();
            }
        }
        
        override func prepareForDragOperation(_ sender: NSDraggingInfo) -> Bool {
            if self.delegate != nil {
                self.delegate?.prepareForDragFileOperation()
            }
            return true
        }
        
        override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
            var files = Array<FileResult>()
            if let board = sender.draggingPasteboard.propertyList(forType: NSFilenamesPboardType) as? NSArray {
                for path in board {
                    print(path)
                    if let p = path as? String {
                        let isDirectory = FlutterFileUtil.isDirectory(p)
                        let fileExtension = FlutterFileUtil.fileExtension(p)
                        files.append((path: p,isDirectory: isDirectory, fileExtension: fileExtension))
                    }
                }
            }
            if self.delegate != nil {
                self.delegate?.performDragFileOperation(files)
            }
            return true
        }
    
    

    Flutter端

    
    ObserverList<DragContainerListener>? _listeners =
          ObserverList<DragContainerListener>();
    
      Future<void> _methodCallHandler(MethodCall call) async {
        if (_listeners == null) return;
    
        for (final DragContainerListener listener in listeners) {
          if (!_listeners!.contains(listener)) {
            return;
          }
    
          if (call.method != 'onEvent') throw UnimplementedError();
    
          String eventName = call.arguments['eventName'];
          Map<String, Function> funcMap = {
            kFileDragAndDropEventEntered: listener.draggingFileEntered,
            kFileDragAndDropEventExit: listener.draggingFileExit,
            kFileDragAndDropEventPrepareDragTask:
                listener.prepareForDragFileOperation,
            kFileDragAndDropEventPerformDragTask: listener.performDragFileOperation,
          };
          if (eventName == kFileDragAndDropEventPerformDragTask) {
            List fileResult = call.arguments['fileResult'];
            var resultList = <DragFileResult>[];
            fileResult.forEach((element) { 
              var result = DragFileResult.fromJson(element);
              resultList.add(result);
            });
            funcMap[eventName]!(resultList);
          } else {
            funcMap[eventName]!();
          }
        }
      }
    
    

    第三步Window Home Page添加监听及处理

    @override
      void initState() {
        super.initState();
        dragAndDropChannel.addListener(this);
      }
    
      @override
      void dispose() {
        dragAndDropChannel.removeListener(this);
        super.dispose();
      }
    
    

    flutter监听的处理(相当于触发了原生的协议),这里简单做了个遮罩,拖进去显示,退出隐藏。

    [图片上传失败...(image-b3e241-1659196308997)]

    
    @override
      void draggingFileEntered() {
        print("flutter: draggingFileEntered");
        setState(() {
          visibilityTips = true;
        });
      }
    
      @override
      void draggingFileExit() {
        print("flutter: draggingFileExit");
        setState(() {
          visibilityTips = false;
        });
      }
    
      @override
      void prepareForDragFileOperation() {
        print("flutter: prepareForDragFileOperation");
        setState(() {
          visibilityTips = false;
        });
      }
    
      @override
      void performDragFileOperation(List<DragFileResult> fileResults) {
        print("flutter: performDragFileOperation");
        checkCanPicker().then((canPicker) {
          if (canPicker) {
            var collectionFiles = <File>[];
            fileResults.forEach((element) {
              if (element.isDirectory == false) {
                collectionFiles.add(File(element.path));
              }
              //TODO Also can collect the image file in Directory
            });
            var chooseFiles = chooseImageFiles(collectionFiles);
            if (chooseFiles.isNotEmpty) {
              controller.refreshWithFileList(chooseFiles);
            }
          }
        });
      }
    
    

    源码地址

    未来研究

    此次插件仅实现了macOS从外部拖文件到应用内部,如何从应用内部拖文件去其他地方?由于deskstop版不支持Platform View。这感觉像是变成了一个死循环,还有待研究。另外写作不易,每次写作都耗费了不少时间,如果此文对你有帮助,希望点赞三连,Github也是Star顶起来,感谢🙏。

    相关文章

      网友评论

          本文标题:file_drag_and_drop一个Flutter桌面版拖动

          本文链接:https://www.haomeiwen.com/subject/erahwrtx.html