小雨同学 2026-05-13 08:44:21 发布前言
Pura X Max 展开后,列表页最明显的变化是横向空间变多了。外屏下点击一条记录再进入详情页,这个路径很自然;到了展开态,如果还沿用同样的跳转方式,用户就会在列表页和详情页之间反复切换,屏幕右侧的大块空间也没有被用起来。
Pura X Max 外屏为 5.4 英寸,内屏为 7.7 英寸,系统版本为 HarmonyOS 6.1。外屏和内屏的尺寸差异足够明显,列表页在展开态下可以把列表和详情放到同一个页面里。用户点击左侧列表,右侧详情直接更新,阅读路径会短很多。
HarmonyOS 多设备页面布局里,空间充足时可以采用分栏布局,把窗口划分为两栏或三栏,用来展示多类内容。响应式布局也把分栏布局作为常见方式之一,适合把导航区和内容区同屏左右展示。
我在处理列表详情页时,会先保留窄屏的普通列表体验,再给展开态增加右侧详情区。这样做不会影响外屏的操作习惯,也能让内屏真正承担更多信息。

问题出在展开态仍然跳转
普通手机上的列表页,一般是这样的路径:
点击列表项。
进入详情页。
返回列表。
再点击另一项。
这个路径适合窄屏,因为一屏很难同时放下列表和详情。用户每次只关注一个页面,层级也比较清楚。
Pura X Max 展开态就不一样了。内屏宽度变大以后,继续让列表占满整屏,会出现几个问题。
列表卡片被横向拉宽,但信息密度没有明显提升。
查看不同记录时,需要频繁进入详情和返回列表。
用户已经拥有更大的屏幕,页面仍然按窄屏流程工作。
这种页面更适合做成主从结构。左侧列表负责选择对象,右侧详情负责展示内容。用户不需要离开当前页面,就能快速比较不同记录。
这里不需要一开始就引入复杂路由。先用 Row 把页面分成左右两块,左侧放列表,右侧放当前选中项的详情。Row 本身就是沿水平方向布局的容器,适合搭建这种左右结构。
用 Row 改成列表详情
页面仍然根据窗口宽度判断布局状态。
窄屏进入 compact,只展示列表。这个状态适合外屏、普通手机宽度和分屏后的窄窗口。
宽屏进入 expanded,页面切成左右两栏。左侧列表负责切换选中项,右侧详情展示选中记录的完整内容。
判断逻辑仍然保持简单:
private readonly expandedWidth: number = 760;private isExpanded(): boolean { return this.pageWidth >= this.expandedWidth;}这里把阈值设置成 760vp,是为了给右侧详情区留出足够空间。列表详情联动比双列卡片更吃宽度,如果阈值过低,左右两栏都会显得挤。
展开态布局可以这样理解:
Row() { // 左侧列表 // 右侧详情}窄屏布局则保持单页面列表:
Column() { // 普通列表}这个结构的关键点在于:布局可以变化,但选中数据不变。当前选中的记录由 selectedId 保存,左侧列表点击后更新它,右侧详情根据它渲染内容。
把列表和详情放进同一个页面
下面这个页面模拟了一组材料记录。窄窗口下只显示普通列表;展开态下,左侧显示列表,右侧显示当前选中记录详情。点击左侧不同记录,右侧详情会立即变化。
页面放在 entry/src/main/ets/pages/Index.ets 即可运行。Pura X Max 适配调试可以使用 DevEco Studio 6.1.0,并安装 Pura X Max 模拟器验证不同窗口形态下的表现。
interface MaterialItem { id: number; title: string; status: string; source: string; time: string; tag: string; owner: string; summary: string; detail: string; todo: string;}@Entry@Componentstruct Index { @State private pageWidth: number = 0; @State private selectedId: number = 1; private readonly expandedWidth: number = 760; private readonly materials: MaterialItem[] = [ { id: 1, title: '社区物业缴费提醒', status: '待处理', source: '拍照整理', time: '09:20', tag: '通知', owner: '物业服务中心', summary: '识别到缴费截止日期、费用明细和办理地点。', detail: '这条记录来自一张物业缴费通知。内容里包含缴费周期、应缴金额、截止日期和办理地点。折叠态下只需要知道它是一条待处理提醒,展开态下可以直接看到更多上下文,减少进入详情页的次数。', todo: '添加缴费提醒,并确认是否需要同步到日程。' }, { id: 2, title: 'Pura X Max 适配会议纪要', status: '待确认', source: '语音转写', time: '10:45', tag: '会议', owner: '产品研发组', summary: '整理出外屏、展开态、横屏和悬停态几类页面问题。', detail: '会议讨论了多个页面在 Pura X Max 上的展示问题,其中列表页、详情页、设置页和图片预览页都需要重新检查窗口变化后的布局表现。展开态更适合使用列表详情结构,减少页面跳转。', todo: '确认适配清单,并把列表详情联动加入开发任务。' }, { id: 3, title: '活动报名确认单', status: '已保存', source: '相册导入', time: '11:30', tag: '表单', owner: '活动运营', summary: '提取到报名人、联系方式、活动时间和签到地址。', detail: '这条记录适合在列表右侧直接查看摘要和关键字段。用户通常只是确认活动时间和地点,不一定需要进入完整详情页。', todo: '保留记录,并在活动前一天提醒。' }, { id: 4, title: '客户需求变更记录', status: '待处理', source: '文本整理', time: '13:10', tag: '项目', owner: '客户成功组', summary: '本次变更涉及首页布局、权限配置和通知策略。', detail: '需求变更类记录往往需要反复对照多个条目。展开态下把列表和详情放在同一屏,可以减少返回列表的频率,也方便连续检查不同变更项。', todo: '同步项目负责人,并拆分到研发排期。' }, { id: 5, title: '课程作业提交说明', status: '已整理', source: '拍照整理', time: '15:25', tag: '学习', owner: '课程助教', summary: '识别到提交时间、文件格式、命名规范和邮箱地址。', detail: '学习类通知一般字段较多。展开态详情区可以把提交要求完整展示出来,左侧列表继续保留其他记录,切换查看会更快。', todo: '创建作业待办,并保留提交格式说明。' } ]; private isExpanded(): boolean { return this.pageWidth >= this.expandedWidth; } private getSelectedItem(): MaterialItem { const found = this.materials.find((item: MaterialItem) => item.id === this.selectedId); return found ? found : this.materials[0]; } private getPagePadding(): number { return this.isExpanded() ? 24 : 16; } private getStatusColor(status: string): string { if (status === '待处理') { return '#B25E00'; } if (status === '待确认') { return '#7C3AED'; } return '#276749'; } private getStatusBgColor(status: string): string { if (status === '待处理') { return '#FFF4E5'; } if (status === '待确认') { return '#F1EAFE'; } return '#E7F5EE'; } @Builder private StatusPill(status: string) { Text(status) .fontSize(12) .fontColor(this.getStatusColor(status)) .padding({ left: 8, right: 8, top: 4, bottom: 4 }) .backgroundColor(this.getStatusBgColor(status)) .borderRadius(999) } @Builder private ListCard(item: MaterialItem) { Column({ space: 10 }) { Row({ space: 8 }) { this.StatusPill(item.status) if (this.isExpanded()) { Text(item.tag) .fontSize(12) .fontColor('#2F8F83') .padding({ left: 8, right: 8, top: 4, bottom: 4 }) .backgroundColor('#E6F4F1') .borderRadius(999) } Blank() if (this.selectedId === item.id) { Text('当前') .fontSize(12) .fontColor('#2F8F83') } } .width('100%') Text(item.title) .fontSize(17) .fontWeight(FontWeight.Medium) .fontColor('#111827') .maxLines(2) .textOverflow({ overflow: TextOverflow.Ellipsis }) if (this.isExpanded()) { Text(item.summary) .fontSize(13) .fontColor('#6B7280') .lineHeight(19) .maxLines(2) .textOverflow({ overflow: TextOverflow.Ellipsis }) } Row({ space: 8 }) { Text(item.source) .fontSize(12) .fontColor('#6B7280') Text('·') .fontSize(12) .fontColor('#9CA3AF') Text(item.time) .fontSize(12) .fontColor('#6B7280') } .width('100%') } .width('100%') .padding(15) .backgroundColor(this.selectedId === item.id ? '#EEF7F5' : '#FFFFFF') .borderRadius(18) .border({ width: this.selectedId === item.id ? 1.5 : 1, color: this.selectedId === item.id ? '#2F8F83' : '#E5E7EB' }) .shadow({ radius: this.selectedId === item.id ? 12 : 8, color: '#12000000', offsetX: 0, offsetY: 4 }) .onClick(() => { this.selectedId = item.id; }) } @Builder private ListPanel() { Column({ space: 12 }) { Row() { Column({ space: 4 }) { Text(this.isExpanded() ? '材料列表' : '整理记录') .fontSize(this.isExpanded() ? 22 : 24) .fontWeight(FontWeight.Bold) .fontColor('#111827') Text(this.isExpanded() ? '点击左侧记录,右侧详情会同步更新' : '外屏保持普通列表浏览') .fontSize(14) .fontColor('#6B7280') } .layoutWeight(1) Text(Math.round(this.pageWidth).toString() + 'vp') .fontSize(12) .fontColor('#374151') .padding({ left: 10, right: 10, top: 6, bottom: 6 }) .backgroundColor('#FFFFFF') .borderRadius(999) } .width('100%') Scroll() { Column({ space: 12 }) { ForEach(this.materials, (item: MaterialItem) => { this.ListCard(item) }, (item: MaterialItem) => item.id.toString()) } .width('100%') .padding({ bottom: 20 }) } .layoutWeight(1) .width('100%') .edgeEffect(EdgeEffect.Spring) } .width('100%') .height('100%') } @Builder private DetailPanel(item: MaterialItem) { Column({ space: 18 }) { Row() { this.StatusPill(item.status) Blank() Text(item.tag) .fontSize(13) .fontColor('#2F8F83') .padding({ left: 10, right: 10, top: 5, bottom: 5 }) .backgroundColor('#E6F4F1') .borderRadius(999) } .width('100%') Column({ space: 8 }) { Text(item.title) .fontSize(27) .fontWeight(FontWeight.Bold) .fontColor('#111827') .lineHeight(34) Text(item.summary) .fontSize(15) .fontColor('#4B5563') .lineHeight(22) } .width('100%') .alignItems(HorizontalAlign.Start) Row({ space: 10 }) { this.MetaBlock('来源', item.source) this.MetaBlock('时间', item.time) this.MetaBlock('负责人', item.owner) } .width('100%') Column({ space: 8 }) { Text('内容整理') .fontSize(16) .fontWeight(FontWeight.Medium) .fontColor('#111827') Text(item.detail) .fontSize(15) .fontColor('#4B5563') .lineHeight(24) } .width('100%') .padding(16) .backgroundColor('#F9FAFB') .borderRadius(18) Column({ space: 8 }) { Text('建议动作') .fontSize(16) .fontWeight(FontWeight.Medium) .fontColor('#111827') Text(item.todo) .fontSize(15) .fontColor('#4B5563') .lineHeight(23) } .width('100%') .padding(16) .backgroundColor('#F3F8F7') .borderRadius(18) Blank() Row({ space: 12 }) { Button('标记完成') .fontSize(15) .fontColor('#FFFFFF') .height(42) .layoutWeight(1) .backgroundColor('#2F8F83') .borderRadius(21) Button('进入详情') .fontSize(15) .fontColor('#2F8F83') .height(42) .layoutWeight(1) .backgroundColor('#E6F4F1') .borderRadius(21) } .width('100%') } .width('100%') .height('100%') .padding(24) .backgroundColor('#FFFFFF') .borderRadius(24) .shadow({ radius: 12, color: '#10000000', offsetX: 0, offsetY: 4 }) } @Builder private MetaBlock(label: string, value: string) { Column({ space: 4 }) { Text(label) .fontSize(12) .fontColor('#9CA3AF') Text(value) .fontSize(14) .fontColor('#374151') .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }) } .layoutWeight(1) .padding(12) .backgroundColor('#F9FAFB') .borderRadius(14) } build() { Column() { if (this.isExpanded()) { Row({ space: 18 }) { Column() { this.ListPanel() } .width(340) .height('100%') Column() { this.DetailPanel(this.getSelectedItem()) } .layoutWeight(1) .height('100%') } .width('100%') .height('100%') .padding(24) } else { Column() { this.ListPanel() } .width('100%') .height('100%') .padding({ left: this.getPagePadding(), right: this.getPagePadding(), top: 18 }) } } .width('100%') .height('100%') .backgroundColor('#F6F7F9') .onAreaChange((_: Area, newValue: Area) => { const width = Number(newValue.width); if (!Number.isNaN(width) && width > 0) { this.pageWidth = width; } }) }}关键实现点和适配边界
这个页面最重要的状态只有两个。
一个是窗口宽度。
@State private pageWidth: number = 0;另一个是当前选中记录。
@State private selectedId: number = 1;宽度决定当前采用列表模式还是列表详情模式,选中记录决定右侧展示什么内容。
private isExpanded(): boolean { return this.pageWidth >= this.expandedWidth;}展开态通过 Row 切出左右两栏。
Row({ space: 18 }) { Column() { this.ListPanel() } .width(340) Column() { this.DetailPanel(this.getSelectedItem()) } .layoutWeight(1)}左侧宽度我设置成 340vp。这个数值不是固定标准,只是为了让列表项在展开态下保持稳定宽度。真实项目里可以根据业务列表内容调整,比如记录标题普遍较长,可以给到 360vp;如果只是短标题列表,320vp 就够用。
右侧详情使用 layoutWeight(1) 占满剩余空间。这样窗口继续变宽时,详情区获得更多空间,左侧列表不会被拉得过宽。
窄屏下只渲染 ListPanel()。
Column() { this.ListPanel()}真实项目里,窄屏点击列表项通常会进入详情页。为了让页面能在一个 Index.ets 里直接看到效果,示例里保留了点击选中状态,没有额外做路由跳转。回到项目时,可以把 compact 下的点击事件换成 Navigation 路由,把 expanded 下的点击事件保留为更新 selectedId。
这个方案适合材料列表、会议列表、客户列表、任务列表、消息列表等场景。它不适合所有列表。像聊天消息、时间线动态、审批流记录这类强顺序内容,更适合保持单列连续阅读,不宜强行拆成左右两栏。
验证要看什么
外屏或窄窗口里,页面应该是普通列表。顶部显示当前窗口宽度,列表从上到下排列,卡片宽度不会被压缩。这个状态下要重点看卡片标题是否能读完,状态标签是否明显,滚动是否自然。

