鸿蒙 HarmonyOS 6 | Pura X Max 鸿蒙原生适配 14:大屏弹窗改成侧边面板 原创
头像 小雨同学 2026-05-28 09:26:00    发布
1 浏览 0 点赞 0 收藏


前言

我在做材料列表详情查看的时候,最早用的是居中弹窗。外屏下这个写法没什么违和感,用户点一条记录,页面弹出一个详情窗口,确认完再关掉,流程短,注意力也集中。手机空间本来就小,弹窗把当前任务单独拎出来,用户不会被列表里的其他内容干扰。

把同样的交互放到 Pura X Max 展开态里,我开始觉得不太对。列表区域明明还有很多空间,用户也能看到多条材料记录,但居中弹窗一出现,原来的列表和选中项都被遮住了。弹窗本身占据了屏幕中心,左右两边却空着不少区域。用户想对照原列表里的记录,或者继续切换另一条材料,就得先关掉弹窗,再回到列表里重新找。

这种情况在展开态里很常见,尤其是下面这些轻量任务:

  • 查看一条记录的补充说明
  • 编辑一两个字段
  • 临时筛选列表
  • 查看备注或处理建议
  • 确认一条识别结果
  • 给当前记录补充状态或标签

这些任务都有一个共同点,它们需要依附在当前页面上完成,不一定值得跳到完整详情页。小屏里可以用弹窗或底部面板承接;展开态里,右侧面板通常会更符合页面结构。左侧仍然保留列表或原内容,右侧承接当前详情和操作,用户不会丢掉自己是从哪条记录点进来的。

Pura X Max 在外屏、展开态、分屏和自由窗口之间切换时,页面可用宽度变化很大。弹层交互不能只按手机外屏的思路处理,窗口宽起来以后,页面有条件保留上下文,弹层也可以从遮住页面变成贴着页面补充信息。

这次我用一个材料列表页来模拟这个场景。点击“查看详情”后,窄窗口使用底部面板,展开态使用右侧面板。状态和数据仍然是一套,只是面板出现的位置跟着窗口宽度变化。这个处理方式比较适合详情补充、筛选条件、备注编辑这类轻量任务。

一、弹窗在展开态里会遮住上下文

1.1 外屏里弹窗可以集中注意力

外屏里用弹窗处理详情补充,很多时候是可以接受的。比如用户在一个窄屏列表里点开某条材料,弹出一个居中的详情卡片,或者从底部拉起一个面板,用户的注意力会集中到这条记录上。页面空间有限,原列表本来就无法和详情同时展开,弹窗相当于给当前任务临时开出一块区域。

我在手机外屏里也会经常这样写。比如编辑一个标题、确认一个提醒、查看一段识别结果,弹窗或底部面板能把任务收得比较干净。用户看完以后关掉弹层,回到原页面继续操作。这个模式在小屏里并不违和,反而能减少页面跳转。

在代码里,这类写法通常很简单。点一条记录,把 showPanel 设为 true,再把当前记录 id 存下来。

private openPanel(itemId: number) {  this.selectedId = itemId;  this.showPanel = true;}

这个状态本身可以保留。真正要调整的是弹层在不同窗口宽度下的呈现方式。外屏可以从底部出来,展开态就没必要继续遮住页面中心。

1.2 展开态里弹窗会抢掉参照物

我把这个页面切到展开态后,再点查看详情,第一个感受就是弹窗挡住了列表。原来的材料列表还在背后,但用户已经看不到自己点的是哪条记录,也看不到上下几条记录之间的关系。对于只查看一条详情来说,这还勉强能用;如果用户需要连续切换记录,居中弹窗就开始影响操作。

展开态的价值之一,是能把原页面和补充内容同时放下。比如左侧继续保留列表,右侧显示详情补充。用户在查看详情时,还能看到列表里其他材料,也能确认当前记录的上下文。这个时候居中弹窗反而把展开态空间浪费掉了。

我会把这类弹层分成两种用途来区分。需要强打断、强确认的内容,比如删除确认、支付确认、危险操作提醒,仍然适合弹窗;只是查看详情、筛选条件、备注编辑、轻量补充信息,就更适合放到侧边面板里。它们不需要遮住整个页面,也不需要让用户离开当前上下文。

二、小屏继续用底部面板

2.1 窄窗口更适合聚焦处理

在外屏或较窄窗口里,我仍然会保留底部面板。原因很简单,窄窗口里很难同时放下列表和详情,用户点开一条记录时,页面优先让他处理当前内容。底部面板从屏幕下方出现,覆盖原页面的一部分,用户会自然把注意力放到当前记录上。

