鸿蒙 HarmonyOS 6 | Pura X Max 鸿蒙原生适配 02:折叠态页面的信息密度控制 原创
头像 小雨同学 2026-05-12 08:22:06    发布
3 浏览 0 点赞 0 收藏

前言

Pura X Max 的外屏并不小,但它仍然属于折叠态下的窄窗口场景。列表页如果把标题、摘要、时间、来源、标签、状态、按钮全都塞进一张卡片,外屏很快就会变得拥挤。用户真正想先看到的是这条记录是什么、当前处于什么状态、能不能马上处理,其他信息可以等到空间足够时再展示。

Pura X Max 的外屏为 5.4 英寸,内屏为 7.7 英寸,外屏分辨率为 1848 × 1264,内屏分辨率为 2584 × 1828,系统版本为 HarmonyOS 6.1。外屏和内屏的尺寸差异很明显,同一张卡片在两种形态下不应该展示完全相同的信息量。

我在处理这类页面时,会先把业务数据拆成两层:核心信息和扩展信息。折叠态只保留核心信息,展开态再展示扩展信息。这样做之后,外屏不会因为字段太多而显得挤,展开态也不会因为信息太少而显得空。

问题出在字段没有分层

很多卡片一开始会把所有字段都展示出来。比如一条整理记录,可能包含这些内容:

标题。

状态。

来源。

时间。

标签。

摘要。

优先级。

操作按钮。

这些字段都重要,但它们的重要性并不一样。

外屏空间有限时,标题和状态的优先级最高。标题让用户知道这条记录是什么,状态让用户知道是否需要处理,主操作按钮让用户可以继续下一步。摘要、来源、时间、标签这些信息虽然有价值,但它们不一定要在折叠态里全部出现。

如果卡片没有信息层级,折叠态会出现几个问题。

卡片高度变大,一屏能看到的记录变少。

摘要占据太多空间,标题反而不突出。

标签、来源、时间同时出现,视觉噪声变多。

主操作按钮被挤到卡片底部,点击路径变长。

折叠态页面更适合快速浏览。用户先扫一遍标题和状态,再决定是否进入处理。展开态有更大的显示空间,可以把摘要、来源、时间、标签展示出来,帮助用户减少点击和跳转。

把同一条数据拆成两层

信息密度控制的关键,是在 UI 层明确区分核心信息和扩展信息。

核心信息放在 compact 状态下:

titlestatusprimaryAction

扩展信息放在 expanded 状态下:

summarysourcetimetagpriority

这不是简单隐藏几个字段。字段分层之后,页面的阅读路径会更清楚。外屏下,用户只需要扫标题和状态;展开态下,用户可以在不进入详情的情况下看到更多上下文。

响应式布局本身强调根据屏幕尺寸和窗口变化调整页面结构,让不同设备和不同窗口尺寸下的阅读、交互体验保持稳定。Pura X Max 这种外屏和内屏差异明显的设备,尤其适合用断点控制字段显示。

我这里仍然使用窗口宽度做判断。页面宽度低于 720vp 时进入 compact,只显示核心信息;达到 720vp 后进入 expanded,显示更多字段。

private readonly expandedWidth: number = 720;private isExpanded(): boolean {  return this.pageWidth >= this.expandedWidth;}

真实项目里可以继续细分,例如 compact、medium、expanded 三档。外屏只显示标题和状态,普通平板显示标题、状态和时间,Pura X Max 展开态显示摘要和标签。为了让问题聚焦,这里先保留两档。

用一个页面验证信息密度变化

下面的页面模拟了一组信息整理记录。窄窗口下,卡片只展示标题、状态和处理按钮;窗口变宽后,摘要、来源、时间、标签和优先级会一起出现。这样可以比较直观看到同一组数据在折叠态和展开态下的信息差异。

页面可以放到 entry/src/main/ets/pages/Index.ets 运行。Pura X Max 适配调试可以使用 DevEco Studio 6.1.0,并安装 Pura X Max 模拟器验证外屏和展开态效果。

