本章导读:这是"寻迹校园 HarmonyOS NEXT 实战"系列第 37 篇。本文基于
Index.ets、Bottom/Top/SideNavigation、HomePage.ets与 2026-08-12 模拟器验收记录,完整拆解<600vp、600--839vp、840--1279vp、>=1280vp四档 ArkUI Shell 的导航、内容密度、状态归属和页面复用方式,同时明确"保留大屏代码"不等于当前应用市场已经支持 Tablet 或 2-in-1。

上图为原创生成的响应式架构插画,不是项目截图。四档宽度共享同一条 NavPathStack、同一组业务 Service 和同一份页面状态,只替换导航外壳与内容编排。
一、为什么不能把手机底栏直接拉到桌面
Phone 底部导航适合拇指操作,但在 1280vp 以上窗口中继续贴底,会带来三个问题:
- 用户视线和鼠标要在页面内容与屏幕底部之间反复移动;
- 横向空间被浪费,内容却仍保持窄单栏;
- 列表、详情和匹配候选无法同时保留上下文。
响应式设计不是"卡片变宽",而是根据输入方式、视线范围和可用空间调整信息架构。
二、四档宽度的项目契约
当前 Design Spec 和 Index.ets 使用三条边界形成四档:
| 档位 | 宽度 | 导航 | 首页结构 |
|---|---|---|---|
sm |
<600vp |
BottomNavigation |
单栏 Scroll |
md |
600--839vp |
TopNavigation |
列表/详情分栏,左列较窄 |
lg |
840--1279vp |
SideNavigation |
常驻侧栏 + 列表/详情双栏 |
xl |
>=1280vp |
SideNavigation |
常驻侧栏 + 34/44/22 三栏工作台 |
断点同时控制导航、首页分栏、内容最大宽度和辅助面板,不由每个页面自行猜测。
三、本章对应的真实文件
| 文件 | 职责 |
|---|---|
entry/src/main/ets/pages/Index.ets |
根宽度、四档判断、Shell 与路由 |
entry/src/main/ets/components/BottomNavigation.ets |
Phone 底部四入口 |
entry/src/main/ets/components/TopNavigation.ets |
md 顶部导航 |
entry/src/main/ets/components/SideNavigation.ets |
lg/xl 常驻侧栏 |
entry/src/main/ets/pages/HomePage.ets |
compact、split 与 workspace 首页 |
entry/src/main/ets/pages/ItemDetailPage.ets |
可嵌入的中栏详情 |
entry/src/main/ets/pages/MatchResultsPage.ets |
可嵌入的右栏候选 |
docs/design/all-pages-design-spec-v2.md |
断点与跨设备设计基准 |
Shell 集中在 Index,业务页面通过参数接收布局能力。
四、用 onAreaChange 获取真实布局宽度
根节点通过 onAreaChange 更新 screenWidth:
ts
.onAreaChange((oldArea: Area, newArea: Area) => {
const width: number = Number(newArea.width);
if (width !== this.screenWidth) this.screenWidth = width;
})
这里使用根布局区域,而不是硬编码设备型号。窗口缩放、横竖屏或多窗口改变可用宽度时,同一判断仍然成立。
五、断点判断为什么集中在 Index
Index.ets 只保留三组核心判断:
ts
private isMediumScreen(): boolean {
return this.screenWidth >= 600 && this.screenWidth < 840;
}
private isLargeScreen(): boolean {
return this.screenWidth >= 840;
}
private isExtraLargeScreen(): boolean {
return this.screenWidth >= 1280;
}
如果首页、消息、我的和发布页分别维护断点,600、840、1280 很容易出现边界差一 vp 的问题。集中判断后,页面只接收 splitView、mediumView、workspaceView 等语义参数。
六、sm:底部导航与单任务页面
sm < 600vp 使用 BottomNavigation。首页为单栏 Scroll,点击记录通过 NavPathStack 进入详情页:
- 内容宽度全部用于当前任务;
- 底栏四入口高度 72vp;
- 中央"登记"按钮使用 48×48vp 圆形操作;
- 搜索、筛选、列表按垂直顺序组织;
- 详情、匹配、发布和认领分别进入独立页面;
- 返回栈语义清晰,适合单手操作。
Phone 上不强行显示左右双栏,因为每一栏都会变得过窄。
七、md:顶部导航释放底部空间
600--839vp 使用 TopNavigation。顶部导航的每项高度为 48vp,内容区下方不再被固定底栏占用。
md 档首页已经开启 splitView,左侧查询列表约 42%,右侧展示详情。左列约束为 280--340vp,避免窄窗口中把详情压成一条竖线。
顶部导航并不意味着所有页面都要双栏;表单和次级路由仍可使用受控最大宽度,在宽屏中居中展示。
八、lg:常驻侧栏建立桌面级导航
840--1279vp 使用 232vp 宽的 SideNavigation。侧栏包含:
- 产品名和本机模式说明;
- 首页、登记、消息、我的四个稳定入口;
- 选中态品牌容器;
- 每个入口的可访问名称与动作描述;
- 底部"本机单机模式"边界提示。
内容区在侧栏右侧居中,最大宽度由 tabContent() 控制为 1120vp,避免窗口继续变宽时正文无限拉长。
九、xl:第三栏不是装饰,而是业务辅助
当宽度达到 1280vp,首页的 workspaceView 为真。此时:
- 左栏 34%:搜索、组合筛选和本机记录;
- 中栏 44%:当前记录或候选详情;
- 右栏 22%:当前查询记录的 Top 3 脱敏匹配候选;
- 点击右栏候选只更新中栏,不重置左栏;
- 侧栏仍保持全局导航与业务边界。
这比"显示更多卡片"更符合失物匹配任务,因为用户需要同时比较查询、证据和候选。

