vyuh_node_flow 是一個剛剛開源的 Flutter Flow 節點編輯器,這是一個基於 MIT 许可的全平台節點/圖形編輯器工具,它提供了類似 React Flow 的一系列功能支持:

基於 vyuh_node_flow ,你可以實現:
可視化編程介面: 創建像 Scratch 或 Unreal Engine Blueprints 那樣的圖形化編程環境

工作流編輯器: 設計和編輯業務流程、數據處理流程或自動化任務

互動式圖表: 構建組織結構圖、思維導圖、狀態機等
數據管道: 可視化地定義和管理數據流和處理步驟
另外,作為一個純粹的 Flutter SDK,它支持所有平台,提供大量開箱即用功能,例如:
StickyAnnotation、標記 MarkerAnnotation)和自定義覆蓋物,註解可以跟隨節點移動NodeFlowViewer 組件,用於僅顯示流程圖,禁止編輯
通過源碼可以看到,這是一個相當完整的 Flow 編輯器,並且它的整體設計思路也很有意思,其中包括:
狀態管理: 它內置直接使用 MobX 自動處理狀態更新和 UI 響應,如節點位置、選擇狀態的變化會自動觸發相關 UI 的重繪
分層渲染: 通過 CustomPaint 和 Stack 實現,編輯器將不同的元素(網格、背景註解、連接線、連接標籤、節點、前景註解、交互元素)渲染在不同的層 (Layer) 中,例如連接標籤在單獨的層中渲染,避免連接線重繪時標籤也重繪
可定制的節點渲染: 通過 nodeBuilder 和 nodeContainerBuilder 允許用戶完全自定義節點的內部內容和外部容器樣式
連接線樣式: ConnectionStyles 提供了多種內置樣式,並且路徑計算邏輯 (ConnectionPathCalculator, SmoothstepPathCalculator 等) 也被抽象出來,便於擴展自定義樣式
連接驗證: 提供了 onBeforeStartConnection 和 onBeforeCompleteConnection 回調,允許開發者在連接開始和完成前進行自定義邏輯驗證,例如檢查端口類型兼容性、防止循環連接等
註解系統: 支持多種類型的註解,並且 GroupAnnotation 可以自動包圍其依賴的節點並跟隨移動,StickyAnnotation 和 MarkerAnnotation 也可以通過 dependencies 跟隨節點
快捷鍵與動作系統: 內置了豐富的快捷鍵操作 (NodeFlowShortcutManager, NodeFlowAction),並且易於擴展和自定義,並且提供了 ShortcutsViewerDialog 來顯示所有可用的快捷鍵。
性能相關優化設計:
Observer 包裹需要響應式更新的 Widget 部分,实现局部刷新RepaintBoundary 隔離複雜的繪製層(如連接線層、節點層),避免不必要的重繪ConnectionPathCache)SpatialIndex) 用於優化大量節點的查詢和命中測試序列化與反序列化: 提供了方便的方法 (toJsonString, fromJsonString, fromUrl, fromAsset) 來保存和加載圖的狀態
也就是,vyuh_node_flow 在項目裡大量使用了 MobX 用於響應式狀態管理,代碼中廣泛使用 Observable, Computed, action, reaction, runInAction 以及 Observer 來實現 UI 和狀態響應,同時放大縮小和平移主要依賴 Flutter 內置的 InteractiveViewer 實現。
相對應的,vyuh_node_flow 也有比較複雜的 API 結果,其中核心 API 有:
NodeFlowController<T>: 核心狀態管理器,負責管理節點、連接、註解、視口、配置、主題和交互狀態,它通過使用 MobX 的 Observable 來實現響應式更新NodeFlowEditor<T>: 主要的編輯器控件,接收 NodeFlowController,負責渲染畫布、節點、連接、註解,並處理用戶交互(拖拽、連接、選擇、平移、縮放)NodeFlowViewer<T>: 只讀模式的 Widget,基於 NodeFlowEditor 但禁用了編輯功能Node<T>: 代表畫布上的一個節點,包含 ID、類型、位置、尺寸、數據 (T) 以及輸入/輸出端口列表,而位置 (position) 和視覺位置 (visualPosition) 是分離的 Observable,後者用於應用網格吸附後的渲染Port: 定義節點的連接點,包含 ID、名稱、位置 (PortPosition)、形狀 (PortShape)、類型 (PortType,如 source/target/both)、是否允許多重連接等屬性Connection: 代表節點間的一條連接線,包含 ID、源/目標節點 ID、源/目標端口 ID,以及可選的標籤 (label, startLabel, endLabel) 和樣式 (style)Annotation: 註解的基類,子類包括 StickyAnnotation (便簽)、GroupAnnotation (節點分組)、MarkerAnnotation (標記),註解可以有自己的位置、尺寸、樣式,並且可以通過 dependencies 相關聯節點NodeFlowTheme: 定義編輯器所有視覺元素的樣式,包括節點、連接線、端口、背景、網格、選擇框等的顏色、大小、形狀、字體等,包含 NodeTheme, ConnectionTheme, PortTheme, LabelThemeNodeFlowConfig: 控制編輯器的行為,如是否啟用網格吸附、小地圖、縮放範圍、自動平移等NodeGraph<T>: 用於序列化和反序列化的數據結構,包含節點、連接、註解和視口狀態運行庫提供的 MVP 示例,可以看到使用起來也不是很複雜,通過向 controller 添加兩個 Node ,然後添加到 NodeFlowEditor ,並通過 _buildNode 渲染需要的節點,就可以得到一個可交互連接的 Flow 顯示:
import 'package:flutter/material.dart';
import 'package:vyuh_node_flow/vyuh_node_flow.dart';
class SimpleFlowEditor extends StatefulWidget {
@override
State<SimpleFlowEditor> createState() => _SimpleFlowEditorState();
}
class _SimpleFlowEditorState extends State<SimpleFlowEditor> {
late final NodeFlowController<String> controller;
@override
void initState() {
super.initState();
// 1. Create the controller
controller = NodeFlowController<String>();
// 2. Add some nodes
controller.addNode(Node<String>(
id: 'node-1',
type: 'input',
position: const Offset(100, 100),
data: 'Input Node',
outputPorts: const [Port(id: 'out', name: 'Output')],
));
controller.addNode(Node<String>(
id: 'node-2',
type: 'output',
position: const Offset(400, 100),
data: 'Output Node',
inputPorts: const [Port(id: 'in', name: 'Input')],
));
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: NodeFlowEditor<String>(
controller: controller,
theme: NodeFlowTheme.light,
nodeBuilder: (context, node) => _buildNode(node),
),
);
}
Widget _buildNode(Node<String> node) {
return Container(
padding: const EdgeInsets.all(16),
child: Text(node.data),
);
}
}
根據前面的 API ,我們可以簡單拼接出 vyuh_node_flow 的實現原理:
NodeFlowController 作為中心樞紐,存儲所有狀態 (節點、連接、視口等) 的 Observable 實例NodeFlowEditor
Stack 組織不同的渲染層 (GridLayer, ConnectionsLayer, NodesLayer, AnnotationLayer 等)NodesLayer 和 AnnotationLayer 使用 Positioned 和 Observer 來渲染每個節點/註解,只在位置等 Observable 變化時重繪ConnectionsLayer 和 GridLayer 使用 CustomPaint 和 Observer 來繪製連接線和網格,響應節點位置和視口的變化NodeFlowEditor 使用 Listener, GestureDetector, MouseRegion 監聽原始指針事件
_performHitTest) 以確定交互物件(畫布、節點、端口、連接、註解),然後調用 NodeFlowController 中對應的內部方法 (_startNodeDrag, _moveNodeDrag, _startConnection, _completeConnection, _updateSelectionDrag 等)來更新 MobX 狀態Observer Widget 的重繪ConnectionPainter 負責繪製連接線
ConnectionPathCache 來緩存計算出的 Path 對象ConnectionStyle 實現)並更新緩存NodeFlowController 的方法進行,內部使用 runInAction 來確保 MobX 的原子更新
從代碼結構和使用的技術來看,vyuh_node_flow 也採用了多種提升性能的策略:
Stack 將網格、連接線、節點、註解等分層繪製,並通過 RepaintBoundary 隔離複雜的繪製層,減少不必要的重繪範圍ConnectionPathCache):緩存連接線的計算路徑 (Path) 和用於命中測試的路徑,避免在每次重繪或交互時重複計算SpatialIndex): (lib/shared/spatial_index.dart, lib/shared/node_spatial_adapter.dart) 使用基於網格的空間索引,來快速查詢可見區域內的節點或與特定區域重疊的節點,這對於節點數量較多時的性能至關重要,避免了遍歷所有節點進行可見性判斷或命中測試總的來說,vyuh_node_flow 是一個功能豐富、設計良好且注重性能的 Flutter 節點編輯器庫,特別適合需要高度自定義和交互性的可視化流程編輯場景,儘管目前還缺失一些複雜的圖形自動佈局算法,但是可用性已經相當成熟,至少對於 Flutter 開發者來說,這是一個不錯的支持。
根據作者的說法,這也是一個致敬 React Flow 的項目