展开态里,左侧应该是固定宽度列表,右侧是详情内容。点击左侧不同记录,右侧标题、摘要、来源、负责人、内容整理和建议动作都会切换。这个状态下要重点看三个位置。
左侧列表是否过宽。
右侧详情是否有足够阅读空间。
点击切换时,选中态和详情内容是否一致。
如果右侧详情只是重复左侧标题,分栏的价值就不明显。展开态的详情区应该承载更多上下文,比如摘要、完整整理内容、建议动作、操作按钮和关联信息。

总结
Pura X Max 展开态适合把列表页从单页面浏览改成列表详情联动。
外屏继续保持普通列表,符合窄屏操作习惯;展开态把列表和详情放到同一屏,用户点击左侧记录,右侧内容直接更新,减少来回跳转。这个结构的关键是把窗口宽度和选中状态分开处理。窗口宽度决定页面结构,选中状态决定详情内容。
实际项目里可以把这个思路放到材料整理、会议记录、客户资料、任务管理和设置分类页面中。只要列表项和详情之间存在频繁切换的需求,展开态分栏就能明显减少操作路径。
相关推荐
鸿蒙小助手
531
0
鸿蒙小助手
266
0
291
0
小雨同学
产品总监、独立开发者社群主理人、资深全栈工程师,HarmonyOS应用开发者高级认证,PMP认证,CSDN博客专家,鸿蒙极客,Trae Fellow,阿里云社区专家博主、51CTO 博客专家、OpenTiny 优秀布道师、科大讯飞荣誉讲师。
帖子
提问
粉丝
鸿蒙 HarmonyOS 6 | Pura X Max 鸿蒙原生适配 02:折叠态页面的信息密度控制
2026-05-12 08:22:06 发布
京公网安备:11010502051901号