interface InfoCardData {  id: number;  title: string;  status: string;  actionText: string;  summary: string;  source: string;  time: string;  tag: string;  priority: string;}​@Entry@Componentstruct Index {  @State private pageWidth: number = 0;  @State private selectedId: number = 1;​  private readonly expandedWidth: number = 720;​  private readonly cards: InfoCardData[] = [    {      id: 1,      title: '社区物业缴费提醒',      status: '待处理',      actionText: '处理',      summary: '识别到物业费缴纳截止日期、金额明细和办理地点,建议添加提醒。',      source: '拍照整理',      time: '09:20',      tag: '通知',      priority: '高优先级'    },    {      id: 2,      title: 'Pura X Max 适配会议纪要',      status: '待确认',      actionText: '确认',      summary: '整理出外屏、展开态、横屏和悬停态几类页面问题,适合作为后续开发清单。',      source: '语音转写',      time: '10:45',      tag: '会议',      priority: '中优先级'    },    {      id: 3,      title: '活动报名确认单',      status: '已保存',      actionText: '查看',      summary: '提取到报名人、联系电话、活动时间和签到地址,后续可以加入日程。',      source: '相册导入',      time: '11:30',      tag: '表单',      priority: '普通'    },    {      id: 4,      title: '客户需求变更记录',      status: '待处理',      actionText: '处理',      summary: '本次变更涉及首页布局、权限配置、消息提醒和后台字段展示,需要同步给开发。',      source: '文本整理',      time: '13:10',      tag: '项目',      priority: '高优先级'    },    {      id: 5,      title: '课程作业提交说明',      status: '已整理',      actionText: '查看',      summary: '识别到提交时间、文件格式、命名规范和邮箱地址,适合保存为待办。',      source: '拍照整理',      time: '15:25',      tag: '学习',      priority: '普通'    },    {      id: 6,      title: '门诊复查预约提示',      status: '已保存',      actionText: '查看',      summary: '提取到复查时间、科室、楼层和注意事项,可以作为健康提醒保存。',      source: '相册导入',      time: '16:40',      tag: '提醒',      priority: '中优先级'    }  ];​  private isExpanded(): boolean {    return this.pageWidth >= this.expandedWidth;  }​  private getColumnsTemplate(): string {    return this.isExpanded() ? '1fr 1fr' : '1fr';  }​  private getPagePadding(): number {    return this.isExpanded() ? 24 : 16;  }​  private getHeaderTitle(): string {    return this.isExpanded() ? '展开态显示完整上下文' : '折叠态保留核心信息';  }​  private getHeaderDesc(): string {    if (this.isExpanded()) {      return '摘要、来源、时间、标签和优先级已经展开,适合在大屏上快速判断记录内容。';    }​    return '当前只保留标题、状态和主操作,减少外屏卡片里的字段堆叠。';  }​  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 MetaPill(text: string) {    Text(text)      .fontSize(12)      .fontColor('#4B5563')      .padding({ left: 8, right: 8, top: 4, bottom: 4 })      .backgroundColor('#F3F4F6')      .borderRadius(999)  }​  @Builder  private InfoCard(item: InfoCardData) {    Column({ space: 12 }) {      Row({ space: 10 }) {        Column({ space: 8 }) {          Text(item.title)            .fontSize(this.isExpanded() ? 18 : 17)            .fontWeight(FontWeight.Medium)            .fontColor('#111827')            .maxLines(this.isExpanded() ? 1 : 2)            .textOverflow({ overflow: TextOverflow.Ellipsis })​          Row({ space: 8 }) {            Text(item.status)              .fontSize(12)              .fontColor(this.getStatusColor(item.status))              .padding({ left: 8, right: 8, top: 4, bottom: 4 })              .backgroundColor(this.getStatusBgColor(item.status))              .borderRadius(999)​            if (!this.isExpanded()) {              Text('核心信息')                .fontSize(12)                .fontColor('#6B7280')            }          }        }        .layoutWeight(1)​        Button(item.actionText)          .fontSize(13)          .fontColor('#FFFFFF')          .height(32)          .padding({ left: 12, right: 12 })          .backgroundColor('#2F8F83')          .borderRadius(16)          .onClick(() => {            this.selectedId = item.id;          })      }      .width('100%')      .alignItems(VerticalAlign.Top)​      if (this.isExpanded()) {        Text(item.summary)          .fontSize(14)          .fontColor('#4B5563')          .lineHeight(20)          .maxLines(2)          .textOverflow({ overflow: TextOverflow.Ellipsis })​        Row({ space: 8 }) {          this.MetaPill(item.source)          this.MetaPill(item.time)          this.MetaPill(item.tag)          this.MetaPill(item.priority)        }        .width('100%')      }    }    .width('100%')    .padding(this.isExpanded() ? 18 : 16)    .backgroundColor(this.selectedId === item.id ? '#EEF7F5' : '#FFFFFF')    .borderRadius(20)    .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    })  }​  build() {    Column({ space: 16 }) {      Column({ space: 8 }) {        Row() {          Column({ space: 4 }) {            Text('信息密度控制')              .fontSize(this.isExpanded() ? 28 : 23)              .fontWeight(FontWeight.Bold)              .fontColor('#111827')​            Text(this.getHeaderTitle())              .fontSize(15)              .fontColor('#2F8F83')          }          .layoutWeight(1)​          Text(Math.round(this.pageWidth).toString() + 'vp')            .fontSize(13)            .fontColor('#374151')            .padding({ left: 10, right: 10, top: 6, bottom: 6 })            .backgroundColor('#FFFFFF')            .borderRadius(999)        }        .width('100%')​        Text(this.getHeaderDesc())          .fontSize(14)          .fontColor('#6B7280')          .lineHeight(21)      }      .width('100%')      .padding({        left: this.getPagePadding(),        right: this.getPagePadding(),        top: 18      })​      Scroll() {        Grid() {          ForEach(this.cards, (item: InfoCardData) => {            GridItem() {              this.InfoCard(item)            }          }, (item: InfoCardData) => item.id.toString())        }        .columnsTemplate(this.getColumnsTemplate())        .columnsGap(12)        .rowsGap(12)        .width('100%')        .padding({          left: this.getPagePadding(),          right: this.getPagePadding(),          bottom: 24        })      }      .layoutWeight(1)      .width('100%')      .edgeEffect(EdgeEffect.Spring)    }    .width('100%')    .height('100%')    .backgroundColor('#F6F7F9')    .onAreaChange((_: Area, newValue: Area) => {      const width = Number(newValue.width);      if (!Number.isNaN(width) && width > 0) {        this.pageWidth = width;      }    })  }}

