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

第21篇|侧边导航:平板和 2in1 为什么不照搬手机布局

第21篇|侧边导航:平板和 2in1 为什么不照搬手机布局

手机上的底部导航很自然,因为拇指容易触达;但平板和 2in1 的窗口更宽,底部导航会把页面压得很低,也会让地图、相册、保险箱这些内容区域失去横向优势。所以这篇不讲“把导航放左边更高级”,而是看代码里如何用窗口尺寸决定导航形态。

官方多设备最佳实践《响应式布局》《自适应布局》强调,界面需要随窗口大小、设备形态等变化调整布局。项目没有靠设备名硬切,而是用窗口宽高做第一判断。

断点在哪里

关键函数只有两行:

private shouldUseSideNavigation(): boolean { return this.getWindowWidthVp() >= 600 && this.getWindowHeightVp() >= 360; }

也就是说,只要窗口宽度达到 600vp,且高度不低于 360vp,就切换到侧边导航。这个条件比“平板才用侧边栏”稳,因为 2in1、PC 窗口化、平板分屏都可能改变真实可用区域。

侧栏宽度也不是固定值:

private getSideNavigationWidth(): number { return this.getWindowWidthVp() >= 840 ? 118 : 96; }

600vp 到 839vp 使用 96vp,840vp 以上使用 118vp。这样中等宽度窗口不会被导航吃掉太多空间,大屏窗口又能给文字和图标更舒服的点击区域。

根布局如何切换

buildAdaptiveRoot()把两种布局分开:

if (this.shouldUseSideNavigation()) { Row() { this.buildSideNavigation() Stack() { this.buildActiveTabContent() } .layoutWeight(1) } } else { this.buildActiveTabContent() }

手机形态直接渲染内容页,导航由内容页底部覆盖层负责;大屏形态先渲染侧边栏,再把当前内容放进右侧Stack。这比在每个页面里分别判断“要不要左边距”更干净,主布局入口只有一个。

地图页为什么要取消底部占位

第 19 篇讲过getMapBottomOverlayInset(),在侧边导航下它直接返回 0:

if (this.shouldUseSideNavigation()) { return 0; }

原因很实际:导航已经移到左侧,底部没有必要再为空导航保留 128/272vp。如果忘了这一步,大屏地图会莫名少一截,用户会以为底部还有内容没展开。

侧边导航的视觉和交互

buildSideNavigation()仍然复用buildNavIconMark(tab),因此图标资源和底部导航一致;差异只在容器方向、宽度、点击区域和安全区处理:

.width(this.getSideNavigationWidth()) .height('100%') .backgroundColor($r('app.color.ml_nav_glass')) .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])

这里的expandSafeArea让侧边栏贴合系统区域,避免大屏或沉浸式窗口下顶部/底部出现断层。选中态仍然通过ml_selected_glass、边框、阴影和hdsEffect表达,不需要重新设计另一套视觉语言。

自测建议

  • 手机竖屏:底部导航出现,侧边栏不出现。
  • 宽度约 600vp:侧边栏出现,底部导航消失。
  • 宽度约 840vp:侧边栏从 96vp 切到 118vp,文字不截断。
  • 地图页大屏:底部不再保留导航占位。
  • 2in1/PC:shouldShowHomeXiaoYiOverlay()会避开桌面类设备,避免首页叠层过多。

为什么用窗口断点,不用设备名

官方多设备文档反复强调“窗口大小”和“可用显示区域”。这和传统手机应用的设备适配思路不一样。项目里虽然也能拿到deviceInfo.deviceType,但侧边导航没有直接写成:

return deviceInfo.deviceType === 'tablet';

原因有三个。第一,2in1 在平板和桌面之间切换,设备名不能表达当前窗口。第二,平板分屏后可用宽度可能小于普通手机横屏。第三,PC 窗口可自由缩放,如果只看设备名,窄窗口也会被强行塞进侧边栏。

项目的真实策略是:

return this.getWindowWidthVp() >= 600 && this.getWindowHeightVp() >= 360;

这句代码把问题从“我是什么设备”改成“我现在有多少空间”,更符合响应式布局的判断依据。

断点矩阵

宽度和高度导航形态侧栏宽度地图底部 inset
宽度小于 600vp底部导航128/272 + safeArea
宽度大于等于 600vp,高度小于 360vp底部导航128/272 + safeArea
600vp 到 839vp,且高度足够侧边导航96vp0
840vp 以上,且高度足够侧边导航118vp0

这张表可以直接作为验收依据。尤其要注意高度条件:有些横向窗口很宽但很矮,如果强行放侧边栏,内容区会变得像一条横幅,操作反而困难。

代码验证 1:侧边栏是主布局的一部分

侧边导航不在某个页面内部补左边距,而是在根布局中占位:

Row() { this.buildSideNavigation() Stack() { this.buildActiveTabContent() } .layoutWeight(1) .height('100%') } .backgroundColor($r('app.color.album_background'))

这说明侧边栏和内容区是兄弟节点。好处是每个 Tab 不需要各自记住“左边有一个导航栏”,相机、地图、相册和保险箱都只关心自己的内容。主布局负责空间分配,子页面负责业务状态,这样代码边界更清楚。

代码验证 2:侧栏也使用安全区

