HarmonyOS 技术实战 02:ArkUI 状态驱动首页与五 Tab 导航

HarmonyOS 技术实战 02:ArkUI 状态驱动首页与五 Tab 导航 前言上一篇讲了喵汪星球的整体架构。这一篇进入页面层重点拆解 ArkUI 中一个非常实用的实现不用复杂路由先用状态驱动一个五 Tab 主界面。项目当前主页面在entry/src/main/ets/pages/Index.ets包含首页、记录、翻译器、健康、我的以及一个从首页快速进入的陪玩模块。它不是简单 TabView而是围绕业务闭环定制了底部导航、中间突出按钮、页面滚动归位和卡片跳转联动。为什么 MVP 阶段不用复杂路由很多应用一开始就拆很多页面但 MVP 阶段常见问题是路由层复杂了状态同步反而更容易出错。这个项目的首版目标是验证“宠物档案 照护打卡 健康应急 叫声分析”的完整闭环因此采用了更直接的结构build() { Column() { Scroll(this.pageScroller) { Column() { if (this.currentTab 首页) { this.HomePage() } else if (this.currentTab 我的) { this.ProfilePage() } else if (this.currentTab 记录) { this.ReminderPage() } else if (this.currentTab 健康) { this.HealthPage() } else if (this.currentTab 陪玩) { this.PlayPage() } else { this.TranslatorPage() } } } this.BottomNav() } }这段代码的思路很朴素currentTab是唯一页面选择状态Builder 方法负责渲染对应模块。它的优势是页面切换成本低不需要传复杂路由参数。各模块可以直接共享当前宠物、任务和记录状态。桌面卡片跳转时只要改currentTab即可。适合业务闭环还在快速变化的阶段。后续如果页面继续增多再迁移到Navigation或router也不迟。用 StorageLink 处理跨入口 Tab 切换项目里最值得学习的是这行StorageLink(mwCurrentTab) currentTab: string 首页普通State只在组件内部响应变化而StorageLink可以和AppStorage建立连接。桌面卡片点击后EntryAbility会读取 Want 参数然后写入同一个 keyprivate applyCardNavigation(want: Want): void { const params want.parameters if (params undefined) { return } const rawTab params[mwTab] if (typeof rawTab string CARD_TABS.indexOf(rawTab) 0) { AppStorage.setOrCreatestring(mwCurrentTab, rawTab) } }这样就形成了一个很轻的联动链路桌面卡片点击 - postCardAction 携带 mwTab - EntryAbility 收到 Want - 写入 AppStorage - Index.ets 的 StorageLink 更新 - 主页面切到对应 Tab这比在卡片里做复杂业务跳转更稳。卡片只负责告诉主应用“我要去哪”真正的业务页面仍由主应用控制。底部导航的设计底部导航不是简单平均五等分。翻译器是本 App 的特色功能所以项目把它做成中间突出按钮Builder BottomNav() { Stack({ alignContent: Alignment.Top }) { Row() { this.NavItem(首页, $r(app.media.tab_home)) this.NavItem(记录, $r(app.media.tab_record)) Column().width(78) this.NavItem(健康, $r(app.media.tab_health)) this.NavItem(我的, $r(app.media.tab_profile)) } Column({ space: Theme.spaceXS }) { Stack() { Image($r(app.media.tab_translator_light)) } .width(62) .height(62) .backgroundColor(Theme.primary) .borderRadius(31) Text(翻译器) } .onClick(() { this.switchTab(翻译器) }) } }这里的技巧是Row 中间留一个固定宽度占位再用 Stack 覆盖一个居中的圆形按钮。视觉上像原生 App 常见的“突出主功能入口”实现上仍然是普通 ArkUI 组件。切换 Tab 时滚动归位如果每个 Tab 共用一个Scroll切换页面时很容易出现一个问题用户在健康页滚到底再切到首页首页也停在很靠下的位置。项目用Scroller解决private pageScroller: Scroller new Scroller() private switchTab(tab: string): void { this.currentTab tab this.pageScroller.scrollTo({ xOffset: 0, yOffset: 0 }) }这是一个小细节但用户体验差别很明显。尤其是记录页、健康页、我的页都有较长内容时切换归位会让应用显得更稳。首页不是信息堆叠而是任务入口首页的目标不是展示所有信息而是回答用户打开 App 时最关心的三个问题今天下一件事是什么有没有未完成或逾期事项我可以快速做什么项目中首页通过nextTask()、overdueText()、QuickActionGrid()组织信息。快速操作入口包括记录、陪玩、症状、应急private readonly quickActions: QuickAction[] [ { title: 记录, hint: 吃喝拉撒, tab: 记录, tone: primary }, { title: 陪玩, hint: 今天玩什么, tab: 陪玩, tone: warm }, { title: 症状, hint: 快速判断, tab: 健康, tone: blue }, { title: 应急, hint: 就医准备, tab: 健康, tone: danger } ]这个数组既是页面数据也是交互配置。每个入口知道自己要跳到哪个 Tab样式由tone决定。Builder 方法让页面保持可读Index.ets中大量使用BuilderBuilder HomePage() {} Builder ReminderPage() {} Builder HealthPage() {} Builder TranslatorPage() {} Builder TaskRow(item: CareTask) {} Builder EntertainmentNotice(message: string) {}这种写法有两个好处第一把页面结构拆成语义块。比如HealthPage()负责健康页SymptomChip()负责症状标签InfoRow()负责信息行。第二减少重复 UI。提醒行、状态卡、标签、反馈按钮都可以复用 Builder 方法。当然如果组件继续膨胀下一步应该拆到components/目录。但在单文件 MVP 阶段Builder 是非常轻量的分层方式。响应式布局的小处理项目通过onAreaChange获取视口宽度.onAreaChange((_oldArea, newArea) { this.updateViewportWidth(newArea.width.toString()) })然后用方法控制页面边距private pageHorizontalPadding(): number { if (this.isLargeScreen()) { return 56 } return Theme.spaceL }这比写死16vp更适合手机、平板、2in1 多设备。喵汪星球的module.json5里声明了deviceTypes: [phone, tablet, 2in1]既然设备类型支持更广页面宽度就不能只按手机思路设计。本篇小结这一篇我们看到了 ArkUI 页面组织中的几个实战点用currentTab做单页多模块渲染。用StorageLink AppStorage接收卡片跳转。用 Stack 定制中间突出的主功能按钮。用 Scroller 在切换 Tab 时滚动归位。用 Builder 方法拆出页面、行、卡片、标签等结构。用视口宽度做简单响应式适配。下一篇进入数据层如何用 Preferences 保存一个复杂 App 的离线状态并做好字段兼容和恢复逻辑。