关键实现点和适配边界

这里的实现重点不在卡片样式,而在字段显示策略。

pageWidth 记录页面当前可用宽度,isExpanded() 判断是否进入展开态。页面根容器绑定 onAreaChange 后,窗口尺寸变化会更新 pageWidth。组件区域变化事件会在组件显示尺寸或位置变化时触发,适合用来处理这类页面级布局响应。

@State private pageWidth: number = 0;​private isExpanded(): boolean {  return this.pageWidth >= this.expandedWidth;}

卡片里的核心信息一直显示。

Text(item.title)Text(item.status)Button(item.actionText)

扩展信息只在 expanded 下显示。

if (this.isExpanded()) {  Text(item.summary)​  Row({ space: 8 }) {    this.MetaPill(item.source)    this.MetaPill(item.time)    this.MetaPill(item.tag)    this.MetaPill(item.priority)  }}

这个判断看起来很简单,但它解决的是页面信息层级问题。外屏下,用户看到的是标题、状态、操作;展开态下,用户看到的是完整上下文。两种形态使用同一组数据,但 UI 呈现的信息量不同。

真实项目里可以把字段进一步分层。

核心信息适合包含标题、状态、主操作、异常提示。

扩展信息适合包含摘要、来源、时间、标签、优先级、关联对象。

详情信息适合放到详情页或展开面板里,比如完整原文、识别结果、操作日志、附件列表。

折叠态页面最怕字段堆叠。能在列表里隐藏的内容,就不要全部放到卡片上。用户需要快速判断时,字段越多,决策成本越高。

验证要看什么

这次适合截两张图。

第一张保留窄窗口状态。顶部显示 折叠态保留核心信息,卡片里只有标题、状态和按钮。这个状态下要重点看卡片高度是否克制,一屏能不能看到多条记录,按钮是否容易点击。

第二张保留展开态状态。顶部显示 展开态显示完整上下文,卡片里出现摘要、来源、时间、标签和优先级。这个状态下要重点看字段是否分组清楚,摘要是否影响列表扫描,双列卡片是否仍然保持足够留白。

如果外屏截图里一张卡片已经占了大半屏,说明字段收得还不够。如果展开态截图里卡片显得空,说明扩展信息没有把大屏价值体现出来。

总结

Pura X Max 折叠态页面的信息密度控制,核心是给字段分层。

外屏下保留标题、状态和主操作,可以让用户快速扫列表,不被摘要、标签、时间这些次级信息干扰。展开态再补充摘要、来源、时间、标签和优先级,可以减少进入详情的次数,也能让内屏空间发挥作用。

这个方法适合记录列表、通知列表、整理结果列表、任务列表、设置项列表等场景。真正落到项目里,不建议把字段简单按数量裁掉,而要按用户判断路径来排优先级。用户第一眼必须知道这是什么、现在是什么状态、能做什么;其他上下文,交给更宽的窗口展示。


©本站发布的所有内容,包括但不限于文字、图片、音频、视频、图表、标志、标识、广告、商标、商号、域名、软件、程序等,除特别标明外,均来源于网络或用户投稿,版权归原作者或原出处所有。我们致力于保护原作者版权,若涉及版权问题,请及时联系我们进行处理。
分类
HarmonyOS
头像

小雨同学

产品总监、独立开发者社群主理人、资深全栈工程师,HarmonyOS应用开发者高级认证,PMP认证,CSDN博客专家,鸿蒙极客,Trae Fellow,阿里云社区专家博主、51CTO 博客专家、OpenTiny 优秀布道师、科大讯飞荣誉讲师。

1

帖子

0

提问

0

粉丝

关注
热门推荐
地址:北京市朝阳区北三环东路三元桥曙光西里甲1号第三置业A座1508室 商务内容合作QQ:2291221 电话:13391790444或(010)62178877
版权所有:电脑商情信息服务集团 北京赢邦策略咨询有限责任公司
声明:本媒体部分图片、文章来源于网络,版权归原作者所有,我司致力于保护作者版权,如有侵权,请与我司联系删除

京ICP备:2022009079号-2

京公网安备:11010502051901号

ICP证:京B2-20230255