介紹 Flutter 桌面應(yīng)用 NativeShell

image

貓哥說

看到這張圖片,我就感覺脖子酸。。。我這樣擺過,雖然看起來很 cool,然后你的脖子要上下調(diào)整,這比左右調(diào)整費事。

今天推薦閱讀的是關(guān)于 Flutter 桌面開發(fā),下面有原文、代碼 Git 鏈接。

開始前我想起 React Native 說過的一句話 《用同樣的方法在多端開發(fā)》,這句話很玄妙又很正確,就是說不是讓你同一套代碼同時能編譯成 ios android web windows linux ...,只是代碼的高度復(fù)用,所以有的同學(xué)在做多端架構(gòu)時就要注意拆包了。

比如 你的業(yè)務(wù)代碼、API、Entity、功能函數(shù) 很多情況下是復(fù)用的。但是各個平臺的底層、交互體驗是個性化。

不要被在 ios android 兩端平滑兼容所欺騙。

今天介紹 NativeShell 這個桌面程序,這個程序演示了 多窗口、模式對話框、拖拽、菜單、工具欄、文件操作、系統(tǒng) API 調(diào)用的例子,如果你正在做研究,這是一個很好參考。

老鐵記得 轉(zhuǎn)發(fā) ,貓哥會呈現(xiàn)更多 Flutter 好文~~~~

微信群 ducafecat

原文

https://matejknopp.com/post/introducing-nativeshell/

視頻

代碼

https://github.com/nativeshell/app_template

參考

正文

自從我第一次看到 Turbo Vision,我就對桌面應(yīng)用程序感興趣。DOS 中那些文本模式可調(diào)整大小的窗口對我來說就像魔術(shù)一樣。它激發(fā)了人們對用戶界面框架的興趣,這種框架在 20 多年后依然很強大。

在過去十年左右的時間里,人們的注意力主要轉(zhuǎn)移到了網(wǎng)絡(luò)和移動設(shè)備上,這并沒有讓我感到特別高興。所以我覺得是時候從陰影中爬出來,離開我的舒適區(qū),試著把一些聚光燈帶回它應(yīng)該在的地方。到桌面上去!:)

Flutter 之路

我最后一個處理的桌面應(yīng)用程序是(現(xiàn)在仍然是) Airflow。它是 Qt 和大量平臺特定代碼的混合體。如果我自己這么說的話,我對最終的結(jié)果非常滿意,但是開發(fā)人員的經(jīng)驗和整體生產(chǎn)力還有很多需要改進的地方。

http://airflow.app/

大約兩年前,我需要一個適用于 iOS 和安卓系統(tǒng)的氣流應(yīng)用程序。經(jīng)過幾個原型后,決定做出,我去 Flutter。我確實喜歡認為我有自己的 UI 開發(fā)經(jīng)驗,畢竟,我在不同的平臺上使用過十幾個 GUI 框架,所以現(xiàn)在沒有什么能讓我感到驚訝的了,對吧?錯了。他們所有人中最大的驚喜就是和 Flutter 一起工作的感覺是多么的好。在我的一生中,從來沒有,一次也沒有,建立用戶界面這么有意義。

https://airflow.app/remote-app/

如果我能用這種方式構(gòu)建桌面應(yīng)用程序,豈不是很神奇?當然,這是可能的,但現(xiàn)實是一個嚴酷的情婦,在那個時候桌面嵌入仍然處于嬰兒期。那就回到 Qt 吧。但這個念頭一直在我腦海中揮之不去。

一兩年過去了,很多事情發(fā)生了變化。還有很多工作要做,但是桌面嵌入已經(jīng)成熟了很多,而且 Flutter on desktop 已經(jīng)開始成為一個可行的選擇。

Flutter desktop 嵌入

現(xiàn)在你可能會問: Matt,F(xiàn)lutter 不是已經(jīng)嵌入了桌面嗎? 那么這一切都是為了什么呢?

是的,的確如此。NativeShell 就建立在他們之上。您可以將 Flutter 桌面嵌入程序想象為一個平臺視圖組件(想想 GtkWidget、 NSView 或者,恕我直言,HWND)。它處理鼠標和鍵盤輸入,繪畫,但它不嘗試管理窗口,或 Flutter 引擎/隔離。或者做一些像平臺菜單和拖放的事情。為了使事情更加復(fù)雜,F(xiàn)lutter 嵌入在每個平臺上都有完全不同的 API。因此,如果您希望為一些低級代碼創(chuàng)建引擎或注冊平臺通道處理程序,則需要為每個平臺分別執(zhí)行此操作。

https://nativeshell.dev/

這就是 NativeShell 介入的地方

從 Flutter 桌面嵌入結(jié)束的地方開始。它為現(xiàn)有的 Flutter 嵌入提供了一個一致的、與平臺無關(guān)的 API。它管理引擎和窗戶。它提供了拖放支持,對平臺菜單的訪問,以及其他超出 Flutter 嵌入范圍的功能。并且它通過簡單易用的 Dart API 公開了所有這些。

