当前位置: 首页 > news >正文

第20篇|底部导航:地图、拍照、相册、保险箱的产品路径

第20篇|底部导航:地图、拍照、相册、保险箱的产品路径

底部导航看起来只是四个入口,但在一个相机类应用里,它实际定义了产品路径:从「在哪里拍」进入地图,从「马上拍」进入相机,从「拍完看」进入相册,从「私密内容」进入保险箱。导航如果只做成按钮,就会漏掉状态、权限和内容边界。

本篇对应源码仍然是entry/src/main/ets/pages/Index.ets,重点看buildBottomNavigation()buildNavItem()buildNavIconMark()

四个入口不是四个普通按钮

项目里的底部导航是这样组织的:

Row() { this.buildNavItem('地图', 'map') this.buildNavItem('拍照', 'camera') this.buildNavItem('相册', 'gallery') this.buildNavItem('保险箱', 'vault') }

每个入口都走同一个buildNavItem(label, tab),这样选中态、字体、边框、光效、点击行为可以统一维护。统一并不等于行为完全一样,gallery的点击会走openFullGalleryFromNavigation(),其他入口走switchTab(tab)。这是一个很小但重要的产品判断:相册不是普通页签,它要进入完整浏览路径。

为什么导航层要透明命中

地图页底部覆盖层代码里有一段容易被忽略:

Column({ space: 14 }) { if (this.getMapPhotoDockMemories().length > 0) { this.buildMapPhotoGroupDock() } this.buildBottomNavigation() } .hitTestBehavior(HitTestMode.Transparent)

它的意思是:容器空白区域不吃掉手势,真正的卡片和按钮才响应点击。这样用户在底部附近仍然能拖动地图,不会感觉地图「死掉了一块」。这比单纯调zIndex更可靠,因为问题不在层级,而在命中区域。

选中态为什么要同时改图标、边框和光效

buildNavIconMark()使用资源命名来区分默认态和选中态:

Image(this.isActiveTab(tab) ? $r('app.media.nav_camera_active') : $r('app.media.nav_camera'))

再配合ml_selected_glassgetNavGlowColor(tab)hdsEffect.HdsEffectBuilder().pointLight(...),选中态会同时体现在图标、背景、边框和光效上。官方 UIDesignKit 文档提供了hdsEffectHdsEffectBuilder,项目里用它做导航玻璃层的光照反馈,而不是只把文字变粗。

和侧边导航的关系

buildBottomNavigation()的第一句是:

if (this.shouldUseSideNavigation()) { Blank().width(0).height(0) } else { // 构建底部导航 }

这说明底部导航只服务手机形态。宽度达到平板/2in1 条件后,主入口交给侧边导航。这个判断避免了一个常见问题:大屏上同时出现底部导航和侧边导航,路径重复,空间也浪费。

自测建议

  1. 在地图页拖动底图,确认底部透明区域不阻断地图手势。
  2. 依次点击地图、拍照、相册、保险箱,确认选中态和页面内容一致。
  3. 点击相册入口,确认走完整相册路径,而不是只切普通 Tab。
  4. 把窗口宽度调到 600vp 以上,确认底部导航消失、侧边导航出现。
  5. 切深色模式,确认ml_nav_glassml_selected_glass、文字色和图标仍然可读。

导航行为不是一个 switch 就结束

底部导航的点击分支看起来很短,但背后有清楚的产品语义:

.onClick(() => { if (tab === 'gallery') { this.openFullGalleryFromNavigation(); return; } this.switchTab(tab); })

mapcameravault是主 Tab 切换,gallery是完整浏览路径。这样设计是因为相册页不仅展示缩略图,还承担大图查看、筛选、回看和返回逻辑。如果相册也只做switchTab('gallery'),用户从首页进入后会丢失“完整浏览”的上下文,后续文章讲相册详情时就会发现入口不一致。

视觉状态的四个来源

一个导航项的选中态由四部分组成,不是单独靠颜色:

来源代码位置作用
图标资源buildNavIconMark(tab)默认态和_active成对切换
玻璃背景ml_selected_glass让当前入口有稳定承托面
边框和阴影getNavGlowColor(tab)区分不同业务入口的强调色
光照效果hdsEffect.HdsEffectBuilder()让玻璃层有系统级质感

官方hdsEffect文档提供的是效果能力,项目里没有把它当成装饰堆叠,而是只在导航容器和选中项上使用。这样做的好处是光效有明确语义:它只强调“你现在在哪个路径里”。

代码验证 1:底部导航只在手机形态出现

buildBottomNavigation()开头直接判断侧边导航:

if (this.shouldUseSideNavigation()) { Blank() .width(0) .height(0) } else { // 构建底部导航 }

这段代码可以用窗口宽度验证。把预览器或真机窗口拖到 600vp 以上,底部导航应该消失;再缩回手机宽度,它应该重新出现。这个判断比在 CSS 或样式里隐藏某个容器更可靠,因为它从构建阶段就不再输出底部导航,避免大屏上出现两个主入口。

代码验证 2:资源成对存在

第 20 篇用到的导航资源必须成对存在,否则选中态会断。可以在项目根目录运行:

Get-ChildItem entry/src/main/resources/base/media -File | Where-Object { $_.Name -like 'nav_*' } | Sort-Object Name

应该能看到nav_map.pngnav_map_active.pngnav_camera.pngnav_camera_active.pngnav_gallery.pngnav_gallery_active.pngnav_vault.pngnav_vault_active.png。这就是官方资源访问文档里“通过资源文件管理媒体资源”的落地方式:代码只写$r('app.media.nav_camera'),真正替换视觉时改资源文件,不改导航逻辑。

可复用检查清单

如果你也在做相机、地图或工具类应用,可以按下面顺序检查底部导航:

  1. 先确认每个入口是否代表不同任务,而不是仅仅分页面。
  2. 再确认特殊路径是否单独处理,例如本文的相册入口。
  3. 然后检查命中区域,容器空白不要吞掉地图或内容手势。
  4. 最后检查大屏降级,底部导航和侧边导航不要同时存在。

这套顺序比先画 UI 更稳,因为它先确定“入口要完成什么任务”,再决定“入口长什么样”。

小结

一个高质量导航,不只是能点。它要表达当前所在位置,要把不同业务路径分开,还要在地图这种强交互背景上处理好命中区域。这里的实现把「路径统一」和「行为差异」放在同一层:UI 统一由buildNavItem()承担,特殊路径由点击分支承担,设备差异由shouldUseSideNavigation()承担。

参考依据:

  • 华为开发者文档《hdsEffect》:https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ui-design-hdseffect
  • 华为开发者文档《资源分类与访问》:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/resource-categories-and-access
  • 项目源码:entry/src/main/ets/pages/Index.ets
http://www.cnnetsun.cn/news/2646469.html

相关文章:

  • AWS EC2 Windows Server 2012升级2016实战:从备份到SSM修复的完整避坑手册
  • WechatExporter深度解析:3步掌握微信聊天记录专业备份方案
  • 从MODBUS协议栈到你的代码:深入理解CRC-16校验的‘位反序’到底在干什么?
  • 隐形冠军舜展智能:16年磨一剑,用等离子技术点亮中国高端制造
  • 大模型推理加速实战:VLLM 与 TensorRT-LLM 深度拆解——PagedAttention 如何让吞吐量提升 2.3 倍,量化与部署中的图优化又带来 40% 显存节省?
  • 卡梅德生物技术快报|Western Blot 实验应用:肺肠轴机制研究全流程技术解析
  • Flutter 测试详解
  • 手把手教你玩转CST材料库:导入厂家数据、创建自定义吸波材料全攻略
  • 网盘直链解析终极指南:一键解锁高速下载体验
  • 别再死磕Vivado了!用VSCode写ZYNQ代码,效率翻倍的保姆级配置指南
  • Docker 从 0 到 1 再到 Kubernetes 实战:第18篇 从 Docker Compose 到 Kubernetes 的思考
  • 基于ESP32与MAX7219的HUD透明点阵时钟DIY全攻略
  • Vue Bot UI:快速构建现代化聊天机器人界面的终极指南
  • 终极AutoCAD字体缺失解决方案:FontCenter自动字体管理插件
  • 保姆级教程:手把手教你用Windows 10/11磁盘管理工具,给移动硬盘固定一个盘符
  • 【Claude合同审查避坑指南】:20年法务+AI专家亲授3类致命条款识别术(附审查清单)
  • 揭秘Claude情感曲线异常波动:5步精准定位Prompt情绪失焦根源并实时校准
  • 抖音下载神器终极指南:一键获取无水印视频的完整教程
  • 843756
  • Keil5软件仿真内存报错别慌!手把手教你用debug.ini文件一劳永逸(附Memory Map对比)
  • 为什么87%的Claude集成项目在POC阶段就埋下合规炸弹?——一张动态风险评估矩阵表说清全部因果链
  • Windows内存管理优化方案:Mem Reduct深度解析与实践指南
  • DistroAV:如何用开源NDI插件彻底改变你的OBS视频工作流
  • AI 智能电动地毯高效紧凑 MOSFET 核心选型方案
  • 大模型纪检涉案情节分析方案:让案件材料真正形成可研判的关系网络
  • 内网开发环境救星:手把手教你用K3s离线搭建轻量K8s集群(避坑指南)
  • 如何安全合规地管理微信数据:从PyWxDump项目下架看技术合规边界
  • 终极WebPShop插件:解锁Photoshop完整WebP处理能力
  • Scanpy数据预处理保姆级教程:用filter_cells、normalize_total等API搞定单细胞数据清洗
  • 别再暴力刷新了!用ScriptableObject和事件驱动重构Unity背包系统,性能提升实测