
Flipper Zero 应用开发中 Dialogs、ViewPort 与 Views/Scenes 三种 GUI 架构怎么选【免费下载链接】flipperzero-firmwareFlipper Zero firmware source code项目地址: https://gitcode.com/GitHub_Trending/fl/flipperzero-firmware写 Flipper Zero 应用时第一个要定的技术决策是 GUI 用哪套架构只用 Dialogs、直接操作 ViewPort 绘制还是走 Views/Scenes 分层模型。选错了要么接口简单到撑不住你的界面要么为两三个提示框背上一整套 Scene Manager。官方文档在 GUI in Apps 中给出的判断依据是一句话你想创建的界面复杂度决定了你需要多少层 GUI 抽象。本文按这三种架构逐个说明适用条件、优缺点并给出仓库中对应的可运行代码示例最后给出验证方式构建、上传、在设备上运行。适用前提你已有 Flipper Zero 设备、USB 数据线并在电脑上安装了 uFBTmicro Flipper Build Tool和 VS Code环境搭建步骤见 Your first app in C。先理解三个基础概念三种架构建立在同一组底层概念上选架构前先确认这几个术语来自 GUI in AppsEventLoop异步事件处理机制事件按键、收包等发生时调用你注册的处理函数主循环无需轮询。Canvas没有任何抽象的原始绘图区域直接绘制适合自绘设计元素比如游戏官方说明这种用法“并不常见”。ViewPort定义某块屏幕区域有位置和大小的渲染与行为。多个 ViewPort 组成绘制栈Drawing Stack自底向上渲染因此可以相互覆盖ViewPort 创建后还可以单独开启或关闭显示。Model / View / View DispatcherModel 是数据内存中的结构体View 负责界面渲染与事件处理并使用 Model当应用需要多个 View 时用View Dispatcher持有所有 View 的引用并在它们之间切换。Scene Scene ManagerScene 管理应用“单个屏幕”内的行为。每个 scene 实现包含on_enter初始化该场景的 views、on_exit反初始化并释放资源、on_event处理外部事件三个函数。外部事件处理可以触发切换到另一个 sceneScene Manager 负责切换简化复杂导航流程。固件内置了一批全屏 View 模块ViewModel 模板Views/Scenes 路线可直接复用button_menu、button_panel、byte_input、dialog_ex、empty_screen、file_browser、loading、menu、number_input、popup、submenu、text_box、text_input、variable_item_list、widget源码位于 gui/modules。方案一Dialogs —— 界面能全部用对话框表达时选它适用条件直接来自文档整个界面可以只用 dialogs 实现时这是最简单的应用 GUI 方案。Dialog API 函数位于头文件 dialogs.h。**优点**简单、易用。缺点选型时必须权衡无法构建复杂 GUI只有 Dialogs 可用Dialogs 是同步运行的——在用户在对话框上按键之前应用的其他代码不会执行。也就是说它不适合需要边显示界面边持续跑逻辑的场景。仓库中可直接对照的最小示例与 Your first app in C 第 4 步一致#include furi.h #include dialogs/dialogs.h #include test_app_icons.h int32_t test_app(void* p) { UNUSED(p); DialogsApp* dialogs furi_record_open(RECORD_DIALOGS); DialogMessage* message dialog_message_alloc(); dialog_message_set_header(message, Hello world, 64, 20, AlignCenter, AlignTop); dialog_message_set_text(message, Im test_app!, 64, 32, AlignCenter, AlignTop); dialog_message_show(dialogs, message); dialog_message_free(message); furi_record_close(RECORD_DIALOGS); return 0; }要点dialog_message_show会阻塞到你按键返回如果应用主体是持续逻辑、对话框只是偶发的确认/提示用它没问题如果整个 UI 由多个界面切换组成Dialogs 撑不住。方案二ViewPort —— 需要自绘画面、追求帧率时选它这个方案创建一个全屏 ViewPort用它渲染屏幕并处理所有按键输入即直接“画屏 收事件”不引入 View/Scene 等更高层抽象。优点简单只用一个 ViewPort 实例没有更复杂的 GUI 抽象快、适合做游戏对屏幕上发生的一切有完全控制。缺点没有导航辅助navigation helpers没有可复用的 Views。文档列出的现有使用场景多数调试工具applications/debug、BLE 配对对话框bt.c、Dolphin Passportpassport.c、以及大多数游戏。仓库中的两个最小参考实现可以按需取用direct_draw.c最“裸”的路线——furi_record_open(RECORD_GUI)后通过gui_direct_draw_acquire直接拿 Canvas订阅RECORD_INPUT_EVENTS监听 Back 键退出主循环里canvas_reset→ 绘制 →canvas_commit并用 DWT 周期计数器自算 FPS 显示。example_view_holder.c介于裸绘制和 Views/Scenes 之间的折中——用ViewHolder把一个现成的 View例如TextBox适配到 GUI 能显示的 ViewPort 上Gui* gui furi_record_open(RECORD_GUI); TextBox* text_box text_box_alloc(); text_box_set_text(text_box, ...); ViewHolder* view_holder view_holder_alloc(); view_holder_attach_to_gui(view_holder, gui); view_holder_set_view(view_holder, text_box_get_view(text_box)); // ... 用 FuriApiLock 阻塞应用线程Back 键回调中解锁退出 ...该示例注释明确提醒真实应用在这里“很可能会有一个事件处理循环”并指回 ViewDispatcher 示例。也就是说 ViewHolder 适合“单屏 少量逻辑”多屏导航还是要升到下一档。方案三Views/Scenes —— 复杂分层 UI 的主力路线这是开发复杂、分层 UI 应用的主方案大多数 Flipper Zero 应用采用这种方法官方点名的例子包括桌面desktop、iButton、Infrared、GPIO、NFC、RFID、Sub-GHz 应用。**优点**支持复杂导航View 可回收复用可复用 view modules 数量多。 **缺点**上手需要时间实现复杂。最小 ViewDispatcher 应用example_view_dispatcher.c 展示了这条路线的完整骨架一个 Widget 视图和一个 Submenu 视图按中间键或选中菜单项互相切换按 Back 退出。关键 API 调用序列Gui* gui furi_record_open(RECORD_GUI); app-widget widget_alloc(); app-submenu submenu_alloc(); app-view_dispatcher view_dispatcher_alloc(); view_dispatcher_attach_to_gui(app-view_dispatcher, gui, ViewDispatcherTypeFullscreen); // 每个 view 用枚举索引注册本例ViewIndexWidget / ViewIndexSubmenu view_dispatcher_add_view(app-view_dispatcher, ViewIndexWidget, widget_get_view(app-widget)); view_dispatcher_add_view(app-view_dispatcher, ViewIndexSubmenu, submenu_get_view(app-submenu)); // 视图内不直接跳转而是发 custom event由统一回调切换 view_dispatcher_set_custom_event_callback( app-view_dispatcher, example_view_dispatcher_app_custom_event_callback); view_dispatcher_set_navigation_event_callback( app-view_dispatcher, example_view_dispatcher_app_navigation_callback); view_dispatcher_set_event_callback_context(app-view_dispatcher, app); view_dispatcher_switch_to_view(app-view_dispatcher, ViewIndexWidget); view_dispatcher_run(app-view_dispatcher); // 阻塞直到 view_dispatcher_stop()从示例代码注释中值得记住的两条纪律视图回调里不直接切换视图而是view_dispatcher_send_custom_event发事件由 custom event 回调统一view_dispatcher_switch_to_view释放顺序有硬性要求必须先view_dispatcher_remove_view移除所有已注册视图再view_dispatcher_free否则会崩溃最后再 free 各 view 和furi_record_close(RECORD_GUI)。Scene 的落地形态Scene Manager 路线在仓库中有真实参照gpio_scene_start.c 对应文档中提到的 GPIO 应用起始菜单场景可以看到三个函数各干什么void gpio_scene_start_on_enter(void* context) { GpioApp* app context; VariableItemList* var_item_list app-var_item_list; // 初始化场景内的 view注册 enter/change 回调、添加条目 variable_item_list_set_enter_callback( var_item_list, gpio_scene_start_var_list_enter_callback, app); variable_item_list_add(var_item_list, USB-UART Bridge, 0, NULL, NULL); // ... view_dispatcher_switch_to_view(app-view_dispatcher, GpioAppViewVarItemList); } bool gpio_scene_start_on_event(void* context, SceneManagerEvent event) { // 处理外部事件例如切换到手动控制场景 if(event.event GpioStartEventManualControl) { scene_manager_set_scene_state(app-scene_manager, GpioSceneStart, GpioItemTest); scene_manager_next_scene(app-scene_manager, GpioSceneTest); } // ... } void gpio_scene_start_on_exit(void* context) { GpioApp* app context; variable_item_list_reset(app-var_item_list); }on_event中典型的导航模式先用scene_manager_set_scene_state记住“从哪个选项进入”返回时可以恢复选中项再scene_manager_next_scene跳到目标场景。文档中列出的 GPIO 场景集合gpio_scene_start起始菜单、gpio_scene_usb_uartUSB-UART 桥模式、gpio_scene_usb_uart_cfg模式设置、gpio_scene_usb_uart_close退出确认、gpio_scene_test手动控制都能在 applications/main/gpio/scenes 目录中一一对应。选型对照与常见搭配把文档中的优缺点并排放在一起判断标准其实很直接判断问题结论整个界面是否只用对话框就能表达是 →Dialogs注意其同步阻塞特性是否需要自绘画面/逐帧渲染游戏、调试可视化且不要导航框架是 →ViewPort或 ViewHolder 挂单个现成 View是否多屏、有层级导航、要复用 submenu/widget 等模块是 →Views/Scenes三者不是互斥的Views/Scenes 路线的视图内部仍然是 ViewPortDialogs 服务RECORD_DIALOGS也可以在复杂应用里单独打开用于确认框——文档中 GPIO 应用用gpio_scene_usb_uart_close这类“确认对话框场景”处理退出即两种路线在真实应用中共存。验证构建、上传并确认界面生效三条路线的代码都遵循同一套验证流程来自 Your first app in C用 uFBT 生成模板工程APPID只能包含a-z、0-9和_ufbt create APPIDtest_app ufbt vscode_dist把上文所选架构的代码写进test_app.c模板结构含application.fam清单、主文件、图标和images/目录。USB 连接设备后执行ufbt launch它会构建、上传并启动应用——此时在设备上确认所选架构的行为符合预期Dialogs显示头/文本并阻塞到按键ViewPort自绘画面持续刷新、Back 退出Views/Scenes按 Back 走 navigation 回调退出、按键/菜单项触发场景切换。若代码里有FURI_LOG_I日志用ufbt cli打开控制台执行log或改过默认日志级别时用log info查看。一个文档明确指出的失败现象启动应用时报SDK version mismatch说明设备固件 SDK 版本与 uFBT 使用的 SDK 版本不一致用ufbt flash_usb刷写设备固件解决。也可以走手动路径用ShiftCtrlBWin/Linux或ShiftCmdBMac构建出 FAP经 qFlipper 的 File Manager 放入 SD 卡apps/Examples目录后在设备上从 Apps → Examples 启动。边界与限制Dialogs 的同步阻塞意味着选它之前先确认你的应用没有“边显示对话框边必须继续执行”的逻辑否则换 ViewPort 或 Views/Scenes。ViewPort 路线“没有导航辅助、没有可复用 View”是特性不是 bug界面越复杂你手写的状态机越多这正是 Views/Scenes 要解决的问题。Views/Scenes 路线的代价是学习成本和实现复杂度文档原话是“Takes time to sink in / Complex implementation”官方指向的外部教程资源Scenes Demo 应用教程、ViewPort 与 SceneManager 的系列视频列在 GUI in Apps 的 Learn more 一节。释放顺序这类约束先移除 view 再 free ViewDispatcher是文档注释中明确写出的硬性要求违反会崩溃。【免费下载链接】flipperzero-firmwareFlipper Zero firmware source code项目地址: https://gitcode.com/GitHub_Trending/fl/flipperzero-firmware创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考