这类交互适合短任务。比如查看一条提醒、确认一段识别结果、保存一个处理建议。用户不需要保留大量上下文,只要知道当前处理对象是什么,以及下一步能点哪个按钮。

示例里,小屏下的底部面板只在 showPanel 为 true 且窗口没有达到展开态时出现。

if (this.showPanel && !this.isExpanded()) {  this.BottomSheet()}

这个判断看起来很简单,但它决定了小屏里的交互节奏。窄窗口不强行分栏,也不把详情塞在右侧,而是用底部面板集中当前任务。等窗口宽起来以后,同样的详情内容再换到右侧面板。

2.2 底部面板要控制高度

底部面板不能无限长。小屏里高度本来就有限,如果面板展开后把整个屏幕都占满,用户会感觉像跳进了另一个页面,但返回关系又没有完整页面那么清楚。

示例里,底部面板用了固定高度。

.height(430)

这个值不是固定标准。真实项目里要根据内容决定,外屏页面里可以略高一点,悬浮窗里要更克制。一般来说,底部面板适合放标题、摘要、少量元信息和一到两个按钮。如果内容继续增长,就要考虑进入完整详情页,而不是让底部面板一直加高。

我在项目里通常会把底部面板看作临时处理区,不会把完整详情全部塞进去。它可以承接当前动作,但不适合承担一个复杂流程。这样后续迁移到展开态侧边面板时,也能保持同一套信息层级。

三、展开态改成右侧面板

3.1 右侧面板保留原页面参照

展开态下,我更愿意把详情补充内容放到右侧。左侧仍然是列表,右侧面板显示当前记录的详情、建议和操作按钮。这样用户打开详情时,不会失去原页面参照,也能继续知道自己是从哪条记录点进来的。

示例里的判断从窗口宽度开始。

private readonly expandedWidth: number = 820;​private isExpanded(): boolean {  return this.getEffectiveWidth() >= this.expandedWidth;}

820vp 是这个示例里的门槛。真实项目里要看页面主体宽度、侧边面板宽度、左右 padding 和列表卡片宽度。比如右侧面板需要 360vp,左侧列表至少要保留 420vp,再加上间距和页面边距,阈值就不能设得太低。

大屏下显示侧边面板的判断也很简单。

if (this.showPanel && this.isExpanded()) {  this.SidePanel()}

我在真实项目里会把这个判断放在页面层,而不是让某个按钮组件自己决定弹出方式。按钮只负责打开详情,至于详情出现在底部还是右侧,交给页面根据窗口宽度处理。

3.2 面板宽度要给主页面留空间

示例里的右侧面板宽度是 360vp。

.width(360)

这个宽度适合展示标题、摘要、元信息、补充说明和两个按钮。它不会太窄,正文还可以阅读;也不会太宽,左侧列表仍然能保留足够空间。如果面板内容更少,可以降到 320vp;如果需要展示表单字段,可以增加到 400vp 左右。

这里我会特别关注左侧列表。侧边面板出现以后,左侧仍然应该能看清列表标题、选中态和至少几条记录。如果右侧面板过宽,左侧列表被挤得只剩窄条,那就和居中弹窗一样失去了保留上下文的意义。

四、用 Stack 还原两种形态

4.1 页面层控制遮罩和面板

这里我没有直接调用系统弹窗 API,而是在页面里用 Stack 叠出遮罩、底部面板和右侧面板。这样做的好处是方便验证布局逻辑,也方便在同一个页面里根据窗口宽度切换不同面板形态。

核心结构放在 build() 里。

if (this.showPanel) {  Column()    .width('100%')    .height('100%')    .backgroundColor(this.isExpanded() ? '#00000000' : '#66000000')​  if (this.isExpanded()) {    this.SidePanel()  } else {    this.BottomSheet()  }}

小屏下,遮罩是半透明黑色,点击遮罩可以关闭面板。这个交互更接近普通弹窗,用户知道当前任务是临时打开的。展开态下,遮罩保持透明,面板出现在右侧,左侧列表仍然可见。关闭动作放在面板内部,避免用户误触左侧列表时把面板关掉。

这两个细节很容易被忽略。很多时候我们只处理面板位置,却忘了遮罩也要跟着变化。小屏需要遮罩帮助用户聚焦,大屏更需要保留页面上下文。遮罩颜色、关闭方式、面板位置,其实都应该和窗口状态一起调整。