NativeShell 是用鐵銹寫的。銹是偉大的,因為它可以讓你編寫高效的低級平臺特定的代碼,如果你需要,但它也讓你使用 NativeShell,而不必知道任何銹。簡單地執(zhí)行貨物運輸是所有需要讓事情進行。Cargo 是 Rust 軟件包管理器(就像 pub 是用于 Dart 的) ,它負責(zé)下載和構(gòu)建所有依賴項。

開始

$ flutter config --enable-windows-desktop
$ flutter config --enable-macos-desktop
$ flutter config --enable-linux-desktop
  • Switch to Flutter Master
$ flutter channel master
$ flutter upgrade

在這之后,你應(yīng)該可以開始了:

$ git clone https://github.com/nativeshell/examples.git
$ cd examples
$ cargo run

NativeShell 透明地集成了 Flutter 建立過程和貨物。如果鐵銹和飛鏢之神在對你微笑,這就是你現(xiàn)在應(yīng)該看到的:

image

Platform Channels

如果您需要從 Flutter 應(yīng)用程序調(diào)用本機代碼,這兩個選項是平臺通道或 FFI。對于一般使用的平臺通道是預(yù)先設(shè)計好的,因為它們更容易使用,并且能夠在平臺和 UI 線程之間適當?shù)貍鬟f消息。

這就是使用 NativeShell 注冊平臺通道處理程序的效果(我保證,這里也是惟一的 Rust 代碼)

fn register_example_channel(context: Rc<Context>) {
    context
        .message_manager
        .borrow_mut()
        .register_method_handler("example_channel", |call, reply, engine| {
            match call.method.as_str() {
                "echo" => {
                    reply.send_ok(call.args);
                }
                _ => {}
            }
        });
}

為了直接使用現(xiàn)有的平臺嵌入 API 來完成這項工作,您需要使用平臺特定的 API 為每個平臺分別編寫這些代碼。然后確保每次創(chuàng)建新引擎時都注冊處理程序(關(guān)閉引擎時可能注銷)。

使用 NativeShell,您只需注冊處理程序一次,它可以從任何引擎調(diào)用。消息可以通過 Serde 被透明地序列化和反序列化為 Rust 結(jié)構(gòu)(使用 Flutter 的標準方法編解碼格式)。

https://github.com/nativeshell/examples/blob/main/src/file_open_dialog.rs#L18

Window Management

想必你希望你的桌面應(yīng)用程序有多個窗口?NativeShell 已經(jīng)掩護你了。調(diào)整窗口大小到內(nèi)容或設(shè)置最小的窗口大小,使 Flutter 布局不底流?它也能做到這一點。它還確保只在內(nèi)容準備好后才顯示窗口,從而消除了丑陋的閃爍。

目前,每個窗口作為單獨的獨立窗體運行。NativeShell 為創(chuàng)建窗口、設(shè)置和調(diào)整幾何形狀、更新樣式和窗口標題提供了 API。它還提供了便于在窗口之間進行通信的 API。

視頻可以根據(jù)內(nèi)容調(diào)整大小,也可以根據(jù)內(nèi)容大小調(diào)整大小。

  • 多窗口
image
  • 模式對話框
image

這將是 Dart 中如何創(chuàng)建和管理多個窗口的最小演示:

void main() async {
  runApp(MinimalApp());
}

class MinimalApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Widgets above WindowWidget will be same in all windows. The actual
    // window content will be determined by WindowState
    return MaterialApp(
      home: WindowWidget(
        onCreateState: (initData) {
          WindowState? context;
          context ??= OtherWindowState.fromInitData(initData);
          // possibly no init data, this is main window
          context ??= MainWindowState();
          return context;
        },
      ),
    );
  }
}

class MainWindowState extends WindowState {
  @override
  Widget build(BuildContext context) {
    return TextButton(
      onPressed: () async {
        // This will create new isolate for a window. Whatever is given to
        // Window.create will be provided by WindowWidget in new isolate
        final window = await Window.create(OtherWindowState.toInitData());
        // you can use the window object to communicate with newly created
        // window or register handlers for window events
        window.closeEvent.addListener(() {
          print('Window closed');
        });
      },
      child: Text('Open Another Window'),
    );
  }
}

class OtherWindowState extends WindowState {
  @override
  Widget build(BuildContext context) {
    return Text('This is Another Window!');
  }

  // This can be anything that fromInitData recognizes
  static dynamic toInitData() => {
        'class': 'OtherWindow',
      };

  static OtherWindowState? fromInitData(dynamic initData) {
    if (initData is Map && initData['class'] == 'OtherWindow') {
      return OtherWindowState();
    }
    return null;
  }
}

Drag & Drop

