Harmony os 技术实战|拼豆制图01:用状态机收口 ArkUI 单页导航

📅 2026/7/29 9:32:45 👁️ 阅读次数 📝 编程学习
Harmony os 技术实战|拼豆制图01:用状态机收口 ArkUI 单页导航

Harmony os 技术实战|拼豆制图01:用状态机收口 ArkUI 单页导航

拼豆制图这种工具型应用,入口看起来只有几个 Tab,但真正麻烦的是入口之间会共享图纸、搜索词、收藏状态和生成结果。若每个页面都自己维护一套跳转和数据,后期加详情页、放大页、导出页时很容易出现“返回到了错误页面”或者“收藏状态没同步”的问题。

这篇文章会把问题拆到能落地的层面,重点解决:

  • 用一个入口状态承接多页面切换,而不是让页面彼此直接互相控制。
  • 让图纸详情、放大查看和底部导航之间的关系更清晰。
  • 把 UI 编排和图片转换、导出等业务服务拆开。
  • 为后续拆成多 Page 或 Navigation 路由保留迁移空间。

为什么导航先要收口

在当前项目中,Index.ets承担了入口页职责:用户可以从首页看热门图纸,从图库筛选 50 张内置图纸,从制作页上传图片生成编号图,也可以进入收藏和个人页。真正的风险不是页面多,而是这些页面都会碰到同一个图纸对象。例如首页卡片点击、图库卡片点击、收藏列表点击,最终都应该进入编号图纸页,并且读到同一个selectedPatternId

如果把每个入口都写成独立跳转,短期能跑,长期会出现两类问题:一类是页面状态散在多个地方,另一类是返回路径无法推断。工具型应用的交互频率高,用户会反复在“找图纸、看图纸、做图纸、保存图纸”之间切换,因此入口状态要像一个小型状态机一样可读、可改、可验证。

落地时可以先抓三个信号:

  1. ArkUI的职责是否只在一个层级里被定义,而不是页面、服务和资源各写一份。
  2. activeTab相关状态是否能从用户入口一路追到结果页,中间没有隐式副作用。
  3. 遇到空数据、取消操作或边界输入时,页面是否还能停在可继续操作的状态。

页面边界不要和业务逻辑混在一起

单页导航并不是把所有代码塞进一个文件,而是在当前阶段先把会话状态统一放在顶层。页面 Builder 负责展示和触发事件,业务服务负责转换、导出、查询。这样做的好处是:当项目还在快速迭代 UI 时,入口关系稳定;当功能变重后,也可以把某个 Builder 提升成独立页面或组件。

决策点推荐做法避免的问题
入口状态activeTab保存当前主入口Tab 图标变化了但内容没变
图纸状态selectedPatternId指向当前图纸详情页拿到过期对象
底部导航只在非放大页显示放大查看时底部栏遮挡图纸
业务能力转换和导出放在 Service页面 Builder 写入大量异步逻辑

这张表真正约束的是变更顺序:先固定“入口状态”的归属,再处理“图纸状态”的输入输出,最后围绕“底部导航”做回归。只要这三处没有漂移,后续增加页面、资源或参数时,改动就不会一路扩散到无关模块。实际排查时也建议按这个顺序记录结论:哪一层接收输入、哪一层生成结果、哪一层负责失败后的恢复。记录得越具体,下一轮迭代越不容易把已经稳定的路径改坏。

先定义导航和图纸状态

导航状态不需要一开始就设计得很复杂,但命名必须稳定。TabItem是底部导航的数据形态,Pattern是页面之间共享的图纸对象;这两个模型只描述数据,不夹带页面行为。

1. 用稳定模型描述导航入口

底部导航不直接写死在 UI 里,先收成一个小模型,后续增加入口或替换图标时改动面会小很多。

exportinterfaceTabItem{key:'home'|'gallery'|'create'|'favorite'|'profile';label:string;icon:string;}exportinterfacePatternRouteState{activeTab:TabItem['key']|'numbered';selectedPatternId:string;isChartExpanded:boolean;}

这个边界只负责说明“现在在哪”和“正在看哪张图纸”。它不关心图纸如何生成,也不关心卡片如何渲染,因此可以被首页、图库和收藏列表共同复用。

build 分支只做视图选择

页面分支应该像目录一样清楚。build中只根据activeTab决定显示哪个视图,复杂计算放到 private 方法,避免在 Builder 内写临时变量和业务判断。

2. build 分支保持声明式

顶层build的职责是选择页面,别把筛选、导出、图片解析等逻辑塞进来。

build(){Column(){if(this.activeTab==='numbered'&&this.isChartExpanded){this.ExpandedChartPage(this.getSelectedPattern());}elseif(this.activeTab==='home'){this.HomePage();}elseif(this.activeTab==='gallery'){this.GalleryPage();}elseif(this.activeTab==='create'){this.CreatePage();}elseif(this.activeTab==='numbered'){this.NumberedPage();}elseif(this.activeTab==='favorite'){this.FavoritePage();}else{this.ProfilePage();}if(!this.isChartExpanded){this.BottomNavigation();}}}

