ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Flipper Zero 固件 JS SDK:gui/loading 加载视图模块详解(原理、源码与实战)

Flipper Zero 固件 JS SDK:gui/loading 加载视图模块详解(原理、源码与实战) Flipper Zero 固件 JS SDKgui/loading 加载视图模块详解原理、源码与实战【免费下载链接】flipperzero-firmwareFlipper Zero firmware source code项目地址: https://gitcode.com/GitHub_Trending/fl/flipperzero-firmware本文围绕 Flipper Zero 固件仓库中的 JS SDKgui/loading视图模块展开结合documentation/js/js_gui__loading.md官方文档与仓库源码讲清这一“沙漏加载屏”视图的作用边界为什么按 BACK 键无法退出、零属性的设计原因以及它在event_loop → gui → gui/loading模块依赖链中的正确导入方式。读完本文你可以在 JS 应用里可靠地展示一个不可交互的动画加载页并用定时器自动切回业务界面且能准确解释其底层 View 实现与输入事件抑制机制。1. 模块定位什么是gui/loading视图gui/loading是 Flipper Zero JavaScript SDKfz-sdk提供的一组 GUI 视图之一文档定义非常简洁Displays an animated hourglass icon. Suppresses allnavigationevents, making it impossible for the user to exit the view by pressing the back key. 显示一个动画沙漏图标。抑制所有navigation事件用户无法通过按返回键退出该视图。也就是说它有两个核心特性视觉层面屏幕中央显示一个持续播放的沙漏动画A_Loading_24图标动画交互层面吞掉全部按键输入navigation事件永远不会产生因此应用不会因为误触而意外退出加载页。这正是它的典型使用场景在等待耗时操作读写存储、等待外设、执行计算期间给用户一个“系统正在工作”的反馈同时保证这一过程不可被按键打断。对应的 SDK 类型声明位于 loading.d.ts其中Props被声明为空对象{}、Child被声明为never——即该视图没有任何属性、不能添加任何子元素文档中 “This view does not have any props” 的说明与此完全一致。2. 模块导入严格的依赖顺序文档给出的导入方式如下let eventLoop require(event_loop); let gui require(gui); let loadingView require(gui/loading);文档特别强调gui/loading依赖gui模块而gui又依赖event_loop模块因此必须按此顺序导入并建议先理解前两个模块的概念再使用本模块。这个约束不是文档的随口要求而是有源码依据的gui模块的构造函数js_gui_create中会调用js_module_get(modules, event_loop)获取事件循环若取不到则直接返回NULL导致模块创建失败见 js_gui.c。也就是说gui是在event_loop之上构建的viewDispatcher、导航/自定义事件契约contract都挂在FuriEventLoop上。每个gui/xxx视图模块则是通过JS_GUI_VIEW_DEF宏注册为插件入口的见 js_gui.h它内部调用js_gui_make_view_factory因此必然在gui模块存在之后才能工作。所以正确的心智模型是三层结构event_loop事件循环→guiViewDispatcher 与视图基础设施→gui/loading具体视图工厂导入顺序与使用顺序都要遵守这条链。3. 基本用法创建并切换到加载视图gui/loading导出的是一个ViewFactory调用make()或无参数可用的makeWith({})即可得到视图实例。官方示例脚本 gui.js 中演示了完整用法核心片段// 创建加载视图无需任何属性 let loadingView require(gui/loading); let views { loading: loadingView.make(), // ...其他视图 }; // 切换到加载视图并用一次性定时器在 1 秒后切回 eventLoop.subscribe(views.demos.chosen, function (_sub, index, gui, eventLoop, views) { if (index 0) { gui.viewDispatcher.switchTo(views.loading); // the loading view captures all back events, preventing our navigation callback from firing // switch to the demo chooser after a second eventLoop.subscribe(eventLoop.timer(oneshot, 1000), function (_sub, _, gui, views) { gui.viewDispatcher.switchTo(views.demos); }, gui, views); } // ... }, gui, eventLoop, views);注意示例代码中的两行注释它们准确描述了本视图的行为“loading view captures all back events, preventing our navigation callback from firing”加载视图捕获所有返回事件使导航回调不会触发。因此离开加载页只能由程序自身完成通常配合eventLoop.timer(oneshot, ...)一次性定时器或任务完成后的事件回调调用gui.viewDispatcher.switchTo(...)切回目标视图。3.1 一个可独立运行的最小示例综合官方示例的模式一个最小可用的完整脚本如下适用于 JS SDK 应用入口// 依赖顺序必须为 event_loop - gui - gui/loading let eventLoop require(event_loop); let gui require(gui); let loadingView require(gui/loading); let submenuView require(gui/submenu); let views { loading: loadingView.make(), menu: submenuView.makeWith( { header: Demo }, [Done] ), }; // 点击菜单后先进入加载视图模拟耗时操作 eventLoop.subscribe(views.menu.chosen, function (_sub, _index, gui, views) { gui.viewDispatcher.switchTo(views.loading); // 1.5 秒后模拟完成自动切回菜单 eventLoop.subscribe( eventLoop.timer(oneshot, 1500), function (_sub, _, gui, views) { gui.viewDispatcher.switchTo(views.menu); }, gui, views ); }, gui, views); // 导航键处理由于 loading 视图不会产生 navigation 事件 // 这里只需处理其他视图的返回逻辑 eventLoop.subscribe( gui.viewDispatcher.navigation, function (_sub, _, eventLoop) { eventLoop.stop(); }, eventLoop ); gui.viewDispatcher.switchTo(views.menu); eventLoop.run();由于加载视图不会产生navigation事件全局的返回处理逻辑gui.viewDispatcher.navigation订阅在加载期间不会被误触发无需在导航回调中额外判断当前视图是否为 loading——这与示例 gui.js 的做法一致。4. 属性View props为什么是“零属性”文档明确说明 “This view does not have any props”。这一点可以从 JS 绑定层的实现直接验证。loading.c 的完整实现只有十余行static const JsViewDescriptor view_descriptor { .alloc (JsViewAlloc)loading_alloc, .free (JsViewFree)loading_free, .get_view (JsViewGetView)loading_get_view, .prop_cnt 0, .props {}, }; JS_GUI_VIEW_DEF(loading, view_descriptor);prop_cnt 0、props {}描述符中不声明任何属性因此在 JS 端调用set(xxx, ...)会走到js_gui_view_assign的兜底分支并报出view has no prop named xxx错误见 js_gui.c。没有custom_make/add_child/reset_children回调结合 js_gui.c 中的实现对无子元素能力的视图调用addChild/resetChildren/setChildren会直接抛出this View cant have children错误——这与loading.d.ts中Child never的声明互相印证。设计上这也合理一个沙漏动画没有可配置状态唯一的行为播放动画、吞掉输入是内置的零属性意味着零误用空间。5. 源码级原理动画从哪来输入为何被吞掉JS 绑定层的loading_alloc/loading_free/loading_get_view直接复用了 GUI 服务层的 C 实现真正的行为逻辑在 applications/services/gui/modules/loading.c 中其结构是标准的 FlipperView四回调模式1创建与资源loading_allocinstance-view view_alloc(); view_allocate_model(instance-view, ViewModelTypeLocking, sizeof(LoadingModel)); model-icon icon_animation_alloc(A_Loading_24); view_tie_icon_animation(instance-view, model-icon);动画素材是内置图标A_Loading_2424×24 的沙漏动画帧序列通过icon_animation_alloc创建后与 View 绑定view_tie_icon_animation让 View 在动画每帧推进时自动触发重绘无需应用层参与Model 采用ViewModelTypeLocking保证模型更新与绘制回调的线程安全。2绘制loading_draw_callback先用白底填充全屏canvas_draw_box再把沙漏底图A_Loading_24与动画帧model-icon叠画在屏幕正中坐标由画布尺寸与 24px 图标尺寸计算得出从而得到“白底 居中动画沙漏”的视觉效果。3输入抑制——“不可退出”的实现点static bool loading_input_callback(InputEvent* event, void* context) { UNUSED(event); furi_assert(context); return true; // 消费所有输入事件且不触发任何回调 }该回调对每一个InputEvent包括 BACK 键都返回true表示“已消费”但内部什么都不做。View 层认为输入已被处理不会继续上抛给ViewDispatcher的导航回调因此navigation事件链路JS 端对应viewDispatcher.navigation的契约对象根本收不到事件。这就是文档所说 “Suppresses allnavigationevents” 的底层机制——不是屏蔽导航事件而是在视图输入阶段就把按键全部吃掉。4进出视图时动画的启停loading_enter_callback在切入时调用icon_animation_start启动动画并对view_tie_icon_animation重新绑定注释说明这是为了兼容多个 Stack View 复用场景loading_exit_callback在切出时调用icon_animation_stop停止动画避免切走后动画仍在消耗资源。对 JS 应用开发者而言这层实现意味着两件事动画的启停完全由 ViewDispatcher 的视图切换驱动无需手动管理加载期间任何按键包括长按、方向键都不会产生任何事件应用可以安全地假定用户“看不到、也操作不了”这个界面。6. 与其他视图及模块的配合从 js_gui.c 可以看到viewDispatcher暴露的switchTo最终调用view_dispatcher_switch_to_view并同步更新currentView字段每个 JS 视图对象在make时已注册进ViewDispatcher并分配唯一view_idjs_gui.c。由此可以给出使用gui/loading的标准实践进入耗时操作开始前gui.viewDispatcher.switchTo(loadingView)离开操作完成或超时后由 JS 侧主动switchTo回目标视图可配合eventLoop.timer(oneshot, ms)不要指望navigation事件把用户从 loading 带出去——它不会发生不要尝试loading.set(...)或向它添加子元素二者都会按第 4 节描述报错。此外若需要在加载的同时给出提示文案可在其他视图如gui/popup带timeout属性或gui/text_box中组合实现gui/loading本身保持“纯动画、零属性”的定位不变。7. 参考文件索引内容路径官方文档本文主体依据documentation/js/js_gui__loading.mdSDK 类型声明Props 为空、Child 为 neverapplications/system/js_app/packages/fz-sdk/gui/loading.d.tsJS 绑定零属性描述符、插件注册applications/system/js_app/modules/js_gui/loading.cGUI 服务层 C 实现动画、绘制、输入抑制applications/services/gui/modules/loading.cView/ViewFactory 通用机制与属性校验applications/system/js_app/modules/js_gui/js_gui.c、js_gui.h官方运行示例loading 演示段落applications/system/js_app/examples/apps/Scripts/gui.js示意图documentation/images/loading.png【免费下载链接】flipperzero-firmwareFlipper Zero firmware source code项目地址: https://gitcode.com/GitHub_Trending/fl/flipperzero-firmware创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表