HarmonyOS 鸿蒙Next如何自定义弹窗、与组件分离解耦、全局弹窗

发布于 1周前 作者 eggper 来自 鸿蒙OS

HarmonyOS 鸿蒙Next如何自定义弹窗、与组件分离解耦、全局弹窗

目的:自定义弹窗,弹出位置可自定义(底部、中心、顶部),弹窗内容自定义、蒙层取消;与UI组件分离解耦
首先需要在启动类

EntryAbility

中添加如下代码,将uicontext保存到AppStorage中

window.getLastWindow(this.context).then((data: window.Window) => {
let uiContext = data.getUIContext();
AppStorage.setOrCreate<UIContext>(‘uiContext’, uiContext);
// 获取UIContext实例
});

然后我们新建一个弹窗单例工具类

import { ComponentContent, promptAction } from '@kit.ArkUI’

export class PopViewUtils {
private constructor() {}
private static popShare: PopViewUtils
private componentContentList: ComponentContent<object>[] = new Array<ComponentContent<object>>()

static shareInstance() {
if (!PopViewUtils.popShare) {
PopViewUtils.popShare = new PopViewUtils()
}
return PopViewUtils.popShare
}
/
* 自定义弹窗
* @param contentView
* @param args
* @param options
*/
static showPopView<T extends object>(contentView: WrappedBuilder<[T]>, args: T,
options?: promptAction.BaseDialogOptions) {
let uiContext = AppStorage.get<UIContext>(‘uiContext’)
if (uiContext) {
let prompt = uiContext.getPromptAction()
let componentContent = new ComponentContent(uiContext, contentView, args)
let customOptions: promptAction.BaseDialogOptions = {
alignment: options?.alignment || DialogAlignment.Bottom,
autoCancel: options?.autoCancel || true,
onWillDismiss: (action: DismissDialogAction) => {
if (options?.onWillDismiss) {
options.onWillDismiss(action)
}else {
action.dismiss()
}
},
onDidAppear: () => {
if (options?.onDidAppear) {
options.onDidAppear()
}
},
onDidDisappear: () => {
if (options?.onDidDisappear) {
options.onDidDisappear()
}
if (PopViewUtils.shareInstance().componentContentList.length > 0) {
PopViewUtils.shareInstance().componentContentList.pop()
}
},
onWillDisappear: () => {
if (options?.onWillDisappear) {
options.onWillDisappear()
}
}
}
prompt.openCustomDialog(componentContent, customOptions)
PopViewUtils.shareInstance().componentContentList.push(componentContent)
}
}

static closePopView() {
let uiContext = AppStorage.get<UIContext>(‘uiContext’)
if (uiContext) {
let prompt = uiContext.getPromptAction()
let comList = PopViewUtils.shareInstance().componentContentList
let com = PopViewUtils.shareInstance().componentContentList[comList.length - 1]
if (com) {
prompt.closeCustomDialog(com)
PopViewUtils.shareInstance().componentContentList.pop()
}
}
}
}
这里解释一下为什么需要弹窗单例类,原因在于api12中提供的通过
uiContext.getPromptAction()

获取到的

PromptAction对象在close方法
closeCustomDialog<T extends Object>(dialogContent: ComponentContent<T>): Promise<void>;

需要将创建的ComponentContent作为参数传入;所以这里为了与UI解耦和使用的便捷,我们建立了一个ComponentContent数组来管理弹窗的弹出和关闭

示例:

PopViewUtils.showPopView<PopParam>(wrapBuilder(activityPopView), param,
{ alignment: DialogAlignment.Bottom })

这里我们通过

declare function wrapBuilder<Args extends Object[]>(builder: (…args: Args) => void): WrappedBuilder<Args>;

wrapBuilder函数接收一个@Builder修饰的自定义函数如下:

@Builder
export function activityPopView(param: PopParam) {
CouponActivityRulePopView({
activityUrl: param.url,
handleClose: () => {
PopViewUtils.closePopView()
}
})
}

