HarmonyOS 3D 拆解分析:单页多 Tab 架构的实现

发布时间:2026/10/7 15:24:36

HarmonyOS 3D 拆解分析:单页多 Tab 架构的实现
前言之前的 3D 拆解分析 App 有两个页面Index首页和AnalysisPage分析工作台。用户在首页选好场景后通过router.pushUrl跳转到分析页面。分析完按返回键回到首页。这个架构能跑但有个问题每次从分析页返回首页页面会重新渲染之前的场景选择状态会丢失。而且两个页面之间传参需要router.pushUrlrouter.getParams数据流绕了一圈。这次重构把首页和分析页合并成一个页面用底部 Tab 导航切换。不再需要页面跳转所有状态都在同一个组件里管理。架构变化旧架构两个独立页面Index.ets (首页) → router.pushUrl → AnalysisPage.ets (分析页) → router.back → Index.ets新架构单页多 TabIndex.ets ├── Tab 0: HomeTab() — 首页内容 ├── Tab 1: AnalyzeTab() — 分析工作台 └── Tab 2: HistoryTab() — 历史记录占位所有 Tab 共享同一个组件实例切换 Tab 只是改变State tab的值不会触发页面重建。底部导航栏SymbolGlyph Tab 切换导航栏布局build(){Stack({alignContent:Alignment.Bottom}){Scroll(){Column(){if(this.tab0){this.HomeTab()}elseif(this.tab1){this.AnalyzeTab()}else{this.HistoryTab()}Blank().height(90)}}Row(){this.TabBtn(0,首页)this.TabBtn(1,拆解)this.TabBtn(2,历史)}.width(100%).height(68).backgroundColor(CW).justifyContent(FlexAlign.SpaceAround).alignItems(VerticalAlign.Center).shadow({radius:12,color:rgba(0,0,0,0.05),offsetY:-2})}}用Stack让导航栏浮在内容区上面。Blank().height(90)在内容区底部留白防止最后一行内容被导航栏遮挡。Tab 按钮组件BuilderTabBtn(idx:number,label:string){Column({space:3}){SymbolGlyph(this.tabIcon(idx,this.tabidx)).fontSize(22).fontColor([this.tabidx?A:#C0BFC6])Text(label).fontSize(10).fontColor(this.tabidx?A:#C0BFC6)}.onClick((){this.tabidx})}三个 Tab首页sys.symbol.house/sys.symbol.house_fill拆解sys.symbol.scope瞄准镜图标代表分析历史sys.symbol.clock时钟图标代表时间线选中态用品牌紫A未选中用淡灰#C0BFC6。图标选择逻辑privatetabIcon(idx:number,active:boolean):Resource{if(active){if(idx0)return$r(sys.symbol.house_fill)if(idx1)return$r(sys.symbol.scope)return$r(sys.symbol.clock)}if(idx0)return$r(sys.symbol.house)if(idx1)return$r(sys.symbol.scope)return$r(sys.symbol.clock)}注意scope和clock没有_fill版本或系统不支持所以选中和未选中用同一个图标只通过颜色区分。house有_fill版本选中时用实心。这种情况很常见——不是所有系统图标都有_fill变体。处理方式没有_fill的图标就用颜色区分有的就用填充颜色双重区分。状态管理从跨页传参到组件内共享旧方案路由传参// 首页privatepush(cols,rows,uri):void{router.pushUrl({url:pages/AnalysisPage,params:{gridCols:cols,...}})}// 分析页aboutToAppear(){constprouter.getParams()asRecordstring,Objectthis.colsp[gridCols]asnumber}新方案组件内状态Statetab:number0Stateuri:stringStatename:stringStatecols:number3Staterows:number3// ... 更多状态privatestartAnalysis(uri:string,c:number,r:number,n:string):void{this.uriurithis.colscthis.rowsrthis.namenthis.val0this.onfalsethis.listfalsethis.rebuildAnalysis()this.tab1// 切换到分析 Tab}startAnalysis方法做三件事设置分析数据uri、cols、rows、name重置 UI 状态val0、onfalse、listfalse切换到分析 Tab不需要路由跳转不需要参数序列化/反序列化所有数据都在同一个组件里。rebuildAnalysis从路由参数到直接调用privaterebuildAnalysis():void{constparts:PartInfo[]segmentImage(this.cols,this.rows,360,360)this.countparts.lengththis.pnpartsToNames(parts)this.pxpartsToGridX(parts)this.pypartsToGridY(parts)this.pdpartsToDepths(parts)// ... 构建网格数据和部件卡片}旧版的rebuild在aboutToAppear中调用依赖路由参数。新版的rebuildAnalysis在startAnalysis中调用直接使用组件内的State变量。首页 Tab场景选择的保留HomeTab 保留了之前的所有功能——问候语、Hero Card、统计卡片、场景预设、三步流程。唯一的区别是点击开始分析时不再跳转页面// 旧版.onClick((){this.push(this.cols(),this.rows(),)})// 新版.onClick((){this.startAnalysis(,this.colsH(),this.rowsH(),recommendGrid(this.colsH(),this.rowsH()))})startAnalysis接收四个参数图片 URI、列数、行数、物品名称。首页没有拍照功能所以 URI 传空字符串。分析 Tab从独立页面到 Tab 内容AnalyzeTab 的布局和之前的 AnalysisPage 几乎一样——头部、工作区、滑块、网格选择、部件清单。区别在于不需要aboutToAppear获取路由参数——数据已经在startAnalysis中设置好了不需要router.back()——切换回首页只需this.tab 0状态变量直接在组件内——不需要跨页面传递工作区高度固定.width(100%).height(320).margin({left:16,right:16,bottom:8})旧版的工作区用.layoutWeight(1)占满剩余空间。新版固定高度 320px——因为 Tab 切换时内容区高度不变用固定高度更可控。部件清单的紧凑化// 旧版.height(170).padding(14).borderRadius({topLeft:18,topRight:18})// 新版.height(140).padding(12).borderRadius({topLeft:16,topRight:16})部件清单的高度、内边距、圆角都缩小了。在 Tab 布局中垂直空间更宝贵——底部导航栏占了 68px内容区需要更紧凑。历史 Tab占位设计BuilderHistoryTab(){Column(){Text(分析历史).fontSize(20).fontWeight(FontWeight.Bold).fontColor(T1).width(100%).padding({left:18,top:14}).margin({bottom:12})Column(){Text().fontSize(48).opacity(0.2).margin({bottom:12})Text(暂无分析记录).fontSize(15).fontWeight(FontWeight.Medium).fontColor(T2)Text(切换到首页选择场景开始拆解).fontSize(12).fontColor(T3).margin({top:4})}.width(100%).layoutWeight(1).justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)}}空状态设计标题分析历史图标48px20% 透明度主文案暂无分析记录副文案切换到首页选择场景开始拆解副文案引导用户去首页操作而不是让用户猜测这个页面是干什么的。保留 AnalysisPage 的原因新版 Index 已经包含了分析功能但AnalysisPage.ets没有删除。原因向后兼容如果有其他页面通过router.pushUrl跳转到 AnalysisPage它仍然能工作独立使用如果后续需要把分析功能单独抽出来比如做成一个可复用的组件AnalysisPage 可以作为参考渐进式迁移先在 Index 中实现新架构验证没问题后再删除旧页面踩坑记录1. Tab 切换时的状态保留if(this.tab0){this.HomeTab()}elseif(this.tab1){this.AnalyzeTab()}ArkUI 的if/else条件渲染会在条件变化时销毁旧组件、创建新组件。这意味着切换 Tab 时每个 Tab 的内部状态会丢失。如果需要保留 Tab 状态比如分析页的滑块位置有两个方案用Visibility控制显示/隐藏组件不销毁只是不可见把需要保留的状态提升到父组件当前方案——val、on等状态在 Index 层管理2. Blank().height 的高度计算Blank().height(90)这个高度需要 ≥ 导航栏高度68px 安全区域。设 90px 留了 22px 的余量。如果设太小最后一行内容会被导航栏遮挡。3. Scroll 的 edgeEffect 在 Tab 切换时Scroll(){...}.edgeEffect(EdgeEffect.Spring)Tab 切换时 Scroll 会重置滚动位置。如果需要记住每个 Tab 的滚动位置需要在切换时保存和恢复scrollOffset。4. SymbolGlyph 的选中态图标不是所有sys.symbol.*都有_fill版本。使用前最好在 DevEco Studio 的预览里确认。如果没有_fill就用颜色区分选中/未选中。写在最后单页多 Tab 架构的核心优势是状态共享——首页选的场景、分析页的滑块位置、部件清单的展开状态都在同一个组件里切换 Tab 不会丢失。代价是代码量增加了——Index.ets 现在包含了三个 Tab 的所有代码文件变长了。如果后续每个 Tab 的功能越来越复杂可以考虑把每个 Tab 拆成独立组件在 Index 中组合使用build(){Stack({alignContent:Alignment.Bottom}){Column(){if(this.tab0){HomeTab({preset:$preset})}elseif(this.tab1){AnalyzeTab({uri:$uri,...})}else{HistoryTab()}}this.BottomNav()}}这样每个 Tab 的代码独立维护Index 只负责 Tab 切换和状态管理。

相关新闻

【2026年下】中小学教师资格考试《综合素质》(中学)历年真题和答案

【2026年下】中小学教师资格考试《综合素质》(中学)历年真题和答案

2026/10/7 5:15:59

2026年下半年中小学教师资格考试考试 2026年下半年中小学教师资格考试笔试定于2026年9月12日举行。为助力广大考生高效备考,我们现正式发布中学学段《综合素质》(科目一)历年真题及配套资料合集。 资料内容详情 本合集为PDF电子版&#xf…

【单片机毕业设计】基于 STM32/51 单片机的 8 路病床无线呼叫对讲系统设计,基于 NRF24L01 的病房智能呼叫与输液监测装置开发(020301)

【单片机毕业设计】基于 STM32/51 单片机的 8 路病床无线呼叫对讲系统设计,基于 NRF24L01 的病房智能呼叫与输液监测装置开发(020301)

2026/10/5 10:10:53

文章目录20 个相关毕业设计备选题目项目研究背景摘要总体方案硬件设备选型、作用与架构说明整体硬件架构核心功能一、基础硬件交互功能二、无线通信采集核心功能三、优先级调度医护交互功能四、输液监测辅助功能五、语音对讲特色功能技术路线项目演示关于我们项目案例源码获取博…

简历审查Agent工程化收尾:模型坑位、线程安全、超时兜底与RAG预告

简历审查Agent工程化收尾:模型坑位、线程安全、超时兜底与RAG预告

2026/10/2 22:10:10

0. 上下文衔接 Day 6 完成了简历审查Agent的API层骨架:上传返回202、后台create_task+GC保护、轮询查询、图编排。但代码跑通和能上线之间还有一段距离,今天收尾处理一批工程化问题: DeepSeek V4模型的思考模式坑:换模型后结构化输出全部报错 图实例的线程安全:全局单例图…

CANN/GE ACL数据集缓冲区添加函数

CANN/GE ACL数据集缓冲区添加函数

2026/10/6 23:38:49

aclmdlAddDatasetBuffer 【免费下载链接】ge GE(Graph Engine)是面向昇腾的图编译器和执行器,提供了计算图优化、多流并行、内存复用和模型下沉等技术手段,加速模型执行效率,减少模型内存占用。 GE 提供对 PyTorch、Te…

用ffmpeg高效批量调整图片尺寸的实战指南

用ffmpeg高效批量调整图片尺寸的实战指南

2026/10/5 16:54:26

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

Transformers 音频特征提取工具库 audio_utils 全解析:从 Mel 刻度换算到对数 Mel 频谱

Transformers 音频特征提取工具库 audio_utils 全解析:从 Mel 刻度换算到对数 Mel 频谱

2026/10/7 2:07:10

Transformers 音频特征提取工具库 audio_utils 全解析:从 Mel 刻度换算到对数 Mel 频谱 【免费下载链接】transformers 🤗 Transformers: the model-definition framework for state-of-the-art machine learning models in text, vision, audio, and mu…

RustFS 多节点集群重启与滚动升级实战:Readiness、Quorum 与 Degraded 模式完全指南

RustFS 多节点集群重启与滚动升级实战:Readiness、Quorum 与 Degraded 模式完全指南

2026/10/5 21:04:58

RustFS 多节点集群重启与滚动升级实战:Readiness、Quorum 与 Degraded 模式完全指南 【免费下载链接】rustfs 🚀2.3x faster than MinIO for 4KB object payloads. RustFS is an open-source, S3-compatible high-performance object storage system sup…

Java Integer缓存揭秘:128陷阱原理、避坑与面试全解

Java Integer缓存揭秘:128陷阱原理、避坑与面试全解

2026/10/7 9:52:55

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

RustFS Scanner 数据用量发布权威性决策:配额准入如何获得可用的权威依据

RustFS Scanner 数据用量发布权威性决策:配额准入如何获得可用的权威依据

2026/10/6 21:46:12

RustFS Scanner 数据用量发布权威性决策:配额准入如何获得可用的权威依据 【免费下载链接】rustfs 🚀2.3x faster than MinIO for 4KB object payloads. RustFS is an open-source, S3-compatible high-performance object storage system supporting mi…