4.2 详情内容复用同一份

这里底部面板和右侧面板都使用同一个 DetailContent()。也就是说,详情内容没有拆成两份,只是外层容器不同。

@Builderprivate DetailContent(item: MaterialItem) {  Column({ space: 16 }) {    // 标题、摘要、补充说明、处理建议和按钮  }}

这样写的好处是,后面改详情字段时,不需要同时改底部面板和右侧面板两套内容。小屏和大屏的差异主要体现在外层容器:小屏是从底部出现,大屏是贴右侧出现。详情内部的字段结构可以保持一致,再根据宽度做少量字号或行高调整。

真实项目里也建议这样处理。弹层形态可以有两种,内容尽量保持一份。否则后面加字段、改文案、调整按钮状态时,很容易出现小屏和大屏不一致。

五、实际运行效果

这里顶部有外屏和展开态两个演示按钮,方便在同一台模拟器里观察面板形态。真实项目里可以删掉这些按钮,页面直接根据真实窗口宽度判断。

外屏状态下,点击任意一条记录的查看详情,详情会从底部弹出,背景有一层半透明遮罩。这个状态适合窄窗口,用户的注意力集中在当前记录上,底部面板也更贴近小屏操作习惯。

展开态状态下,再点击查看详情,详情会出现在右侧,左侧列表不会被遮住。选中的列表项会保留高亮,用户能看到自己打开的是哪条记录,也可以继续对照列表里的其他材料。

六、如何迁移到实际项目

6.1 演示宽度要删掉

示例里的 previewWidth 只是为了在同一个模拟器里切换外屏和展开态。真实项目里不需要这些按钮,页面应该直接使用真实窗口宽度。

private getEffectiveWidth(): number {  if (this.previewWidth > 0) {    return this.previewWidth;  }​  return this.pageWidth;}

迁回项目时,可以直接返回 pageWidth

private getEffectiveWidth(): number {  return this.pageWidth;}

页面宽度可以继续通过 onAreaChange 写入。这里记录的是页面根容器宽度,而不是设备名称。对 Pura X Max 来说,同一台设备可能处在外屏、展开态、分屏和自由窗口里,面板形态要看当前窗口给了多少空间。

6.2 不是所有弹窗都适合改成侧边面板

侧边面板适合轻量补充任务,比如查看详情、筛选条件、备注编辑、状态确认。它的价值在于保留原页面上下文,让用户不离开列表,也能完成一小段操作。

如果是删除确认、支付确认、权限授权这类需要强提醒的动作,我仍然会用弹窗。它们本来就需要打断用户,让用户明确确认当前操作。侧边面板太轻,反而不适合这类高风险动作。

如果是多步骤表单、长文编辑、图片裁剪、复杂审批流程,我也不会放在侧边面板里。右侧面板宽度有限,复杂流程放进去会让用户一直滚动,还容易丢失表单上下文。这类任务应该进入完整页面,或者使用更大的编辑页面承接。

6.3 面板状态要和选中项保持一致

示例里用了 selectedId 保存当前选中记录,用 showPanel 控制面板是否显示。点击不同记录时,面板内容会跟着更新。

private openPanel(itemId: number) {  this.selectedId = itemId;  this.showPanel = true;}

真实项目里也要留意这个状态关系。用户在展开态里点击列表 A,右侧面板显示 A 的详情;继续点击列表 B,右侧面板应该切到 B,而不是重新弹出一个新的弹窗。这样列表和面板之间的关系才是连续的。

我会把这类状态放在页面层,而不是放在单个列表卡片里。列表卡片负责触发打开动作,页面负责保存选中项和面板状态。这样底部面板和右侧面板都能复用同一份状态,不会因为窗口宽度变化导致当前详情丢失。

总结

Pura X Max 展开态里,弹窗继续放在屏幕中间,很多时候会把原页面关系打断。外屏空间小,底部面板可以让用户先处理当前记录;展开态空间变宽以后,详情补充、筛选条件、备注编辑这类内容放到右侧,左侧列表还能留在原位,用户知道自己刚才点的是哪条记录,也能继续对照上下几条材料。

我后面处理这类弹层时,会先看它承担的任务:

  • 如果只是查看详情、补充说明、备注编辑、筛选条件,右侧面板更适合展开态。
  • 如果是删除、支付、授权这类强确认动作,居中弹窗仍然更合适,因为它需要让用户停下来确认。
  • 如果是多步骤表单、长文编辑、图片裁剪这类复杂流程,应该进入完整页面,不适合塞进侧边面板。
  • 如果只是外屏上的短任务,比如看一条记录、点一下保存、稍后处理,底部面板已经够用。

弹层的位置要看当前窗口能不能保留原页面参照。窗口窄的时候,先让用户集中处理当前记录;窗口宽的时候,就不要急着遮住列表,把补充内容放到右侧,让原页面和详情内容同时留在视野里。这样处理以后,弹层不再只是一个固定样式,而是会根据任务轻重和窗口宽度换一种呈现方式。

附:完整代码

interface MaterialItem {  id: number;  title: string;  status: string;  source: string;  time: string;  tag: string;  summary: string;  detail: string;  suggestion: string;}​@Entry@Componentstruct Index {  // 页面真实宽度,由 onAreaChange 写入  @State private pageWidth: number = 0;​  // 演示宽度,只用于在同一个模拟器里观察外屏和展开态  @State private previewWidth: number = 0;​  // 当前选中项。底部面板和右侧面板都读取这个状态  @State private selectedId: number = 1;​  // 面板是否打开。窗口宽度变化时,面板位置会跟着切换  @State private showPanel: boolean = false;​  // 模拟保存次数,用来观察面板切换后操作状态是否保留  @State private saveCount: number = 0;​  private readonly expandedWidth: number = 820;​  private readonly materials: MaterialItem[] = [    {      id: 1,      title: '社区物业缴费提醒',      status: '待处理',      source: '拍照整理',      time: '09:20',      tag: '通知',      summary: '识别到缴费截止日期、金额明细和办理地点。',      detail: '这条记录来自一张社区物业缴费通知。内容包含缴费周期、应缴金额、截止日期和办理地点。小屏下适合通过底部面板快速查看,大屏下可以用右侧面板保留列表上下文。',      suggestion: '保存为待办提醒,并在截止日期前一天提醒。'    },    {      id: 2,      title: 'Pura X Max 适配会议纪要',      status: '待确认',      source: '语音转写',      time: '10:45',      tag: '会议',      summary: '整理出弹窗、侧边面板、分屏窗口和横屏结构几类问题。',      detail: '会议纪要类记录经常需要对照多个条目。展开态下右侧详情面板能减少页面跳转,列表仍然保留在原位置,切换记录也更方便。',      suggestion: '确认适配任务,并同步到开发清单。'    },    {      id: 3,      title: '客户需求变更记录',      status: '待处理',      source: '文本整理',      time: '13:10',      tag: '项目',      summary: '本次变更涉及首页布局、权限配置和消息提醒。',      detail: '需求变更类记录适合在右侧面板里查看补充信息。主页面保留列表,右侧承接详情、处理建议和操作按钮。',      suggestion: '同步项目负责人,并拆分到研发排期。'    },    {      id: 4,      title: '活动报名确认单',      status: '已保存',      source: '相册导入',      time: '15:25',      tag: '表单',      summary: '提取到报名人、联系方式、活动时间和签到地址。',      detail: '报名确认类材料通常只是补充查看,不一定需要进入完整详情页。小屏弹出底部面板,宽屏使用侧边面板即可。',      suggestion: '保存记录,并在活动前一天提醒。'    },    {      id: 5,      title: '门诊复查预约提示',      status: '已整理',      source: '拍照整理',      time: '16:40',      tag: '提醒',      summary: '提取到复查时间、科室、楼层和注意事项。',      detail: '提醒类信息适合轻量处理。侧边面板可以承接确认、保存、稍后处理等动作,避免把用户带到另一个页面。',      suggestion: '加入日程提醒,并保留原始记录。'    }  ];​  // Demo 中优先使用演示宽度,真实项目里可以直接返回 pageWidth  private getEffectiveWidth(): number {    if (this.previewWidth > 0) {      return this.previewWidth;    }​    return this.pageWidth;  }​  private isExpanded(): boolean {    return this.getEffectiveWidth() >= this.expandedWidth;  }​  private getContentWidth(): Length {    if (this.previewWidth > 0) {      return this.previewWidth;    }​    return '100%';  }​  private getPagePadding(): number {    return this.isExpanded() ? 24 : 16;  }​  private getTitleSize(): number {    return this.isExpanded() ? 28 : 23;  }​  private getModeText(): string {    return this.isExpanded() ? 'expanded · 右侧面板' : 'compact · 底部面板';  }​  private getModeDesc(): string {    if (this.isExpanded()) {      return '宽窗口下详情进入右侧面板,左侧列表仍然保留。';    }​    return '窄窗口下详情从底部弹出,当前任务先聚焦处理。';  }​  private getSelectedItem(): MaterialItem {    const found = this.materials.find((item: MaterialItem) => item.id === this.selectedId);    return found ? found : this.materials[0];  }​  private setPreview(width: number) {    this.previewWidth = width;    this.showPanel = false;  }​  private openPanel(itemId: number) {    this.selectedId = itemId;    this.showPanel = true;  }​  private closePanel() {    this.showPanel = false;  }​  private save() {    this.saveCount += 1;  }​  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 PreviewButton(text: string, width: number) {    Text(text)      .fontSize(12)      .fontColor(this.previewWidth === width ? '#FFFFFF' : '#2F8F83')      .textAlign(TextAlign.Center)      .padding({ left: 10, right: 10, top: 7, bottom: 7 })      .backgroundColor(this.previewWidth === width ? '#2F8F83' : '#E6F4F1')      .borderRadius(999)      .onClick(() => {        this.setPreview(width);      })  }​  @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 MetaPill(text: string) {    Text(text)      .fontSize(12)      .fontColor('#4B5563')      .padding({ left: 8, right: 8, top: 4, bottom: 4 })      .backgroundColor('#F3F4F6')      .borderRadius(999)  }​  @Builder  private HeaderPanel() {    Column({ space: 10 }) {      Row() {        Column({ space: 4 }) {          Text('大屏弹窗改成侧边面板')            .fontSize(this.getTitleSize())            .fontWeight(FontWeight.Bold)            .fontColor('#111827')​          Text(this.getModeText())            .fontSize(14)            .fontColor('#2F8F83')        }        .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%')​      Text('演示宽度:' + Math.round(this.getEffectiveWidth()).toString() + 'vp。' + this.getModeDesc())        .fontSize(14)        .fontColor('#6B7280')        .lineHeight(21)​      Row({ space: 8 }) {        this.PreviewButton('自动', 0)        this.PreviewButton('外屏', 430)        this.PreviewButton('展开态', 960)      }      .width('100%')    }    .width('100%')  }​  @Builder  private MaterialCard(item: MaterialItem) {    Column({ space: 12 }) {      Row({ space: 8 }) {        this.StatusPill(item.status)        this.MetaPill(item.tag)​        Blank()​        Text(item.time)          .fontSize(12)          .fontColor('#6B7280')      }      .width('100%')​      Text(item.title)        .fontSize(17)        .fontWeight(FontWeight.Medium)        .fontColor('#111827')        .maxLines(2)        .textOverflow({ overflow: TextOverflow.Ellipsis })​      Text(item.summary)        .fontSize(13)        .fontColor('#6B7280')        .lineHeight(19)        .maxLines(this.isExpanded() ? 2 : 1)        .textOverflow({ overflow: TextOverflow.Ellipsis })​      Row({ space: 10 }) {        Text(item.source)          .fontSize(12)          .fontColor('#4B5563')​        Blank()​        Button('查看详情')          .fontSize(13)          .fontColor('#FFFFFF')          .height(34)          .padding({ left: 12, right: 12 })          .backgroundColor('#2F8F83')          .borderRadius(17)          .onClick(() => {            this.openPanel(item.id);          })      }      .width('100%')    }    .width('100%')    .padding(16)    .backgroundColor(this.selectedId === item.id && this.showPanel ? '#EEF7F5' : '#FFFFFF')    .borderRadius(20)    .border({      width: this.selectedId === item.id && this.showPanel ? 1.5 : 1,      color: this.selectedId === item.id && this.showPanel ? '#2F8F83' : '#E5E7EB'    })    .shadow({      radius: this.selectedId === item.id && this.showPanel ? 12 : 8,      color: '#12000000',      offsetX: 0,      offsetY: 4    })  }​  @Builder  private ListArea() {    Scroll() {      Column({ space: 12 }) {        ForEach(this.materials, (item: MaterialItem) => {          this.MaterialCard(item)        }, (item: MaterialItem) => item.id.toString())      }      .width('100%')      .padding({ bottom: 24 })    }    .layoutWeight(1)    .width('100%')    .edgeEffect(EdgeEffect.Spring)  }​  @Builder  private DetailContent(item: MaterialItem) {    Column({ space: 16 }) {      Row() {        this.StatusPill(item.status)​        Blank()​        Text('关闭')          .fontSize(13)          .fontColor('#6B7280')          .padding({ left: 10, right: 10, top: 6, bottom: 6 })          .backgroundColor('#F3F4F6')          .borderRadius(999)          .onClick(() => {            this.closePanel();          })      }      .width('100%')​      Column({ space: 8 }) {        Text(item.title)          .fontSize(this.isExpanded() ? 24 : 21)          .fontWeight(FontWeight.Bold)          .fontColor('#111827')          .lineHeight(this.isExpanded() ? 31 : 28)​        Text(item.summary)          .fontSize(14)          .fontColor('#4B5563')          .lineHeight(22)      }      .width('100%')      .alignItems(HorizontalAlign.Start)​      Row({ space: 8 }) {        this.MetaPill(item.source)        this.MetaPill(item.time)        this.MetaPill(item.tag)      }      .width('100%')​      Column({ space: 8 }) {        Text('详情补充')          .fontSize(16)          .fontWeight(FontWeight.Medium)          .fontColor('#111827')​        Text(item.detail)          .fontSize(14)          .fontColor('#4B5563')          .lineHeight(23)      }      .width('100%')      .padding(14)      .backgroundColor('#F9FAFB')      .borderRadius(16)​      Column({ space: 8 }) {        Text('处理建议')          .fontSize(16)          .fontWeight(FontWeight.Medium)          .fontColor('#111827')​        Text(item.suggestion)          .fontSize(14)          .fontColor('#4B5563')          .lineHeight(23)      }      .width('100%')      .padding(14)      .backgroundColor('#F3F8F7')      .borderRadius(16)​      Text('已保存 ' + this.saveCount.toString() + ' 次')        .fontSize(13)        .fontColor('#6B7280')​      Button('保存处理结果')        .fontSize(15)        .fontColor('#FFFFFF')        .height(44)        .width('100%')        .backgroundColor('#2F8F83')        .borderRadius(22)        .onClick(() => {          this.save();        })​      Button('稍后处理')        .fontSize(15)        .fontColor('#2F8F83')        .height(42)        .width('100%')        .backgroundColor('#E6F4F1')        .borderRadius(21)​      if (this.isExpanded()) {        Text('展开态下,右侧面板不会遮住左侧列表,用户可以继续保留原页面参照。')          .fontSize(13)          .fontColor('#6B7280')          .lineHeight(20)      }    }    .width('100%')    .height('100%')  }​  @Builder  private SidePanel() {    Row() {      Blank()​      Column() {        this.DetailContent(this.getSelectedItem())      }      .width(360)      .height('100%')      .padding(20)      .backgroundColor('#FFFFFF')      .borderRadius({        topLeft: 24,        topRight: 0,        bottomLeft: 24,        bottomRight: 0      })      .shadow({        radius: 16,        color: '#18000000',        offsetX: -4,        offsetY: 0      })    }    .width('100%')    .height('100%')  }​  @Builder  private BottomSheet() {    Column() {      Blank()​      Column() {        this.DetailContent(this.getSelectedItem())      }      .width('100%')      .height(430)      .padding(18)      .backgroundColor('#FFFFFF')      .borderRadius({        topLeft: 24,        topRight: 24,        bottomLeft: 0,        bottomRight: 0      })      .shadow({        radius: 16,        color: '#18000000',        offsetX: 0,        offsetY: -4      })    }    .width('100%')    .height('100%')  }​  build() {    Stack() {      Column() {        Column({ space: 16 }) {          this.HeaderPanel()          this.ListArea()        }        .width(this.getContentWidth())        .height('100%')        .padding({          left: this.getPagePadding(),          right: this.getPagePadding(),          top: 18,          bottom: 16        })      }      .width('100%')      .height('100%')      .alignItems(HorizontalAlign.Center)​      if (this.showPanel) {        Column()          .width('100%')          .height('100%')          .backgroundColor(this.isExpanded() ? '#00000000' : '#66000000')          .onClick(() => {            if (!this.isExpanded()) {              this.closePanel();            }          })​        if (this.isExpanded()) {          this.SidePanel()        } else {          this.BottomSheet()        }      }    }    .width('100%')    .height('100%')    .backgroundColor('#F6F7F9')    .onAreaChange((_: Area, newValue: Area) => {      const width = Number(newValue.width);      if (!Number.isNaN(width) && width > 0) {        this.pageWidth = width;      }    })  }}


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

暂无评论数据

加载中...

发布

头像

小雨同学

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

14

帖子

0

提问

25

粉丝

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

京ICP备:2022009079号-2

京公网安备:11010502051901号

ICP证:京B2-20230255