上图展示根宽度只决定 Shell,selectedTab、NavPathStack、查询条件、选中记录与 Service 数据不随布局分支重建。
十、三种导航组件为什么不能写成一个超大组件
Bottom、Top 和 Side 虽然入口相同,但信息结构不同:
- Bottom 使用等宽四项和中央圆形登记入口;
- Top 使用水平按钮,登记入口为主色矩形;
- Side 使用图标+文字的纵向列表,并显示产品与模式说明。
如果强行用一个组件加大量条件分支,布局、间距和焦点顺序会互相牵连。当前项目共享 BottomTab、图标、颜色和 onSelect 契约,但保留三个清晰组件。
十一、三个 Shell 如何共享同一个路由栈
Index 只创建一个:
ts
private readonly pathStack: NavPathStack = new NavPathStack();
Phone 的 Navigation 包裹底栏主内容;md 与 lg/xl 的 Navigation 分别位于顶部或侧栏 Shell 内,但都消费同一个 pathStack 和 destinationBuilder。
因此窗口变化不应创建新的路由对象,已打开的发布、认领或详情路径仍有权威来源。
十二、Tab 状态为什么放在 Shell 层
selectedTab 属于全局导航状态,保存在 Index:
ts
@Local selectedTab: BottomTab = BottomTab.HOME;
Bottom、Top、Side 只接收当前值和回调。布局切换后,新导航组件仍读取同一个 selectedTab,不会从"消息"莫名跳回"首页"。
这体现了一个关键原则:状态生命周期应高于使用它的布局分支。
十三、登记入口为什么不直接改变 selectedTab
登记不是常驻 Tab,而是次级流程:
ts
if (tab === BottomTab.PUBLISH) {
this.pathStack.pushPathByName(AppRoute.PUBLISH_TYPE, undefined);
return;
}
this.selectedTab = tab;
这样用户完成或取消登记后可以返回原 Tab,上层导航选中态不会被一个临时流程污染。
十四、HomePage 如何消费布局能力
Shell 下传三项参数:
ts
HomePage({
splitView: this.screenWidth >= 600,
mediumView: this.screenWidth >= 600 && this.screenWidth < 840,
workspaceView: this.screenWidth >= 1280
})
HomePage 根据能力选择 compactHome() 或 splitScreenHome(),而不是再次读取设备信息。这让页面更容易测试,也避免平台判断散落。
十五、分栏宽度为什么还需要 min/max
左栏百分比之外还有约束:
ts
.width(this.workspaceView ? '34%' : (this.mediumView ? '42%' : '38%'))
.constraintSize({
minWidth: this.workspaceView ? 300 : (this.mediumView ? 280 : 340),
maxWidth: this.workspaceView ? 460 : (this.mediumView ? 340 : 460)
})
百分比负责跟随窗口,minWidth/maxWidth 防止卡片过窄或搜索区无限变宽。响应式布局通常需要比例与约束共同工作。
十六、内容最大宽度如何保护阅读体验
tabContent() 在大屏上设置 1120vp 或 1480vp 上限。页面内部的表单和详情还会设置 720vp 上限。
这解决两类问题:
- 超宽屏长文本一行过长,阅读效率下降;
- 页面为了填满空间而把按钮和输入框拉得过大。
可用空间不等于必须占满的内容宽度。
十七、嵌入模式如何复用详情与匹配页
ItemDetailPage 和 MatchResultsPage 都支持 embedded:
- 独立页面模式显示自己的 Header 和返回逻辑;
- 嵌入模式隐藏重复导航,填充中栏或右栏;
- 业务加载、状态文案和按钮仍复用同一实现;
- 候选点击通过回调更新中栏,而不是再次 push 路由。
复用的是页面主体,不是把独立页面截图放进大屏容器。
十八、断点切换不应重新初始化业务层
aboutToAppear() 初始化 ReportService、ClaimService、HandoffService、ModerationService 和设置服务。宽度变化只更新 screenWidth,不重新创建 Repository 或重置数据。
如果在每个 Shell 分支中初始化业务层,拖动窗口可能形成重复数据库打开、重复请求或页面状态闪烁。
十九、loading、empty、error 在四档中仍需存在
响应式布局不能只对"有四条数据"进行设计。HomePage 的左列同时处理:
loading:正在加载本机记录;error:错误容器与重新加载;empty:登记丢失/拾得入口;- 组合筛选无结果:重置筛选;
- 正常列表:可选中记录卡片。
在 xl 中,左列没有选择时,中栏和右栏也有各自空态,不能显示空白框。
二十、键盘焦点如何跨 Shell 保持可达
三种导航都使用原生 Button,并提供 accessibilityText 与 accessibilityDescription。2026-08-12 的模拟器验收记录显示:
- xl 焦点从搜索、筛选、记录、中栏操作、候选、侧栏再回到搜索;
- Phone 焦点从搜索、筛选、四张记录、四个底栏入口再回到搜索;
- 精确 1280vp 时 Tab/Shift+Tab 能进入侧栏与右栏候选。
这证明了当时模拟器输入链路,不能替代实体键盘或屏幕阅读器验收。
二十一、运行证据必须带日期和环境
本系列引用的响应式证据来自 2026-08-12:
- MateBook Pro API 22 模拟器;
- Pura 90 API 24 模拟器;
xunji-responsive-focus-state-audit.md;xunji-exact-xl-breakpoint-input-audit.md;- 当时本地脚本与 APP 构建通过。
当前文章核对了现有断点代码仍然存在,但没有把旧模拟器证据冒充成 2026-08-26 的实体 2-in-1 复测。
二十二、首发 manifest 为什么仍只声明 phone
当前 entry/src/main/module.json5 明确:
json
"deviceTypes": ["phone"]
项目选择先以 Phone 降低首发审核成本,保留 Tablet/2-in-1 响应式代码和设计资产供后续版本使用。
因此准确表述是"代码包含四档响应式 Shell,并在模拟器完成过断点验收";不能表述为"应用市场当前支持四类设备"。
二十三、模拟器与实体设备的验证边界
模拟器可以验证布局切换、焦点事件和页面状态;实体设备仍要单独检查:
- 系统缩放和窗口吸附;
- 触控板、鼠标滚轮与蓝牙键盘;
- 折叠屏铰链与安全区;
- 系统字体和输入法;
- 性能、重绘和多窗口生命周期;
- 商店设备声明与实际安装兼容。
任何一项未运行都应写成 not run。
二十四、推荐的响应式 Shell 测试矩阵
| 宽度 | 预期 Shell | 重点断言 |
|---|---|---|
| 377vp | Bottom + 单栏 | 底栏、详情 push、单手操作 |
| 599vp | Bottom + 单栏 | 临界点前不提前分栏 |
| 600vp | Top + 双栏 | 导航切换、左列最小宽 |
| 839vp | Top + 双栏 | 临界点前仍为 md |
| 840vp | Side + 双栏 | 侧栏出现、Tab 状态保持 |
| 1278.95vp | Side + 双栏 | 右栏不出现、详情保持 |
| 1280vp | Side + 三栏 | 34/44/22、候选联动 |
测试数据、筛选条件和选中候选应固定,否则视觉差异无法定位到布局还是数据。
二十五、工程复盘:响应式首先是状态架构
四档 Shell 能稳定切换的关键,不是写了四个 if,而是把 selectedTab、pathStack、查询条件、选中记录和业务数据放在布局分支之上。Shell 只决定导航和编排,Service 与 Repository 仍是同一权威来源。
如果布局分支拥有自己的状态副本,窗口往返必然出现筛选重置、路由丢失和候选切换。
二十六、本文小结
"寻迹校园"用 600、840、1280 三个边界建立 sm/md/lg/xl 四档 ArkUI Shell:Phone 底栏、md 顶栏、lg 侧栏双栏、xl 侧栏三栏。Index.ets 集中读取根宽度并复用同一导航栈,HomePage 只接收语义布局参数。
现有代码与 2026-08-12 模拟器证据能证明响应式架构和断点行为;当前首发 manifest 仍只声明 Phone,实体 Tablet/2-in-1、外设和商店支持不能由保留代码推导。
系列导航:第 37 篇 / 共 50 篇。上一篇:《用语义 Token 管理 Light/Dark》;下一篇:《34%/44%/22% 三栏工作台》。