这段代码的意图是让视图关系一眼可见。放大页优先判断,是因为它会改变底部导航可见性;普通主入口按稳定顺序排列,后续排查时能快速定位分支。

点击入口要同时更新业务上下文

用户点击图纸卡片时,不只是在切换页面,还在更新当前图纸上下文。把这两个动作放在同一个事件里,可以避免详情页依赖上一轮的图纸状态。

3. 图纸点击同时更新上下文和入口

从首页、图库、收藏进入详情时,事件处理要写成同一套动作。

privateopenPattern(patternId:string):void{this.selectedPatternId=patternId;this.isChartExpanded=false;this.activeTab='numbered';this.exportStatus='';}

selectedPatternId是业务上下文,activeTab是页面上下文,二者必须一起更新。顺手清空导出状态,是为了避免上一张图纸的保存提示留到下一张图纸上。

返回和放大页要有明确出口

放大编号图是一个特殊状态:它仍然属于编号图纸能力,但需要隐藏底部导航并保持返回路径。这里不需要新增一个主 Tab,用布尔状态表达更直接。

4. 给放大页一个独立出口

放大查看不是新的主入口,它只是编号图纸页的展开状态。

privateshowExpandedChart():void{this.isChartExpanded=true;}privatecloseExpandedChart():void{this.isChartExpanded=false;this.activeTab='numbered';}

这个写法避免了返回时猜测来源页面。无论用户从首页还是收藏进入图纸,一旦关闭放大图,都回到编号图纸视图,页面关系更可预测。

模块配置给入口一个稳定身份

入口模块本身也要在module.json5里有稳定身份。应用的主 Ability、图标、启动页和页面 profile 都应该指向明确资源,否则调试时容易把 UI 问题误判成页面逻辑问题。

5. 入口 Ability 配置保持单一

配置层只暴露一个主入口,业务页面由 ArkUI 状态控制。

{ "module": { "name": "entry", "type": "entry", "pages": "$profile:main_pages", "abilities": [{ "name": "EntryAbility", "srcEntry": "./ets/entryability/EntryAbility.ets", "exported": true }] } }

Stage 模型下,入口 Ability 负责启动应用,应用内部的首页、图库、制作页不必都暴露成系统入口。这样能降低配置噪声,也方便后续统一处理启动参数。

验证这条导航链路

实际项目里,验证不能只看页面有没有打开。更稳的做法是把入口、状态、边界数据和失败路径都走一遍,尤其是拼豆图纸这种“看起来能显示,放大后才暴露问题”的功能。

验证前建议准备一组固定样本:一条正常路径、一条空数据路径、一条失败路径,再加一条连续操作路径。这样每次改动都能比较同一批场景,不会只凭当前页面肉眼感觉判断。如果涉及屏幕尺寸、资源替换或系统能力,还要保留修改前后的截图和关键输入,方便回退时确认差异来自哪里。

hvigor assembleHap--no-daemonWrite-Host"真机或模拟器验证:首页 -> 图库 -> 编号图纸 -> 放大图 -> 返回 -> 收藏"
  1. 从首页热门图纸进入编号图纸页,确认标题、色号、图例属于同一张图纸。
  2. 从图库筛选后进入编号图纸页,再返回首页,确认底部导航没有停在错误状态。
  3. 打开放大图后确认底部导航隐藏,关闭后仍回到编号图纸页。
  4. 连续点击两张不同图纸,确认导出状态不会串到下一张图纸。
  5. activeTab改成未知值时,要有兜底页面或回到首页。

常见问题和处理

现象先看哪里处理方式
Tab 高亮变了但内容没变是否只更新了图标状态把内容分支统一绑定到activeTab
进入详情后仍显示上一张图selectedPatternId更新顺序点击事件里先写图纸 ID 再切换页面
放大页底部被遮住isChartExpanded判断位置放大状态下不渲染底部导航
返回路径混乱是否存在多个返回入口集中到closeExpandedChartactiveTab='home'

后续可以怎样演进

当页面继续增多时,可以把activeTab迁移成更正式的路由枚举,并把openPatterncloseExpandedChart这类动作收进一个轻量的导航服务。若后续需要从系统分享、通知或桌面快捷方式进入某张图纸,再在 Ability 启动参数里解析目标图纸 ID,然后写入同一套页面状态。

继续推进时建议保持三条约束:

  1. 先让现有主路径可回归,再拆更细的组件或服务。
  2. 新增状态必须能说明来源、更新时机和失败后的保留策略。
  3. 新增资源或配置要能从页面反查到生成来源,避免后期只靠人工记忆维护。

小结

单页导航的关键不是“少建页面”,而是先把入口状态和业务上下文放在一个可推断的位置。拼豆制图当前阶段用activeTab + selectedPatternId + isChartExpanded就能覆盖主要路径;页面负责展示,Service 负责业务,后续拆分也不会伤到用户路径。