很難想象還有哪個桌面用戶界面框架不支持拖放操作。NativeShell 支持拖放文件路徑、 url、自定義 Dart 數(shù)據(jù)(由 StandardMethodCodec 實現(xiàn)序列化) ,甚至可以擴展以處理自定義平臺特定格式。

它應(yīng)該很容易使用,而且我對它的結(jié)果很滿意,盡管它確實涉及到編寫一些看起來非常嚇人的代碼。

image

https://github.com/nativeshell/examples/blob/main/lib/pages/drag_drop.dart

https://github.com/nativeshell/nativeshell/blob/main/nativeshell/src/shell/platform/win32/drag_com.rs

Popup Menu

很多框架和應(yīng)用程序都犯了這樣的錯誤,這常常讓我感到驚訝。直到最近 Firefox 才開始在 macOS 上使用本地彈出菜單。無論你的應(yīng)用程序多么精致,如果你的菜單出錯了,你就會感覺不對勁。

允許你輕松地創(chuàng)建和顯示上下文菜單。考慮到菜單系統(tǒng)的強大功能,這個菜單 API 看似簡單。菜單是反應(yīng)性的。你可以要求重建菜單,而可見和 NativeShell 將計算三角洲和只更新菜單項實際上已經(jīng)改變。

image
  int _counter = 0;

  void _showContextMenu(TapDownDetails e) async {
    final menu = Menu(_buildContextMenu);

    // Menu can be updated while visible
    final timer = Timer.periodic(Duration(milliseconds: 500), (timer) {
      ++_counter;
      // This will call the _buildContextMenu() function, diff the old
      // and new menu items and only update those platform menu items that
      // actually changed
      menu.update();
    });

    await Window.of(context).showPopupMenu(menu, e.globalPosition);

    timer.cancel();
  }

  List<MenuItem> _buildContextMenu() => [
        MenuItem(title: 'Context menu Item', action: () {}),
        MenuItem(title: 'Menu Update Counter $_counter', action: null),
        MenuItem.separator(),
        MenuItem.children(title: 'Submenu', children: [
          MenuItem(title: 'Submenu Item 1', action: () {}),
          MenuItem(title: 'Submenu Item 2', action: () {}),
        ]),
      ];

MenuBar

可能是 NativeShell 中我最喜歡的功能。在 macOS 上,它呈現(xiàn)為空窗口小部件,而是將菜單放在系統(tǒng)菜單欄(在屏幕頂部)。在 Windows 和 Linux 上,它使用 Flutter 小部件呈現(xiàn)頂級菜單項,然后使用本機菜單處理其余部分。這意味著菜單欄可以位于小部件層次結(jié)構(gòu)中的任何位置,它不局限于窗口的頂部,也不依賴于 GDI 或 Gtk 來繪制 iself。

它支持鼠標跟蹤和鍵盤導(dǎo)航,就像普通系統(tǒng)菜單欄一樣,但沒有任何限制。

image

現(xiàn)在情況如何

NativeShell 正在被沉重的開發(fā)。事情很可能會破裂。迫切需要更多的文檔和示例。但我認為它的形狀可能對某些人有用。

所有三個支持的平臺(macOS,Windows,Linux)都具有完全的同等功能。

https://github.com/nativeshell/nativeshell/tree/main/nativeshell/src/shell/platform/macos

https://github.com/nativeshell/nativeshell/tree/main/nativeshell/src/shell/platform/win32

https://github.com/nativeshell/nativeshell/tree/main/nativeshell/src/shell/platform/linux

如果你一路走到了這里,你可以繼續(xù) nativeshell.dev。

https://nativeshell.dev/

感謝您的反饋!

https://github.com/nativeshell/nativeshell/issues


? 貓哥

https://ducafecat.tech/

https://github.com/ducafecat

往期

開源

GetX Quick Start

https://github.com/ducafecat/getx_quick_start

新聞客戶端

https://github.com/ducafecat/flutter_learn_news

strapi 手冊譯文

https://getstrapi.cn

微信討論群 ducafecat

系列集合

譯文

https://ducafecat.tech/categories/%E8%AF%91%E6%96%87/

開源項目

https://ducafecat.tech/categories/%E5%BC%80%E6%BA%90/

Dart 編程語言基礎(chǔ)

https://space.bilibili.com/404904528/channel/detail?cid=111585

Flutter 零基礎(chǔ)入門

https://space.bilibili.com/404904528/channel/detail?cid=123470

Flutter 實戰(zhàn)從零開始 新聞客戶端

https://space.bilibili.com/404904528/channel/detail?cid=106755

Flutter 組件開發(fā)

https://space.bilibili.com/404904528/channel/detail?cid=144262

Flutter Bloc

https://space.bilibili.com/404904528/channel/detail?cid=177519

Flutter Getx4

https://space.bilibili.com/404904528/channel/detail?cid=177514

Docker Yapi

https://space.bilibili.com/404904528/channel/detail?cid=130578

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容