【HarmonyOS】多Toast显示工具类
【HarmonyOS】封装可以同时显示多个toast的工具类
src/main/ets/common/MyPromptActionUtil.ets
import { ComponentContent, PromptAction, window } from '@kit.ArkUI'; import { BusinessError } from '@kit.BasicServicesKit'; // MyPromptInfo 类用于生成唯一的 dialogID export class MyPromptInfo { public dialogID: string constructor() { this.dialogID = this.generateRandomString(10) } // 生成指定长度的随机字符串 generateRandomString(length: number): string { const characters = 'abcdefghijklmnopqrstuvwxyz0123456789'; let result = ''; for (let i = 0; i < length; i++) { const randomIndex = Math.floor(Math.random() * characters.length); result += characters.charAt(randomIndex); } return result; } } // MyPromptActionUtil 类用于封装弹窗操作 export class MyPromptActionUtil<T extends MyPromptInfo> { static myDialogPromptActionUtil: MyPromptActionUtil<MyToastInfo> | undefined = undefined public static showToast(message: string) { if (!MyPromptActionUtil.myDialogPromptActionUtil) { //当前页面没显示toast // getContext().eventHub.off(MyPromptActionUtil.myDialogPromptActionUtil?.dialogID) // MyPromptActionUtil.myDialogPromptActionUtil?.closeCustomDialog() //如果之前有的toast对话框,并且正在显示,则先关闭toast提示 window.getLastWindow(getContext()).then((windowClass) => { const uiContext = windowClass.getUIContext() MyPromptActionUtil.myDialogPromptActionUtil = new MyPromptActionUtil<MyToastInfo>(uiContext, wrapBuilder(myToastView), new MyToastInfo(message)) .setModal(false)//true:存在黑色半透明蒙层,false:没有蒙层 .setSwipeBackEnabled(false)//true:侧滑允许关闭弹窗 .setMaskTapToCloseEnabled(true)//true:点击半透明蒙层可关闭弹窗【注:如果setModal(false),那么就没有蒙层,所以点击对话框外也没有响应事件,也就是这里设置了也没效果,并且事件会穿透】 .setAlignment(DialogAlignment.Center) .onWillAppear(() => { console.info('在对话框的打开动画开始之前调用的回调函数') getContext().eventHub.on(MyPromptActionUtil.myDialogPromptActionUtil?.dialogID, (data: string) => { //监听结果 if (data == '关闭弹窗') { MyPromptActionUtil.myDialogPromptActionUtil?.closeCustomDialog() } }) }) .onWillDisappear(() => { console.info('在对话框的关闭动画开始之前调用的回调函数') getContext().eventHub.off(MyPromptActionUtil.myDialogPromptActionUtil?.dialogID) MyPromptActionUtil.myDialogPromptActionUtil = undefined }) .showCustomDialog() }) } else { //当前正在显示toast getContext().eventHub.emit(MyPromptActionUtil.myDialogPromptActionUtil.dialogID, { msg: message }) } } private uiContext: UIContext; private promptAction: PromptAction; private contentNode: ComponentContent<T> | undefined; private wrapBuilder: WrappedBuilder<[T]>; private t: T; private isModal: boolean = true; private alignment: DialogAlignment = DialogAlignment.Center; private isSwipeBackEnabled: boolean = true; private isMaskTapToCloseEnabled: boolean = true; public dialogID: string constructor(uiContext: UIContext, wrapBuilder: WrappedBuilder<[T]>, t: T) { this.uiContext = uiContext; this.promptAction = uiContext.getPromptAction(); this.wrapBuilder = wrapBuilder; this.t = t; this.dialogID = t.dialogID } setSwipeBackEnabled(isSwipeBackEnabled: boolean) { this.isSwipeBackEnabled = isSwipeBackEnabled; return this; } setMaskTapToCloseEnabled(isMaskTapToCloseEnabled: boolean) { this.isMaskTapToCloseEnabled = isMaskTapToCloseEnabled return this; } setAlignment(alignment: DialogAlignment) { this.alignment = alignment; return this; } setModal(isModal: boolean) { this.isModal = isModal; return this; } onDidAppear(callback: () => void) { this.onDidAppearCallback = callback; return this; } onDidDisappear(callback: () => void) { this.onDidDisappearCallback = callback; return this; } onWillAppear(callback: () => void) { this.onWillAppearCallback = callback; return this; } onWillDisappear(callback: () => void) { this.onWillDisappearCallback = callback; return this; } private onDidAppearCallback?: () => void; private onDidDisappearCallback?: () => void; private onWillAppearCallback?: () => void; private onWillDisappearCallback?: () => void; closeCustomDialog() { if (this.contentNode) { this.promptAction.closeCustomDialog(this.contentNode); } return this; } // 显示自定义弹窗 showCustomDialog() { try { if (!this.contentNode) { this.contentNode = new ComponentContent(this.uiContext, this.wrapBuilder, this.t); } this.promptAction.openCustomDialog(this.contentNode, { // 打开自定义弹窗 alignment: this.alignment, isModal: this.isModal, showInSubWindow: false, maskRect: { x: 0, y: 0, width: '100%', height: '100%' }, onWillDismiss: (dismissDialogAction: DismissDialogAction) => { //弹窗响应 console.info("reason" + JSON.stringify(dismissDialogAction.reason)) console.log("dialog onWillDismiss") if (dismissDialogAction.reason == 0 && this.isSwipeBackEnabled) { //手势返回时,关闭弹窗。 this.promptAction.closeCustomDialog(this.contentNode) } if (dismissDialogAction.reason == 1 && this.isMaskTapToCloseEnabled) { this.promptAction.closeCustomDialog(this.contentNode) } }, onDidAppear: this.onDidAppearCallback ? this.onDidAppearCallback : () => { }, onDidDisappear: this.onDidDisappearCallback ? this.onDidDisappearCallback : () => { }, onWillAppear: this.onWillAppearCallback ? this.onWillAppearCallback : () => { }, onWillDisappear: this.onWillDisappearCallback ? this.onWillDisappearCallback : () => { }, }); } catch (error) { // 错误处理 let message = (error as BusinessError).message; let code = (error as BusinessError).code; console.error(`OpenCustomDialog args error code is ${code}, message is ${message}`); } return this; } } class MyToastInfo extends MyPromptInfo { public message: string = "" constructor(message: string) { super() this.message = message } } @Builder function myToastView(data: MyToastInfo) { MyToastView({ dialogID: data.dialogID, message: data.message }) } @ObservedV2 class ToastBean { message: string = "" @Trace isShow: boolean = true constructor(message: string) { this.message = message } } @Component struct MyToastView { @State toast_info_list: ToastBean[] = [] @Prop dialogID: string @Prop message: string aboutToAppear(): void { this.toast_info_list.push(new ToastBean(this.message)) getContext().eventHub.on(this.dialogID, (data: object) => { if (data['msg']) { this.toast_info_list.push(new ToastBean(data['msg'])) } }) } build() { Column() { ForEach(this.toast_info_list, (item: ToastBean) => { Text(item.message) .fontSize('36lpx') .fontColor(Color.White) .backgroundColor("#B2ff0000") .borderRadius(8) .constraintSize({ maxWidth: '80%' }) .padding({ bottom: '28lpx', left: '60lpx', right: '60lpx', top: '28lpx' }) .margin(5) .visibility(item.isShow ? Visibility.Visible : Visibility.None) .onVisibleAreaChange([0.0, 1.0], (isVisible: boolean, currentRatio: number) => { console.info('Test Text isVisible: ' + isVisible + ', currentRatio:' + currentRatio) if (isVisible && currentRatio >= 1.0) { setTimeout(() => { item.isShow = false }, 2000) } }) .animation({ duration: 200, onFinish: () => { console.info('==== onFinish') //动画结束后,判断数组是否已全部为隐藏状态,是的话证明所有toast内容都展示完成,可以释放全局弹窗了 let isAnimAll = true for (let i = 0; i < this.toast_info_list.length; i++) { if (this.toast_info_list[i].isShow == true) { //至少有一个正在显示 isAnimAll = false break; } } if (isAnimAll) { console.info('已展示完全部toast,为了性能,关闭弹窗释放view') getContext(this).eventHub.emit(this.dialogID, "关闭弹窗") } } }) .transition(TransitionEffect.OPACITY.animation({ duration: 200 })) }) } } }
src/main/ets/pages/Page01.ets
import { MyPromptActionUtil } from '../common/MyPromptActionUtil' @Entry @Component struct Page01 { build() { Column() { Button('显示Toast').onClick(() => { MyPromptActionUtil.showToast(`随机数:${this.getRandomInt(1, 100)}`) }) } .width('100%') .height('100%') } getRandomInt(min: number, max: number): number { min = Math.ceil(min); max = Math.floor(max); return Math.floor(Math.random() * (max - min + 1)) + min; } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了