HarmonyOS鸿蒙Next中模态框问题,使用bindSheet和bindContentCover的效果不一样。
HarmonyOS鸿蒙Next中模态框问题,使用bindSheet和bindContentCover的效果不一样。 【问题描述】:模态框问题,使用bindSheet和bindContentCover的效果不一样。用@builder装饰器都会发生变化,是不是说明使用全屏模态弹窗(bindContentCover)的时候不能使用外部导入的组件?
【问题现象】:bindSheet的模态框中的UI可以变化,bindContentCover的模态框中的UI未发生变化
相关代码:
import { ScrollEffectType, hdsMaterial, HdsTabsController, HdsNavigation} from "@kit.UIDesignKit";
export const mainTabController = new HdsTabsController();
@ComponentV2
struct ContentSheet {
@Local count: number = 0;
build() {
Column({space: 12}) {
Button("加一")
.onClick(() => {
this.count++;
})
Text(`计数 ${this.count}`)
.fontColor($r("sys.color.font_primary"))
}
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.size({width: "100%", height: "100%"})
.backgroundColor($r("sys.color.background_secondary"))
}
}
@Entry
@ComponentV2
struct Question3Page {
@Local showSheet: boolean = false;
build() {
HdsNavigation() {
Column() {
Button("打开全屏弹窗")
.onClick(() => {
this.showSheet = true;
})
}
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.size({width: "100%", height: "100%"})
}
.titleBar({
style: {
scrollEffectOpts: {
enableScrollEffect: false,
scrollEffectType: ScrollEffectType.GRADIENT_BLUR
},
systemMaterialEffect: {
materialType: hdsMaterial.MaterialType.ADAPTIVE,
materialLevel: hdsMaterial.MaterialLevel.ADAPTIVE
}
},
content: {
title: {
mainTitle: "模态框问题"
}
},
avoidLayoutSafeArea: true
})
.bindContentCover(
this.showSheet,
this.contentSheetBuilder(),
{
onWillDisappear: () => {
this.showSheet = false;
}
}
)
// .bindSheet(
// this.showSheet,
// this.contentSheetBuilder(),
// {
// onWillDisappear: () => {
// this.showSheet = false;
// }
// }
// )
}
@Builder
contentSheetBuilder() {
ContentSheet()
}
}
【版本信息】:不涉及
【复现代码】:不涉及
【尝试解决方案】:暂无
更多关于HarmonyOS鸿蒙Next中模态框问题,使用bindSheet和bindContentCover的效果不一样。的实战教程也可以访问 https://www.itying.com/category-93-b0.html
尊敬的开发者,您好,bindContentCover官网文档说明,builder里面的根节点需要唯一,统一根节点后可使builder中变量变化, 详细参考链接: bindContentCover 。
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| isShow | boolean | 是 | 是否显示全屏模态页面。 -true:显示全屏模态页面。 -false:隐藏全屏模态页面。 从API version 10开始,该参数支持$$双向绑定变量。 从API version 18开始,该参数支持!!双向绑定变量。 |
| builder | CustomBuilder | 是 | 配置全屏模态页面内容。builder里面的根节点需要唯一。 |
| type | ModalTransition | 否 | 全屏模态页面的系统转场方式。 默认值:ModalTransition.DEFAULT。 说明: 与transition同时设置时,此属性不生效。 |
更多关于HarmonyOS鸿蒙Next中模态框问题,使用bindSheet和bindContentCover的效果不一样。的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
老师,原代码是这个,按您的意思是根节点不唯一,我给他加一个column组件就能使用了,但是我还是不明白这个是为什么,加个column组件根节点就不唯一了吗?老师能解释一下吗?
@Builder
contentSheetBuilder() {
ContentSheet()
}
@Builder
contentSheetBuilder() {
Column(){
ContentSheet()
}
}
解决方案:
如果用bindContentCover就要用**@LocalBuilder:**
[@LocalBuilder](/user/LocalBuilder)
contentSheetBuilder() {
ContentSheet()
}
如果用bindSheet就用**@Builder:**
[@Builder](/user/Builder)
contentSheetBuilder() {
ContentSheet()
}
至于原因嘛,我也不知道,我只知道怎么解决问题。
这里的“builder 根节点需要唯一”主要是为了给全屏模态容器一个稳定的挂载和更新边界,不表示 bindContentCover 不能使用外部组件。bindContentCover 会把内容挂到系统级全屏模态容器中,和普通页面内的 bindSheet 相比,组件树位置、上下文绑定和状态刷新路径更敏感;如果 builder 返回多个兄弟根节点,或者根节点不稳定,状态变化时就更容易出现内容不刷新、上下文丢失或 diff 边界不清的问题。
可以按这个结构写:
@LocalBuilder contentBuilder() {
Column() {
ContentSheet()
}
}
build() {
Button('打开')
.onClick(() => { this.showCover = true })
.bindContentCover($$this.showCover, () => {
this.contentBuilder()
})
}
ContentSheet 这种外部组件可以继续用。它自己的计数状态就放在 ContentSheet 内部;如果需要父组件控制状态,再用 @Param / @Link 或 V2 对应的传参方式显式传进去。若你看的页面没有这句说明,可能是 API 版本或缓存文档不同,建议切到当前 API 版本的 bindContentCover 文档再确认。
尊敬的开发者,您好,请参考bindContentCover官网文档说明,builder参数里面的根节点需要唯一,统一根节点后可使builder中变量变化,全屏模态弹窗(bindContentCover)可以使用外部导入的组件,可参考如下代码:
Index.ets
import { ScrollEffectType, hdsMaterial, HdsTabsController, HdsNavigation } from '@kit.UIDesignKit';
import {contentSheetBuilder} from './BuilderPage'
export const mainTabController = new HdsTabsController();
@Entry
@ComponentV2
struct Question3Page {
@Local showSheet: boolean = false;
build() {
HdsNavigation() {
Column() {
Button('打开全屏弹窗')
.onClick(() => {
this.showSheet = true;
});
}
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.size({ width: '100%', height: '100%' });
}
.titleBar({
style: {
scrollEffectOpts: {
enableScrollEffect: false,
scrollEffectType: ScrollEffectType.GRADIENT_BLUR
},
systemMaterialEffect: {
materialType: hdsMaterial.MaterialType.ADAPTIVE,
materialLevel: hdsMaterial.MaterialLevel.ADAPTIVE
}
},
content: {
title: {
mainTitle: '模态框问题'
}
},
avoidLayoutSafeArea: true
})
.bindContentCover(
this.showSheet,
contentSheetBuilder(),
{
onWillDisappear: () => {
this.showSheet = false;
}
}
)
// .bindSheet(
// this.showSheet,
// contentSheetBuilder(),
// {
// onWillDisappear: () => {
// this.showSheet = false;
// }
// }
// )
}
}
BuilderPage.ets
@Builder
//全局 builder
export function contentSheetBuilder() {
Column() {
ContentSheet();
}
}
@ComponentV2
struct ContentSheet {
@Local count: number = 0;
build() {
Column({ space: 12 }) {
Button('加一')
.onClick(() => {
this.count++;
});
Text(`计数 ${this.count}`)
.fontColor($r('sys.color.font_primary'));
}
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.size({ width: '100%', height: '100%' })
.backgroundColor($r('sys.color.background_secondary'));
}
}
发的文档中,并没有看到builder参数里面的根节点需要唯一说明
只修改为
@LocalBuilder
contentSheetBuilder() {
ContentSheet()
}
就可以了。
@Builder(标准):它更像是一个纯函数,虽然定义在类里,但在某些场景下(特别是传给bindContentCover这种系统级全屏容器时),它与当前组件的this上下文绑定可能会变弱,或者生成的是一个独立的 UI 快照。@LocalBuilder(本地):它强绑定当前组件的上下文。它不仅能访问this,而且当组件的状态变化时,@LocalBuilder内部引用的状态(包括它所调用的子组件的状态)能正确地触发 UI 刷新。

