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

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

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

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

断点在哪里

关键函数只有两行:

ts 复制代码
private shouldUseSideNavigation(): boolean {
  return this.getWindowWidthVp() >= 600 && this.getWindowHeightVp() >= 360;
}

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

侧栏宽度也不是固定值:

ts 复制代码
private getSideNavigationWidth(): number {
  return this.getWindowWidthVp() >= 840 ? 118 : 96;
}

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

根布局如何切换

buildAdaptiveRoot() 把两种布局分开:

ts 复制代码
if (this.shouldUseSideNavigation()) {
  Row() {
    this.buildSideNavigation()
    Stack() {
      this.buildActiveTabContent()
    }
    .layoutWeight(1)
  }
} else {
  this.buildActiveTabContent()
}

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

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

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

ts 复制代码
if (this.shouldUseSideNavigation()) {
  return 0;
}

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

侧边导航的视觉和交互

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

ts 复制代码
.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,但侧边导航没有直接写成:

ts 复制代码
return deviceInfo.deviceType === 'tablet';

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

项目的真实策略是:

ts 复制代码
return this.getWindowWidthVp() >= 600 && this.getWindowHeightVp() >= 360;

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

断点矩阵

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

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

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

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

ts 复制代码
Row() {
  this.buildSideNavigation()

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

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

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

侧边栏容器最后调用:

ts 复制代码
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])

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

常见错误

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

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

复现步骤

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

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

小结

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

参考依据:

相关推荐
G_dou_2 小时前
Flutter+OpenHarmony实战:XMB Tracke
flutter·harmonyos·鸿蒙
脑极体10 小时前
点亮星河AI+鸿蒙,一座艺术场馆的日神觉醒
人工智能·华为·harmonyos
●VON10 小时前
鸿蒙Flutter实战:分类管理页BottomSheet CRUD
数据库·flutter·华为·harmonyos·鸿蒙
GitCode官方13 小时前
开源鸿蒙 PC 直播回顾|从环境搭建到真机验证:鸿蒙 PC 命令行迁移全链路。
华为·开源·harmonyos
想你依然心痛14 小时前
HarmonyOS 6(API 23)智能体驱动的沉浸式AR文化遗产数字修复工坊
华为·ar·harmonyos·智能体
largecode17 小时前
座机号码认证如何操作?申请热线实名名片,树立统一官方客服形象
linux·sql·华为·c#·.net·wpf·harmonyos
大雷神17 小时前
第07篇|权限分层策略:相机、定位、生物认证、手势为什么分开申请
harmonyos
●VON18 小时前
鸿蒙Flutter实战:水平滑动分类标签筛选栏
flutter·华为·harmonyos