侧边栏容器最后调用:

.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])

这个细节容易漏。大屏、沉浸式窗口、折叠屏和 2in1 模式下,顶部和底部系统区域不一定和手机完全一致。侧栏扩展到系统安全区,能避免上方出现一条突兀空隙,也能让导航背景和整个窗口边界对齐。

常见错误

第一种错误是“只移动导航,不移动空间责任”。比如把底部导航改成左侧绝对定位,但内容区仍然按全屏宽度计算,最后侧栏盖住地图或列表。第二种错误是“侧栏宽度固定”,在 600vp 附近占掉太多内容,在 1000vp 以上又显得局促。第三种错误是“底部导航没有退出”,大屏同时存在两套入口,用户不知道哪个才是主路径。

本文的实现分别用Row根布局、getSideNavigationWidth()buildBottomNavigation()的早返回解决这三个问题。

复现步骤

  1. 在预览器中把窗口宽度调到 599vp,确认底部导航还在。
  2. 调到 600vp,确认侧边栏出现,底部导航消失。
  3. 调到 840vp,确认侧边栏加宽,文字仍然是一行。
  4. 进入地图页,确认底部没有多余空白。
  5. 切换相册或保险箱,确认右侧内容区自动占满剩余宽度。

这组步骤能覆盖窗口断点、导航互斥、内容占位和安全区四类风险,比只截一张大屏图更有说服力。

小结

一多适配不是把手机界面等比放大,而是把“路径入口”和“内容空间”重新分配。这个项目的侧边导航实现有三个好处:断点基于窗口而非设备标签,根布局统一切换,导航资源和选中态复用手机实现。这样改动面积小,但用户在平板和 2in1 上拿到的是另一种合适的布局,而不是被拉宽的手机页。

参考依据:

  • 华为开发者最佳实践《响应式布局》:https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-multi-device-responsive-layout
  • 华为开发者最佳实践《自适应布局》:https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-multi-device-adaptive-layout
  • 项目源码:entry/src/main/ets/pages/Index.ets
http://www.jsqmd.com/news/913031/

相关文章:

  • 保姆级教程:用Matlab/Simulink+CarSim复现平行泊车仿真(附模型文件与避坑点)
  • CSS Transitions 过渡效果详解
  • 抖音音频提取革命:3分钟搞定批量下载的开源神器
  • Claude生成代码质量究竟如何?37项实测指标揭穿90%开发者忽略的隐藏风险
  • C++跨平台开发:微信聊天记录导出工具架构解析与实现
  • 【雷达干扰】FMCW 雷达稀疏低秩 Hankel 矩阵分解的干扰抑制附Matlab代码
  • 2026年近期,如何选择行业知名的液压马达定制厂家? - 2026年企业资讯
  • 挖坑指南:为什么你的数据采集卡老是“丢帧”?一篇文章讲透Flash、FRAM、PSRAM的区别与实战
  • 三步轻松复活经典游戏联机:IPXWrapper让老游戏重获新生
  • 别再瞎测了!用IxChariot给工业网关做吞吐量测试,这5个坑我帮你踩过了
  • 隐形冠军舜展智能:16年磨一剑,用等离子技术点亮中国高端制造
  • 第19篇|沉浸式首页:地图、玻璃层、信息卡片的层级关系
  • 制造业AI智能体选型:跨系统执行、任务拆解与信创适配三大技术维度对比
  • Photoshop AVIF插件深度探索:为什么这款开源神器正在改变图像处理工作流?
  • 从Windows转战Ubuntu?手把手教你无缝迁移Beyond Compare使用习惯(含dpkg安装与破解详解)
  • 16位ADC不够用?别急着换芯片!教你用“过采样+滑动平均”榨出24位极致精度
  • 别再重装系统了!LightDM报错‘Failed to Start’的5种修复方案与深度解析
  • Flutter Hero Animation 详解
  • 2026年Q2北京铝合金回收:北京溴化锂机组回收/北京电器回收/北京电子设备回收/北京电池回收/北京电线电缆回收/选择指南 - 优质品牌商家
  • 从MODBUS协议栈到你的代码:深入理解CRC-16校验的‘位反序’到底在干什么?
  • 高性能语音合成部署:基于Sherpa-Onnx的MeloTTS多语言模型转换与优化方案
  • 文泉驿微米黑终极安装指南:5MB轻量级中文字体跨平台快速部署
  • 【图像提取】基于数学形态学的数字视网膜图像血管提取 (DRIVE) 数据集分割附Matlab代码
  • 【AI搜索革命性差异指南】:3大核心维度拆解AI搜索与传统搜索的底层逻辑差异
  • 别只用来聊天!解锁BitoAI在VSCode中的5个高效编程场景(含代码规范检查与性能优化)
  • FastAdmin后台开发实战:手把手教你从零新增一个自定义管理页面(ThinkPHP6框架)
  • Simulink封装模块的‘隐藏关卡’:初始化命令与回调函数实战指南(避坑+案例)
  • 深入Windows消息循环:手把手教你用Unity拦截WM_SIZING实现自定义窗口控制
  • 【绿化】Fong投屏 一键手机投屏 多设备兼容超稳定
  • 给STM32CubeIDE新手的第一份保姆级环境搭建指南(含JRE安装、汉化、主题美化)