HarmonyOS 鸿蒙Next中BindSheet中的sheet在屏幕旋转后不消失

HarmonyOS 鸿蒙Next中BindSheet中的sheet在屏幕旋转后不消失 tablet上在Portrait模式下通过bindSheet弹出一个表单,这时候旋转屏幕到landscape模式,在屏幕旋转时,已经将sheet的present标志设置成false,但是屏幕上会留有一个空白的表单。如图。

请问如何解决掉这个问题。

9 回复

这个问题本质上是:

bindSheet 在横竖屏切换过程中
生命周期和窗口重建不同步

尤其:

  • Pad
  • 折叠屏
  • 大屏
  • 多窗口

更容易出现。

你现在的现象:

present 已经 = false
但屏幕残留一个空白 sheet

其实是:

旧窗口的 sheet overlay 没被正确销毁

属于:

bindSheet 在 orientation change 时的典型问题

社区里已经有人反馈过:

  • bindSheet 在 Pad 表现异常
  • bindSheet 生命周期不一致
  • overlay 残留
  • sheet 不消失

———————— 你这里最重要的一点:

不要只改 boolean

很多人会这样:

this.showSheet = false

以为:

sheet 一定会销毁

但实际上:

旋转期间 window 正在重建

这时候:

sheet transition 可能卡在中间态

于是:

  • 蒙层没了
  • 状态是 false
  • 但 overlay node 还挂着

于是看到:

一个空白 sheet

————————

最有效的解决方案

方案1(最推荐)

旋转前先主动 dismiss

不要等 orientation 变化后再改状态。

而是:

监听窗口方向变化
先关闭 sheet
再旋转

例如:

aboutToDisappear() {
  this.showSheet = false
}

或者:

onPageHide() {
  this.showSheet = false
}

更关键的是:

orientation change 时主动关闭

例如:

display.on('change', () => {
  this.showSheet = false
})

很多情况下:

必须在窗口开始切换前关闭

而不是:

切换后关闭

————————

方案2(非常有效)

延迟关闭

因为:

orientation 切换是异步的

可以:

setTimeout(() => {
  this.showSheet = false
}, 0)

或者:

setTimeout(() => {
  this.showSheet = false
}, 100)

很多:

空白 overlay 残留

都是 transition timing 导致的。

————————

方案3(推荐)

不使用 bindSheet

大项目里:

很多团队已经:

不用 bindSheet 做复杂业务

尤其:

  • 大屏
  • 横竖屏
  • 多窗口
  • 平板

因为:

bindSheet 本质是系统 overlay

生命周期:

并不完全受组件树控制

更稳定的方式:

自己实现 BottomSheet

用:

Stack + Column + translateY

自己做。

这样:

  • orientation
  • 生命周期
  • 动画
  • 销毁

完全可控。

很多成熟项目:

最终都会自己写 Sheet

因为官方 bindSheet:

在复杂场景坑比较多

————————

方案4(临时修复)

强制重新创建页面节点

例如:

@State pageKey: number = 0

旋转后:

this.pageKey++

然后:

Column()
  .key(this.pageKey)

这样:

整个 subtree 会重建

可以把:

残留 overlay 一起清掉

虽然:

属于 workaround

但挺有效。

————————

方案5(容易被忽略)

不要在 bindSheet 内做 orientation 联动

比如:

.bindSheet(this.show, builder, {
  height: xxx
})

如果:

height / detents / mode
跟 orientation 联动

很容易:

切换期间 sheet 状态错乱

建议:

orientation change 时
先 dismiss
等稳定后再 reopen

而不是:

动态修改 sheet 属性

————————

官方层面的情况

目前:

bindSheet 在 Pad/横屏场景
确实还有不少边界问题

尤其:

  • overlay
  • detents
  • embedded
  • landscape
  • multi-window

社区已经有很多反馈。

————————

实战里推荐做法

如果你这个:

是核心业务表单

建议:

不要依赖 bindSheet

直接:

Stack + 自定义动画 BottomSheet

稳定性会高很多。

因为:

bindSheet 更适合轻量半模态

例如:

  • 分享面板
  • 选项菜单
  • 简单信息

而不是:

复杂表单 + orientation + 大屏