import { ScrollEffectType, hdsMaterial, HdsTabsController, HdsNavigation} from "@kit.UIDesignKit";
export const mainTabController = new HdsTabsController();
@ComponentV2
struct ContentSheet {
@Local count: number = 0;
build() {
Column({space: 12}) {
Button("加一")
.onClick(() => {
this.count++;
})
Text(`计数 ${this.count}`)
.fontColor($r("sys.color.font_primary"))
}
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.size({width: "100%", height: "100%"})
.backgroundColor($r("sys.color.background_secondary"))
}
}
@Entry
@ComponentV2
struct Question3Page {
@Local showSheet: boolean = false;
build() {
HdsNavigation() {
Column() {
Button("打开全屏弹窗")
.onClick(() => {
this.showSheet = true;
})
}
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.size({width: "100%", height: "100%"})
}
.titleBar({
style: {
scrollEffectOpts: {
enableScrollEffect: false,
scrollEffectType: ScrollEffectType.GRADIENT_BLUR
},
systemMaterialEffect: {
materialType: hdsMaterial.MaterialType.ADAPTIVE,
materialLevel: hdsMaterial.MaterialLevel.ADAPTIVE
}
},
content: {
title: {
mainTitle: "模态框问题"
}
},
avoidLayoutSafeArea: true
})
.bindContentCover(
this.showSheet,
this.contentSheetBuilder(),
{
onWillDisappear: () => {
this.showSheet = false;
}
}
)
// .bindSheet(
// this.showSheet,
// this.contentSheetBuilder(),
// {
// onWillDisappear: () => {
// this.showSheet = false;
// }
// }
// )
}
@LocalBuilder
contentSheetBuilder() {
ContentSheet()
}
}
这个差异重点看 builder 的组件归属和状态归属。@Builder 在跨组件/回调场景下可能改变调用上下文;@LocalBuilder 会把构建函数固定在声明它的组件下,更适合 bindContentCover 这类需要保持当前组件状态关系的场景。
你的场景可以这样写:
[@LocalBuilder](/user/LocalBuilder)
contentSheetBuilder() {
ContentSheet()
}
build() {
Button('打开')
.onClick(() => {
this.showSheet = true
})
.bindContentCover($$this.showSheet, () => {
this.contentSheetBuilder()
})
}
如果弹窗内容内部有自己的状态,比如 ContentSheet 里的 @Local count,应让它作为弹窗内容组件自己的状态维护;如果需要父组件控制,则把父状态通过 @Param/@Require/@Link 或 V2 对应方式明确传进去。
@LocalBuilder 文档:
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-localbuilder
问题根源在于 bindContentCover 与 bindSheet 对 @Builder 参数的生命周期处理逻辑不同。你当前代码在 contentSheetBuilder 中直接 ContentSheet(),会创建全新组件实例。
bindSheet:@Builder接收后通常会缓存其内容,弹窗显示/隐藏时复用同一组件实例,因此@Local count的状态得以保留,UI 可以正常刷新。bindContentCover:每次打开全屏模态弹窗时,会重新执行@Builder函数并创建新的ContentSheet实例,导致count回到初始值 0,点击“加一”也仅在新实例上变化,看起来 UI 未刷新。
这与是否使用外部导入组件无关,可使用任何组件,但组件内部的状态必须在正确的生命周期内保持。若需在 bindContentCover 中让状态持续生效,应让状态拥有更长的生命周期(如提升到父组件并通过 @Param 或 @Provider 传入),或避免每次弹出都重新构建实例。


