鸿蒙 HarmonyOS 6 | Pura X Max 鸿蒙原生适配 14:大屏弹窗改成侧边面板
把同样的交互放到 Pura X Max 展开态里,我开始觉得不太对。列表区域明明还有很多空间,用户也能看到多条材料记录,但居中弹窗一出现,原来的列表和选中项都被遮住了。弹窗本身占据了屏幕中心,左右两边却空着不少区域。用户想对照原列表里的记录,或者继续切换另一条材料,就得先关掉弹窗,再回到列表里重新找。
前言
我在做材料列表详情查看的时候,最早用的是居中弹窗。外屏下这个写法没什么违和感,用户点一条记录,页面弹出一个详情窗口,确认完再关掉,流程短,注意力也集中。手机空间本来就小,弹窗把当前任务单独拎出来,用户不会被列表里的其他内容干扰。
把同样的交互放到 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()。也就是说,详情内容没有拆成两份,只是外层容器不同。
@Builder
private 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
@Component
struct 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;
}
})
}
}
更多推荐


所有评论(0)