HarmonyOS 鸿蒙Next应用级弹窗布局问题:弹窗拦截返回后页面无法返回
HarmonyOS 鸿蒙Next应用级弹窗布局问题:弹窗拦截返回后页面无法返回
需求是有个应用级的类似弹窗的布局,切换页面时,弹窗一直显示,弹窗按物理键返回不消失,页面正常返回; 目前用promptAction.openCustomDialog能满足部分需求,但是物理键返回时 action.reason == DismissReason.PRESS_BACK,拦截了返回,导致页面也无法返回了,要怎么处理?
const option: promptAction.CustomDialogOptions = {
builder: buildDialog.bind(DialogUtils.getPageThis(), controller),
alignment: DialogAlignment.Bottom,
backgroundColor: Color.Transparent,
backgroundBlurStyle: BlurStyle.NONE,
cornerRadius: 8,
width: Math.min(ScreenUtils.getScreenVpWidth(), 500),
autoCancel: false,
onWillDismiss: (action: DismissDialogAction) => {
if (action.reason == DismissReason.PRESS_BACK) {
// 处理按下返回键的情况
}
},
offset: {
dx: 0,
dy: -ScreenUtils.getBottomNavHeight() + 20 - 60
},
showInSubWindow: true,
isModal: false,
onDidDisappear: () => {
MessageToast.get().dialogDismiss();
}
};
promptAction.openCustomDialog(option)
.then((id) => {
controller?.setDialogId(id);
});
更多关于HarmonyOS 鸿蒙Next应用级弹窗布局问题:弹窗拦截返回后页面无法返回的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
由于您提供的Demo代码不全,这边无法复现您的问题,给您找了相关参考介绍,看能不能解决您的问题 api12中新增了onWillDismiss方法:
- 当用户执行点击遮障层关闭、左滑/右滑、三键back、键盘ESC关闭交互操作时,如果注册该回调函数,则不会立刻关闭弹窗。在回调函数中可以通过reason得到阻拦关闭弹窗的操作类型,从而根据原因选择是否能关闭弹窗。当前组件返回的reason中,暂不支持CLOSE_BUTTON的枚举值。
- 在onWillDismiss回调中,不能再做onWillDismiss拦截。 可参考以下文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/ts-methods-custom-dialog-box-V5#ZH-CN_TOPIC_0000001847211028__customdialogcontrolleroptions对象说明 /******/ 可以使用onBackPress当用户点击返回按钮时触发,仅@Entry装饰的自定义组件生效。返回true表示页面自己处理返回逻辑,不进行页面路由;返回false表示使用默认的路由返回逻辑,不设置返回值按照false处理。 参考文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/ts-custom-component-lifecycle-V5#ZH-CN_TOPIC_0000001930676681__onbackpress
我们的技术老师给您提供Demo 参考如下
import { ComponentContent, router } from ‘@kit.ArkUI’
@Entry @Component struct Index { @State message: string = “hello”
aboutToAppear(): void { AppStorage.setOrCreate(‘app_ui_context’, this.getUIContext()) }
build() { Row() { Column() { Button(“下一页”) .onClick(() => { router.pushUrl({url:“pages/SecondPage”}) })
Button("显示弹窗")
.onClick(() => {
DialogUtils.show()
})
.margin(10)
}
.width('100%')
.height('100%')
.padding(50)
}
.height('100%')
} }
export class DialogUtils { public static show() { let uiContext = AppStorage.get<UIContext>(‘app_ui_context’)!; let promptAction = uiContext.getPromptAction();
let contentNode = new ComponentContent(uiContext, wrapBuilder(buildDialog));
promptAction.openCustomDialog(contentNode, {
alignment: DialogAlignment.Bottom,
autoCancel: false,
onWillDismiss: (action: DismissDialogAction) => {
if (action.reason == DismissReason.PRESS_BACK) {
router.back()
}
if (action.reason == DismissReason.TOUCH_OUTSIDE) {
action.dismiss()
},
},
offset: { dy: -50, dx: 0 },
showInSubWindow: true,
});
} }
@Builder function buildDialog() { Text(‘我是一个一直显示的控件’) .padding(10) .fontSize(30) .backgroundColor(Color.Red) }
//SecondPage.ets @Entry @Component struct SecondPage { build() { Column(){ Text(‘我是第二个页面’).fontSize(40) } } }
我们的专家给您提供一些业务功能场景供您参考,
1、基于Dialog类型NavDestination,实现弹窗页面跳转返回后弹窗不关闭
NavDestination有两个类型,通过mode属性进行配置,前文介绍的NavDestination均是STANDARD类型。
名称 描述 STANDARD 标准类型NavDestination的生命周期跟随NavPathStack栈中标准Destination变化而改变。 DIALOG 默认透明。不影响其他NavDestination的生命周期。
DIALOG类型的NavDestination背景透明,且不会影响其他NavDestination生命周期,也就是说前面的页面不会隐藏,因此比较适合开发类似“高德地图”的应用,此类应用特点是:底层一个固定的页面,其余页面都是覆盖在底层页面之上,但是底层页面始终可见。mode为DIALOG的NavDestination在转入和转出时,默认不支持动画,可以通过自定义动画的方式配置动画。
在全局弹窗需求交付之前,可以通过Dialog来实现弹窗,Dialog实现的弹窗可以实现解耦,还可以实现弹窗跳转页面返回,弹窗不关闭等效果(比如隐私弹窗,该弹窗还可以接续跳转到隐私条款页)。
构建Dialog类型的NavDestination,因为Dialog类型页面背景是透明的,为了更好的效果,可以增加一层蒙层。如果要对弹窗组件增加类似移动出现效果,需要在组件中自行实现。
@Component export struct PrivacyDialog { @Consume(‘pageInfo’) pageStack : NavPathStack; @State isAgree: string = “Not Agree”; build() { NavDestination(){ Stack({ alignContent: Alignment.Center }){ // 蒙层 Column() {} .width(“100%”) .height(“100%”) .backgroundColor(‘rgba(0,0,0,0.5)’) // 隐私弹窗 Column() { Text(“注册应用账号”).fontSize(30).height(‘20%’) Text(“请您仔细阅读一下协议并同意,我们将全力保护您的个人信息安全,您可以使用账号登录APP。”).height(‘40%’) Divider() Row(){ // 点击隐私条款,跳转到隐私条款页面,并接受隐私条款的返回值,用来刷新页面的同意状态。 Text("《应用隐私政策》").onClick(ent => { let pathInfo : NavPathInfo = new NavPathInfo(‘PrivacyItem’, null , (popInfo: PopInfo) => { this.isAgree = popInfo.result.toString(); }) this.pageStack.pushDestination(pathInfo, true) }) Text(this.isAgree) }.height(‘20%’) Divider() // 点击同意\不同意按钮,将状态返回登录页 Row(){ Button(“不同意”).onClick(ent => { this.pageStack.pop(“Not Agree”, true) }).width(‘30%’) Button(“同意”).onClick(ent => { this.pageStack.pop(“Agree”, true) }).width(‘30%’) }.height(‘20%’) }.backgroundColor(Color.White) .height(‘50%’) .width(‘80%’) } .hideTitleBar(true) // 设置Dialog类型 .mode(NavDestinationMode.DIALOG) } } }
2、基于页面生命周期监听,实现页面埋点
在应用运维时,需要了解哪些业务功能用户使用频率较高,为了实现此能力,可以在页面显示\隐藏的的时候进行相应的埋点操作,以方便后台进行统计分析。此类诉求可以通过DevNavigation的状态监听实现。
可以在Ability中的onWindowStageCreate方法中通过uiObserver.on(“navDestinationUpdate”,<info>)方法注册DevNavigation的状态监听,样例代码通过打样日志的方式记录各个DevNavigation显示\隐藏时的状态,在真实业务中可以相应进行替换。
export default class EntryAbility extends UIAbility {
…
onWindowStageCreate(windowStage: window.WindowStage): void {
…
windowStage.getMainWindow((err: BusinessError, data) => {
…
windowClass = data;
// 获取UIContext实例。
let uiContext: UIContext = windowClass.getUIContext();
// 获取UIObserver实例。
let uiObserver : UIObserver = uiContext.getUIObserver();
// 注册DevNavigation的状态监听.
uiObserver.on(“navDestinationUpdate”,<info> => {
// NavDestinationState.ON_SHOWN = 0, NavDestinationState.ON_HIDE = 1
if (info.state == 0) {
// NavDestination组件显示时操作
console.info(‘page ON_SHOWN:’ + info.name.toString());
} else if (info.state == 1){
// NavDestination组件隐藏时操作
console.info(‘page ON_HIDE’ + info.name.toString());
} else {
// NavDestination组件其他操作
console.info(‘page state:’ + info.state);
}
})
})
}
}
3、基于路由拦截实现页面返回弹窗确认
业务场景:要针对页面进行统一的逻辑处理,比如在页面返回时根据页面参数配置规则匹配是否需要给出弹窗提示,若规则匹配到就弹框,否则默认走返回流程。
为了实现此功能,需要依赖路由拦截能力,通过路由跳转拦截,实现控制路由跳转,弹窗或阻止路由跳转等操作。 Canary2版本,为NavPathStack提供了setInterception方法,用于设置Navigation页面跳转拦截回调。该方法需要一个NavigationInterception对象,该对象包含三个回调函数:
名称 描述 willShow 页面跳转前拦截,允许操作栈,在当前跳转中生效。 didShow 页面跳转后回调。在该回调中操作栈在下一次跳转中刷新。 modeChange Navigation单双栏显示状态发生变更时触发该回调。
简单理解就是willShow会在from页面动效完成之前回调;didiShow会在from页面动效完成之后回调。此处需要注意的是无论哪个回调,在进入回调时页面栈都已经发生了变化。
案例中,由于在弹出确认框时,不能出现登录页面返回的转场动画,因此需要在willShow回调中执行相关业务。代码中使用了系统路由表框架,具体实现细节参考路由框架
(1)代码判断srcPage为登录页面loginPageView,targetPage为主页面MainPage是执行弹窗逻辑。 (2)由于loginPageView此时已经出栈,需要重新将loginPageView重新入栈。此处需要注意需要将参数重新传递给loginPageView,否则无法顺利完成页面初始化。如果需要保持页面之前的输入值,则需要为组件独立注册LocalStorage以保持状态。 (3)弹窗ConfirmDialog通过Dialog类型的NavDestination实现,启动ComfirmDialog时,将点击OK和Cancel的回调一并传入,从而可以提高ComfirmDialog的独立封装能力。 (4)当点击‘OK’确认返回时,需要将ConfirmDialog和loginPageView同时出栈。此时需要注意这一帧的起点栈为Dialog类型,因此不会有转场动画,如果需要转场动画,需要通过自定义动画实现。
export function registerInterception() { RouterManager.setInterception({ willShow: (<from: NavDestinationContext | “navBar”, to: NavDestinationContext | “navBar”, operation: NavigationOperation, animated: boolean>) => { if (typeof to === “string”) { console.log(“target page is navigation home”); return; } if (typeof from === “string”) { console.log(“target page is navigation home”); return; } let target: NavDestinationContext = to as NavDestinationContext; let srcPage: NavDestinationContext = from as NavDestinationContext; console.log("==== setInterception target.pathInfo.name = " + target.pathInfo.name) if (target.pathInfo.name === ‘MainPage’&& srcPage.pathInfo.name === ‘loginPage’) { RouterManager.pushPath(‘loginPage’, srcPage.pathInfo.param, srcPage.pathInfo.onPop, true) let cdd = new ConfirmDialogData(); cdd.onCancelClick = () => { RouterManager.popWithoutParam(false) } cdd.onConfirmClick = () => { // RouterManager.removeByName(RouterInfo.LOGIN_PAGE) RouterManager.popWithoutParam(true) RouterManager.popWithoutParam(true) } RouterManager.pushPath(“confirmDialog”, cdd, () => {}, false) } }, didShow: (<from: NavDestinationContext | “navBar”, to: NavDestinationContext | “navBar”, operation: NavigationOperation, isAnimated: boolean>) => { }, modeChange: (<mode: NavigationMode>) => { } }) }
4、补充一些有用的知识
从案例中可以看出,loginPage经历了出栈再入栈两个动作,即使再次入栈时,将初始的入参重新传递了回去,但是页面表单之前填入的信息任然无法恢复,也就是如果之前表单填入了’abc’,再次入栈时显示的时初始值,不是’abc’。为了解决这个问题,可以采用组件级别的localStorage来解决。
let localStorageLoginPage: LocalStorage = new LocalStorage(); localStorageLoginPage.setOrCreate(‘userName’, ‘’);
@Component export struct loginPageView { @LocalStorageLink(‘userName’) userName : string = ‘’ @State initUserName : string = ‘’
build() { NavDestination(){ TextInput({placeholder: ‘User Name’, text: this.initUserName}) .onChange((value: string) => { this.userName = value; }).width(‘80%’) … } .hideTitleBar(true) .onReady((ctx) =>{ this.loginParam = ctx.pathInfo.param as LoginParamInHAR; // 判断页面初始输入框初始阶段显示的指是来自传参还是localStorage if (this.userName == ‘’) { this.initUserName = this.loginParam.userName == ‘not login’ ? ‘’ : this.loginParam.userName } else { this.initUserName = this.userName; } }) } }
@Builder export function getLoginPage() : void { loginPageView({}, localStorageLoginPage); }
更多关于HarmonyOS 鸿蒙Next应用级弹窗布局问题:弹窗拦截返回后页面无法返回的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
针对HarmonyOS鸿蒙Next应用级弹窗布局问题中弹窗拦截返回后页面无法返回的情况,这通常是由于弹窗处理逻辑与页面返回栈管理不当所导致。
在鸿蒙系统中,弹窗(Dialog)通常作为临时视图覆盖在当前页面上,当弹窗显示时,如果未正确处理返回事件,可能会导致返回操作被弹窗拦截,而无法传递到下方的页面。
解决此问题的方法通常涉及以下几个方面:
-
弹窗返回事件处理:确保在弹窗中正确处理返回事件,如当用户点击返回键时,可以关闭弹窗而不是退出应用或页面。
-
页面返回栈管理:检查应用中的页面返回栈管理逻辑,确保在弹窗关闭后,能够正确恢复到之前的页面状态。
-
生命周期管理:注意弹窗显示与隐藏时的页面生命周期变化,确保在弹窗隐藏后,页面能够正确响应返回操作。
-
事件传递机制:了解并合理利用鸿蒙系统的事件传递机制,确保返回事件能够按照预期传递到正确的处理逻辑中。
如果问题依旧没法解决请联系官网客服,官网地址是:https://www.itying.com/category-93-b0.html