class PopParam {
url: string = ‘’
}

这里需要一下,如果自定义函数没有参数时,需要声明一个假参数Object类型

举例:

PopViewUtils.showPopView<Object>(wrapBuilder(activityPopView), new Object(), { alignment: DialogAlignment.Bottom })


@Builder

export function activityPopView(param: object)

{

CouponActivityRulePopView(

{ activityUrl: param.url,

handleClose: () => { PopViewUtils.closePopView() }

})

}
如上示例没有参数也需要设置为Object;

原因:经过反复测试

ComponentContent有两个构造函数,不传参数的构造函数必闪退

cke_80552.png

示例2

/
* 协议确认勾选弹窗
* @param param
*/
static showNoticeAcceptPrompt(param: PopAcceptParam) {
PopViewUtils.showPopView(wrapBuilder(showAcceptPrompt), param, { alignment: DialogAlignment.Center })
}
@Builder
function showAcceptPrompt(param: PopAcceptParam) {
ProtocolAcceptPopView({
param: param,
closeAction: () => {
PopViewUtils.closePopView()
}
})
}

export interface PopAcceptParam {
title?: string
cancelName?: string
confirmName?: string
/
* 弹窗自定义文案,与multList互斥
*/
noticeContent?: string
/

* 弹窗链接配置项
*/
multList?: Array<Multifunction>
/
* 弹窗链接点击事件
* @param model
*/
linkClick?: (model: Multifunction) => void
/

* 弹窗确认点击事件
*/
confirmClick?: () => void
}

使用:

NotifyUntilHelper.showNoticeAcceptPrompt({
title : ‘提示’,
confirmName:‘同意并购买’,
cancelName:‘取消’,
noticeContent:“已阅读并同意以上《用户须知》和《使用说明》”,
confirmClick:()=>{
this.acceptFlag = true
this.bottomHandleAction()
}
})

如上示例,我们可以将组件中的回调事件及可变的UI配置利用参数对象来进行传递;

底部弹窗自定内容:cke_10062.png

弹窗效果:cke_14523.png

通过PopViewUtils基本上可以满足项目中弹窗解耦、自定义弹出页面、loading等功能;组件的参数直接通过interface定义可配置项;
弹窗封装我也是走了很多弯路,希望能帮到大家;有什么问题或者更好的改进方式可以在评论区留言



关于HarmonyOS 鸿蒙Next如何自定义弹窗、与组件分离解耦、全局弹窗的问题,您也可以访问:https://www.itying.com/category-93-b0.html 联系官网客服。

13 回复

在使用的过程中发现,当存在多个弹出对象,比如loading和弹窗同时出现,loading在接口完成后会自动关闭,当多个接口加载时,如果出现弹窗显示内容,这个时候会出现弹窗被close,loading无法移除的问题;针对该问题进行修复。

PopViewUtils.showPopView<Object>(wrapBuilder(buildText), new Object(), { alignment: DialogAlignment.Bottom })

@Builder export function buildText() { Column() { Text(‘params.text’) .fontSize(16) .fontWeight(FontWeight.Bold) } .width(328) .padding(24) .justifyContent(FlexAlign.Center) .alignItems(HorizontalAlign.Center) .backgroundColor(Color.White) .borderRadius(24) }

这样弹窗出来会闪退

HarmonyOS的分布式文件系统让我在多设备间传输文件变得轻松无比。

import { ComponentContent, promptAction } from '[@kit](/user/kit).ArkUI'

