Flutter 多窗口支持类型和 API 介绍 这么多年了官方终于正式把多窗口 API 单独拿出来说了两年前 Canonical 开始负责 Flutter PC 端的开发和维护之后多窗口也一直是它在推进目前的完成的设计主要围绕五种窗口类型展开 regular、dialog、tooltip、 popup 和 satellite 窗口其中普通的 Regular 常规窗口就不用说了基本默认的多窗口下都会用到类似的窗口能力其次是 Popup 窗口Popup 主要提供类似独立窗口菜单的功能属于子窗口可以有输入焦点用户可以通过方向键在下拉菜单里切换选中目标另外还会内部还会强制要求 Popup 窗口保持可见不会因为弹出位置导致窗口出现在屏幕之外被裁剪然后就是 Tooltip它和于弹出窗口很类似区别在于它们没有输入焦点一般用来展示一些提示信息比如当鼠标悬停在某个控件上时就提示控件的作用Dialog 也很常见属于子窗口分模态和非模态当对话框通过模态方式显示在另一个窗口里面时 对话框窗口关闭之前窗口会禁止获得焦点。Satellite 窗口也是月中很常见的辅助弹出框它可以保持相对于父窗口的位置 父窗口移动和调整大小时它们也会跟随变化 Satellite 还有具备对接能力也就是它们可以从漂浮式 Satellite 窗口转到嵌入到主窗口内部而这里这些窗口类型目前都存在于一个窗口层级结构里比如一个 App 可以在它 Root 里设置一个主窗口普通窗口)然后在它下面嵌套 Pop 窗口和 Dialog 对话框内部还可以嵌套 ToolTip。目前这些多窗口 API 已经在 master 可以用我自己也用了一段时间其实用 main 分支没什么不好的flutter channel main flutter upgrade flutter config --enable-windowing配置后你就可以创建原生窗口要创建一个窗口我们首先需要创建一个WindowController这个 controller 主要和底层平台交互用来创建和更新窗口final controller WindowController( title: My Application, size: const Size(800, 600), );控制器会收到窗口的初始配置信息比如窗口大小和标题等然后每种窗口类型都有自己的窗口控制器比如如果要创建一个对话框窗口final dialogController DialogWindowController( title: My Dialog, size: const Size(400, 300), parent: parentController, );这里和普通窗口不同在于DialogWindowController有一个可选的父窗口控制器。然后控制器创建完成后就可以直接用于后续修改窗口例如我们可以修改之前创建的常规窗口的标题、大小和销毁controller.setTitle(Hello, world!); controller.setSize(const Size.square(1000)); controller.destroy();然后就是渲染到窗口这里需要同样需要将控制器和要渲染的内容传递给Window组件Widget build(BuildContext context) { return Window( controller: controller, child: MyPage(), ); }每种窗口类型都有其对应的控件比如对于普通窗口用Window控件对于对话框窗口用DialogWindow控件然后因为有窗口都位于同一个控件树所以实际上可以做到跨窗口共享状态。然后如果你需要在窗口监听事件比如接受窗口关闭通知目前有两种获取窗口状态的方式通过WindowControllerDelegate或者WindowScope比如// Create the class first... class MyWindowDelegate with WindowControllerDelegate { override void onWindowDestroyed() { super.onWindowDestroyed(); ServicesBinding.instance.exitApplication(AppExitType.required); } } ​ // and then pass it to the controller constructor. final controller WindowController( title: My Application, size: const Size(800, 600), delegate: MyWindowDelegate(), ); ​另外通过WindowScope也可以做到类似监听你可以通过WindowScope.of访问 Scope 然后获取对应状态class MyWidget extends StatelessWidget { override Widget build(BuildContext context) { final title WindowScope.titleOf(context); // ... do something with the window title } }整个 Demo 大概类似// ignore_for_file: invalid_use_of_internal_member // ignore_for_file: implementation_imports ​ import dart:ui; ​ import package:flutter/services.dart; import package:flutter/src/widgets/_window.dart; import package:flutter/widgets.dart; ​ /// Exits the application when the user closes the window. class ExitOnCloseDelegate with WindowControllerDelegate { override void onWindowCloseRequested(WindowController controller) { ServicesBinding.instance.exitApplication(AppExitType.required); } } ​ void main() { WidgetsFlutterBinding.ensureInitialized(); runWidget(const HelloWindow()); } ​ /// Displays a window and owns its [WindowController]. class HelloWindow extends StatefulWidget { const HelloWindow({super.key}); ​ override StateHelloWindow createState() _HelloWindowState(); } ​ class _HelloWindowState extends StateHelloWindow { final WindowController _controller WindowController( size: const Size(600, 400), title: MyApp, delegate: ExitOnCloseDelegate(), ); ​ override void dispose() { _controller.dispose(); super.dispose(); } ​ override Widget build(BuildContext context) { return Window( controller: _controller, child: const Directionality( textDirection: TextDirection.ltr, child: ColoredBox( color: Color(0xFFFFFFFF), child: Center( child: Text( Hello, Window, style: TextStyle(color: Color(0xFF000000), fontSize: 24), ), ), ), ), ); } }不过需要注意的是多窗口现在用的不是runApp你需要用runWidget替代void main() { WidgetsFlutterBinding.ensureInitialized(); runWidget(const MultiWindowApp()); }总体来说目前 Flutter 的多窗口从我的体验上已经可以直接用了虽然有小问题但是不会很影响主流程至少 Window 和 macOS 上还行Linux 我就没测试过了。链接https://flutter.dev/to/windowing-exampleIntroducing the Desktop Windowing API for Flutter | The Flutter Blog