———————— 简单总结:

  • 这是 bindSheet 在横竖屏切换时的典型 overlay 残留问题
  • 不是你 present=false 写错
  • 本质是 window/orientation 生命周期和 sheet transition 不同步
  • 最有效方案:
    • orientation 前先 dismiss
    • 延迟关闭
    • 或直接自定义 BottomSheet
  • 大项目里很多团队已经不用 bindSheet 做复杂业务
  • Pad/横屏下 bindSheet 确实存在不少已知边界问题

更多关于HarmonyOS 鸿蒙Next中BindSheet中的sheet在屏幕旋转后不消失的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


嗯,这个bindSheet的实现确实比较差劲。用了一个临时的sheet,然后重建后就自动销毁了。

学习了

有要学HarmonyOS AI的同学吗,联系我:https://www.itying.com/goods-1206.html,

感觉你虽然把 present 设为 false,但旧的 sheet 节点或者蒙层没有真正从组件树上销毁,旋转后布局重绘了。可以考虑通过 windowSizeChange事件关闭 Sheet并更新 key强制重建,这能有效解决屏幕旋转后的显示问题。在 onWillDismiss回调中设置 this.isShow = false确保了状态与视图同步。使用 $$this.isShow进行状态绑定和 key(this.sheetKey)强制重建组件

import { window } from '@kit.ArkUI';

@Entry
@Component
struct SheetRotateFix {
  @State isShow: boolean = false;
  @State sheetKey: number = 0; // 强制重建 key

  aboutToAppear() {
    const win = window.getLastWindow(getContext(this));
    // 监听横竖屏切换
    win.on('windowSizeChange', () => {
      this.isShow = false;
      this.sheetKey += 1; // 销毁旧 sheet
    });
  }

  aboutToDisappear() {
    // 取消事件监听,避免内存泄漏
    const win = window.getLastWindow(getContext(this));
    win.off('windowSizeChange');
  }

  build() {
    Column() {
      Button('打开 Sheet')
        .onClick(() => {
          this.isShow = true;
        })
    }
    .width('100%')
    .height('100%')
    // 关键:$$ + key + onWillDismiss
    .bindSheet($$this.isShow, () => {
      Column() {
        Text('表单内容')
          .fontSize(20)
          .margin(20)
      }
      .width('100%')
      .height(300)
      .key(this.sheetKey) // 强制重建
    }, {
      sheetMode: SheetMode.BOTTOM,
      onWillDismiss: () => {
        this.isShow = false;
      }
    })
  }
}

你在屏幕旋转前强制关闭 sheet,不要依赖自动的 dismiss,我猜测这个原因是屏幕旋转时 sheet 的 dismiss 动画和布局重算有冲突,应该是遗留的问题吧,或者改成 CustomDialog 应该可以完全避免

也不知道怎么回事,图片上传不上来。就是转到横屏后,屏幕就出现一个白色的空白表单,带有一个关闭按钮。

BindSheet 的 sheet 在屏幕旋转后不消失,是由于旋转触发了页面重构,但 sheet 的状态绑定未能正确重置。解决方案:在页面 onPageShowonConfigurationUpdate 中手动调用控制 sheet 显示的状态变量置为 false,确保旋转后 sheet 关闭。

在 HarmonyOS Next 中,BindSheetisPresent 状态在屏幕旋转时可能因为页面重建导致关闭未彻底,可通过以下方式解决:

利用方向变化回调强制移除残留
@Entry 组件中监听 onConfigurationUpdate,当方向切换时,先延时重置 isPresent,确保在布局完成后再清理。

@Entry
@Component
struct MyPage {
  @State showSheet: boolean = false;
  @StorageLink('currentOrientation') orientation: number = 0;

  onConfigurationUpdate(config: Configuration) {
    if (config.orientation !== this.orientation) {
      this.orientation = config.orientation;
      if (this.showSheet) {
        // 延时关闭,避免重建时状态冲突
        setTimeout(() => {
          this.showSheet = false;
        }, 50);
      }
    }
  }

  build() {
    Column() {
      // ...页面内容
    }
    .bindSheet($$this.showSheet, this.sheetBuilder(), {
      height: SheetSize.MEDIUM,
      showClose: true,
    })
  }

  @Builder sheetBuilder() {
    // 表单内容
  }
}

原理
屏幕旋转会触发 aboutToDisappear/aboutToAppear,此时 BindSheet 的关闭动画可能因重建中断,导致空白蒙层残留。通过 onConfigurationUpdate 捕获方向变化,延迟一帧执行关闭,可保证组件状态与 UI 同步销毁。

回到顶部