export class PopViewUtils {
private constructor() {}
private static popShare: PopViewUtils
private infoList: PopViewModel[] = new Array<PopViewModel>()

static shareInstance() {
if (!PopViewUtils.popShare) {
PopViewUtils.popShare = new PopViewUtils()
}
return PopViewUtils.popShare
}
/**
* 自定义弹窗
* [@param](/user/param) contentView
* [@param](/user/param) args
* [@param](/user/param) options
*/
static showPopViewWithType<T extends object>(type:PopViewShowType,contentView: WrappedBuilder<[T]>, args: T,
options?: promptAction.BaseDialogOptions) {
let uiContext = AppStorage.get<UIContext>('uiContext')
if (uiContext) {
let prompt = uiContext.getPromptAction()
let componentContent = new ComponentContent(uiContext, contentView, args)
let customOptions: promptAction.BaseDialogOptions = {
alignment: options?.alignment || DialogAlignment.Bottom,
autoCancel: options?.autoCancel || true,
maskColor:options?.maskColor,
onWillDismiss: (action: DismissDialogAction) => {
if (options?.onWillDismiss) {
options.onWillDismiss(action)
}else {
if (action.reason == DismissReason.TOUCH_OUTSIDE) {
action.dismiss()
}
}
},
onDidAppear: () => {
if (options?.onDidAppear) {
options.onDidAppear()
}
},
onDidDisappear: () => {
if (options?.onDidDisappear) {
options.onDidDisappear()
}
if (PopViewUtils.shareInstance().infoList.length > 0) {
PopViewUtils.shareInstance().infoList.pop()
}
},
onWillDisappear: () => {
if (options?.onWillDisappear) {
options.onWillDisappear()
}
}
}
prompt.openCustomDialog(componentContent, customOptions)
let infoList = PopViewUtils.shareInstance().infoList
let info : PopViewModel = {
com:componentContent,
popType:type
}
infoList[0] = info
console.debug("弹出 pop num" + infoList.length)

}else {
console.debug("获取uiContext失败")
}
}
/**
* 弹窗
* [@param](/user/param) contentView
* [@param](/user/param) args
* [@param](/user/param) options
*/
static showPopView<T extends object>(contentView: WrappedBuilder<[T]>, args: T,
options?: promptAction.BaseDialogOptions) {
PopViewUtils.showPopViewWithType(PopViewShowType.alter,contentView,args,options)
}

static closePopView() {

PopViewUtils.closePopViewWithType(PopViewShowType.alter)
}


static closePopViewWithType(type:PopViewShowType) {
let uiContext = AppStorage.get<UIContext>('uiContext')
if (uiContext) {
let prompt = uiContext.getPromptAction()
let sameTypeList = PopViewUtils.shareInstance().infoList.filter((model)=>{
return model.popType == type
})
let info = sameTypeList[sameTypeList.length - 1]
if (info.com) {
PopViewUtils.shareInstance().infoList = PopViewUtils.shareInstance().infoList.filter((model)=>{
return model.com != info.com
})
prompt.closeCustomDialog(info.com)
}
}else {
console.debug("获取uiContext失败")
}
}
}
export enum PopViewShowType {
alter,
loading
}
interface PopViewModel {
com : ComponentContent<object>
popType : PopViewShowType
}

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

感谢分享,收藏为工具库了(doge
能截一下图片看吗, 看看效果图

已补充,弹出内容大小均个人自定义,可设置弹出内容位置

你好,源码能上传一下吗

//自定义全局dialog布局 @Component export struct MyDialogView{ param:MyDialogParams=new MyDialogParams(); checkState:boolean=false; build() { Column(){ if(this.param.type!=MYDIALOGTYPE.NOTITILE) { Text(this.param.titleText) .width(‘100%’) .textAlign(TextAlign.Center) .height(44) .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }) .fontColor(this.param.titleFontColor) .fontSize(this.param.titleFontSize) .padding({ left: 30, right: 30 }) } Text(this.param.contentText) .fontColor(this.param.contentFontColor) .fontSize(this.param.contentFontSize) .padding({ left:30, right:30, bottom:20, top:20 }) if(this.param.type==MYDIALOGTYPE.HASRECENT) { Row() { Checkbox() .shape(CheckBoxShape.ROUNDED_SQUARE) .select(this.checkState) .selectedColor($r(‘app.color.color_f39800’)) .width(20) .height(20) .margin({ right: 5 }) .onChange((check:boolean)=>{ this.checkState=check; }) Text($r(‘app.string.recentNoResponse’)) .fontSize(12) .fontColor($r(‘app.color.color_999999’)) }.width(‘100%’) .padding({ left: 30, right: 30, bottom: 20 }) } Line() .width(‘100%’) .height(1) .backgroundColor($r(‘app.color.color_EBEBEB’)) Row(){ if(this.param.type!=MYDIALOGTYPE.NOCANCEL) { Text(this.param.cancelText) .fontColor(this.param.cancelFontColor) .fontSize(this.param.clickTextFontSize) .layoutWeight(1) .textAlign(TextAlign.Center) .onClick(() => { MyDialogUtils.hide(); if (this.param.cancelClickEvent) { this.param.cancelClickEvent(); } }) Line() .width(1) .height(‘100%’) .backgroundColor($r(‘app.color.color_EBEBEB’)) } Text(this.param.sureText) .fontColor(this.param.cancelFontColor) .fontSize(this.param.clickTextFontSize) .layoutWeight(1) .textAlign(TextAlign.Center) .onClick(()=>{ if(this.param.type==MYDIALOGTYPE.HASRECENT){ GlobalUtils.getInstance().getPreferences().putSync(ConstantsUtils.DELIVER_SHOULD_SHOW_TIP,!this.checkState); GlobalUtils.getInstance().getPreferences().flush(); } MyDialogUtils.hide(); if(this.param.sureClickEvent){ this.param.sureClickEvent(); } }) }.width(‘100%’) .height(45) }.width(‘80%’).backgroundColor($r(‘app.color.color_FFFFFF’)) .borderRadius(4) }

} export enum MYDIALOGTYPE{ NORMAL=0, NOCANCEL=3, NOTITILE=1, HASRECENT=2,

} export class MyDialogParams { cancelText:string|Resource=$r(‘app.string.cancel’); sureText:string|Resource=$r(‘app.string.sure’); clickTextFontSize:number|Resource=16; cancelFontColor:Resource=$r(‘app.color.color_565656’); sureFontColor:Resource=$r(‘app.color.color_565656’); hasTitle:boolean=true; titleFontSize:Resource|number=18; titleFontColor:Resource=$r(‘app.color.color_212121’); titleText:string|Resource=$r(‘app.string.myDialogWarmTip’); hasCancelButton:boolean=true; contentText:Resource|string=""; contentFontColor:Resource=$r(‘app.color.color_212121’); contentFontSize:Resource|number=14; type:MYDIALOGTYPE=MYDIALOGTYPE.NORMAL; sureClickEvent?:Function; cancelClickEvent?:Function; } @Builder export function myDialogBuilder(params:MyDialogParams){ MyDialogView({ param:params }) }

//调用dialog方法 class MyDialogUtils{ private static myDialog:ComponentContent<MyDialogParams> | null = null private static promptAction:PromptAction show(params:MyDialogParams){ window.getLastWindow(getContext()).then((windowClass)=>{ const uiContext = windowClass.getUIContext() MyDialogUtils.myDialog= new ComponentContent(uiContext, wrapBuilder(myDialogBuilder),params); MyDialogUtils.promptAction = uiContext.getPromptAction() MyDialogUtils.promptAction.openCustomDialog(MyDialogUtils.myDialog, { alignment:DialogAlignment.Center, isModal:true, autoCancel:false } ) }) } hide(){ MyDialogUtils.promptAction.closeCustomDialog(MyDialogUtils.myDialog); } } let myDialogUtils=new MyDialogUtils(); export default myDialogUtils as MyDialogUtils;

使用时 let params = new MyDialogParams(); params.contentText = getResourceString($r(‘app.string.showDeliverDialog1’)) + jobName + getResourceString($r(‘app.string.showDeliverDialog2’)); params.sureClickEvent = callback(); params.type = MYDIALOGTYPE.HASRECENT; MyDialogUtils.show(params);

向大佬学习!
回到顶部