前言
从api9开始开发鸿蒙的大佬们想必一开始被弹窗折腾得有点难受,使用起来各种不顺手且不方便还有各种限制, 尤其是面对复杂且多样化的需求设计场景下太难受了,还好ComponentContent在api12的版本出现了
ComponentContent的出现能解决什么问题?
以CustomDialog弹窗为例,如果要使用弹窗CustomDialogController必须定义在页面中,通过CustomDialogController控制弹窗显示隐藏,这样就至少带来2个问题:
1:哪里需要弹窗就需要在哪里声明CustomDialogController,不能直接在其他普通类(非@Component)直接使用弹窗,这样就导致把弹窗的部分逻辑强制耦合在页面上,业务越复杂,耦合度越高
2:在实际业务场景下弹窗不方便改成普通页面或者普通组件
而通过ComponentContent实现弹窗的方案只需要一个uiContext即可在任意地方控制弹窗显示,而且还能快速的改造成其他页面、组件、甚至是overlay(浮层)和bindSheet(半模态)且入侵性极低
基于ComponentContent实现的弹窗有多方便?
显示弹窗的核心代码就三行(实例化Dialog,设置视图,显示弹窗),以前做Android开发的应该对这个写法很熟悉
text
import { BaseDialog, ComponentParam } from '@zhongrui/easy_dialog'
@Entry
@Component
struct Page {
build() {
Column() {
Button('显示弹窗').onClick(() => {
//只要有uiContext,此处显示弹窗的代码可以写到主线程的任意地方
const dialog = new BaseDialog(this.getUIContext())
//传入弹窗视图和参数
dialog.setContentView(wrapBuilder(dialogView), new ComponentParam('自定义参数'))
dialog.show()
})
//弹窗的视图以组件的形式在页面中直接使用
DialogView({ param: new ComponentParam('自定义参数') })
}
.height('100%')
.width('100%')
}
}
@Builder
function dialogView(param: ComponentParam<string>) {
DialogView({ param: param })
}
@ComponentV2
struct DialogView {
@Param param: ComponentParam<string> = new ComponentParam('')
build() {
Button('销毁弹窗').onClick(() => {
this.param.dismiss()
})
}
}
基于ComponentContent实现的弹窗如何快速改造成页面、组件、甚至是overlay(浮层)和bindSheet(半模态)
text
//弹窗的视图以组件的形式在页面中直接使用,其他地方可以不做任何修改编译不会报错
DialogView({ param: new ComponentParam('自定义参数') })
text
//改成overlay浮层
const dialog = new BaseOverlay(this.getUIContext())
dialog.setContentView(wrapBuilder(dialogView), new ComponentParam('自定义参数'))
dialog.show()
text
//改成bindSheet半模态
const dialog = new BaseSheet(this.getUIContext(),'页面组件id')
dialog.setContentView(wrapBuilder(dialogView), new ComponentParam('自定义参数'))
dialog.show()
没错,只需要在实例化的时候,改个名字就行了
下载安装
ohpm install @zhongrui/easy_dialog
easy_dialog开源库中心仓(api详细使用说明)
源码地址
效果图
![]() |
![]() |
![]() |
![]() |
当出现以下场景时使用easy_dialog轻松应对
-
场景1:不同页面显示同一个弹窗时,弹窗上面某些埋点上报需要上报不同页面来源(页面传递参数给弹窗视图)
-
场景2:在弹窗的视图上执行某个操作之后,需要将某些数据同步给页面(弹窗视图和页面通信)
-
场景3:页面满足某个状态时,需要将某些数据同步给弹窗视图(页面和弹窗视图通信)
-
场景4:页面出现多个运营活动弹窗,产品要求出现多个弹窗的场景,上一个弹窗消失时,下一个弹窗才能出现,不能同时显示
-
场景5:页面,弹窗,半模态之间切换
领导:这个登录页面你改成弹窗实现吧
我:修改这个大概需要1个小时(将页面改成弹窗用easy_dialog实际上10分钟不到就改完了)
领导:这个弹窗能不能滑上去,我看其他app有这个效果,能不能改成这样的效果?
我:我瞅瞅,噢~这个半模态效果啊,可以改的
领导:我听其他开发说这是bindSheet,你再花1小时研究下怎么实现的
我:好的好的(用2秒钟把BaseDialog改成了BaseSheet,然后摸鱼59分钟后去给领导演示效果)
实现上述场景的需求,核心是解耦的条件下进行传参和通信,只要解决这两个问题那么该方案就能覆盖99%以上的业务场景
场景1:不同页面显示同一个弹窗时,弹窗上面某些埋点上报需要上报不同页面来源
text
const dialog = new BaseDialog(this.getUIContext())
dialog.setContentView(wrapBuilder(dialogView), new ComponentParam('自定义参数'))
//通过Tag给Dialog设置页面来源
dialog.addTag('pageSource','xxx')
dialog.show()
@Builder
function dialogView(param: ComponentParam<string>) {
DialogView({ param: param })
}
text
@ComponentV2
struct DialogView {
@Param param: ComponentParam<string> = new ComponentParam('')
aboutToAppear(): void {
//获取页面来源
const pageSource = this.param.getTag('pageSource')
}
build() {
Text()
}
}
场景2:在弹窗的视图上执行某个操作之后,需要将某些数据同步给页面
text
@Entry
@Component
struct Page {
build() {
Column() {
Button('显示弹窗').onClick(() => {
const dialog = new BaseDialog(this.getUIContext())
dialog.setContentView(wrapBuilder(dialogView), new ComponentParam('自定义参数'))
//注册事件,接收弹窗视图发送的事件
dialog.addEvent<string>('key', (data) => {
//获取事件传的参数
const result = data
return '返回值'
})
dialog.show()
})
}
.height('100%')
.width('100%')
}
}
@Builder
function dialogView(param: ComponentParam<string>) {
DialogView({ param: param })
}
text
@ComponentV2
struct DialogView {
@Param param: ComponentParam<string> = new ComponentParam('')
build() {
Text('xxx操作').onClick(() => {
//给Dialog发送事件且获取结果
const result = this.param.sendEvent('key', '自定义数据')
})
}
}
场景3:页面满足某个状态时,需要将某些数据同步给弹窗视图(页面和弹窗视图通信)
text
import { BaseDialog, ComponentParam } from '@zhongrui/easy_dialog'
@Entry
@Component
struct Page {
build() {
Column() {
Button('显示弹窗').onClick(() => {
const dialog = new BaseDialog(this.getUIContext())
dialog.setContentView(wrapBuilder(dialogView), new ComponentParam('自定义参数'))
dialog.show()
setTimeout(() => {
//模拟触发某个状态
//给弹窗视图发送事件并获取返回值
const result = dialog.sendEvent('key', '自定义数据')
}, 2000)
})
}
.height('100%')
.width('100%')
}
}
@Builder
function dialogView(param: ComponentParam<string>) {
DialogView({ param: param })
}
text
@ComponentV2
struct DialogView {
@Param param: ComponentParam<string> = new ComponentParam('')
aboutToAppear(): void {
//注册事件,接收dialog发送的事件
this.param.addEvent<string>('key', (data) => {
//获取事件传的参数
const result = data
return '返回值'
})
}
build() {
}
}
场景4:页面出现多个运营活动弹窗,产品要求出现多个弹窗的场景,上一个弹窗消失时,下一个弹窗才能出现,不能同时显示
text
import { BaseDialog, ComponentParam, DialogManager } from '@zhongrui/easy_dialog'
@Entry
@Component
struct Page {
build() {
Column() {
Button('显示弹窗').onClick(() => {
const dialog1 = new BaseDialog(this.getUIContext())
dialog1.setContentView(wrapBuilder(dialogView), new ComponentParam('第1个弹窗'))
const dialog2 = new BaseDialog(this.getUIContext())
dialog2.setContentView(wrapBuilder(dialogView), new ComponentParam('第2个弹窗'))
const dialog3 = new BaseDialog(this.getUIContext())
dialog3.setContentView(wrapBuilder(dialogView), new ComponentParam('第3个弹窗'))
DialogManager.get('def').show(dialog1)
DialogManager.get('def').show(dialog2)
DialogManager.get('def').show(dialog3)
//或者
DialogManager.get('def').addDialog(dialog1)
DialogManager.get('def').addDialog(dialog2)
DialogManager.get('def').addDialog(dialog3)
DialogManager.get('def').show()
})
}
.height('100%')
.width('100%')
}
}
@Builder
function dialogView(param: ComponentParam<string>) {
DialogView({ param: param })
}
text
@ComponentV2
struct DialogView {
@Param param: ComponentParam<string> = new ComponentParam('')
build() {
Button('取消弹窗').onClick(() => {
this.param.dismissAndShowNext()
})
}
}
实际项目用法
1:新建一个普通类比如LoginDialog,继承BaseDialog或BaseOverlay或BaseSheet
2:创建一个静态方法对外提供,哪个页面需要显示弹窗,调用静态方法即可,页面不用关心弹窗的创建逻辑以及弹窗内部交互逻辑
3:@Builder装饰的function方法定义在LoginDialog文件里面
4:遇到复杂的业务场景,部分业务逻辑在LoginDialog类中单独闭环与外部页面完全解耦
页面
text
import { LoginDialog, LoginResult } from '../test/LoginDialog'
@Entry
@Component
struct Page {
build() {
Column() {
Button('显示登录弹窗').onClick(() => {
LoginDialog.showDialog(this.getUIContext(), '156xxx8888', (result: LoginResult) => {
//获取登录结果
})
})
}
.height('100%')
.width('100%')
}
}
LoginDialog类
text
import { BaseDialog, ComponentParam } from '@zhongrui/easy_dialog';
import { LoginDialogView } from './LoginDialogView';
export interface LoginParam {
phone: string
}
export interface LoginResult {
phone: string
userName: string
pwd: string
}
@Builder
function dialogView(param: ComponentParam<LoginParam>) {
LoginDialogView({ param: param })
}
export class LoginDialog extends BaseDialog {
public static showDialog(uiContext: UIContext, phone: string,
loginSuccess: (data: LoginResult) => void): LoginDialog {
//构造业务参数
const data: ComponentParam<LoginParam> = new ComponentParam<LoginParam>({ phone: phone })
//创建弹窗
const dialog = new LoginDialog(uiContext)
//设置视图
dialog.setContentView(wrapBuilder(dialogView), data)
//设置弹窗参数promptAction.BaseDialogOptions(和官网文档一致)
//maskRect,alignment,offset,isModal,showInSubWindow,onWillDismiss,autoCancel
//maskColor,transition,dialogTransition等
dialog.setOption({})
//设置tag
dialog.addTag('key', '自定义参数')
//注册登录成功事件
dialog.addEvent<LoginResult>('success', (data: LoginResult | undefined) => {
if (!data) {
return
}
loginSuccess?.(data)
})
//显示弹窗
dialog.show()
return dialog
}
//可以定义其他方法根据实际场景处理一些额外的业务逻辑
public otherMethod() {
this.addTag('key', 'value')
}
}
LoginDialog视图
text
import { ComponentParam } from '@zhongrui/easy_dialog'
import { LoginParam, LoginResult } from './LoginDialog'
@ComponentV2
export struct LoginDialogView {
@Param param: ComponentParam<LoginParam> = new ComponentParam<LoginParam>({ phone: '' })
@Local phone: string = ''
aboutToAppear(): void {
this.phone = this.param.data.phone
//可以获取其他参数
this.param.getTag('key')
}
build() {
Column() {
Text("手机号:" + this.phone)
Button('登录').onClick(() => {
//登录成功
const result: LoginResult = {
phone: this.phone,
userName: 'Harmony',
pwd: 'OpenHarmony'
}
//发送登录成功事件
this.param.sendEvent('success', result)
//隐藏弹窗
this.param.dismiss()
})
}
}
}
进阶用法
公共部分
typescript
//除了构造函数能设置业务参数,也可以通过setData设置,支持各种类型
const data: ComponentParam<string> = new ComponentParam('自定义参数')
data.setData('xxx')
//Dialog(自定义弹窗)
let dialog: AbsDialog = new BaseDialog(this.getUIContext())
//Overlay(浮层)
dialog = new BaseOverlay(this.getUIContext())
//bindSheet(半模态弹窗)
dialog = new BaseSheet(this.getUIContext(), 'contentId')
//Dialog(自定义弹窗)设置绑定的组件id
//虽然不设置也可以,但是后续Navigation跳转的页面都会被弹窗覆盖,根据实际场景调用即可
dialog.setComponentId('contentId')
//(和官方文档一致)设置实例化ComponentContent的参数,所以setBuildOption必须在setContentView之前调用
const buildOption: BuildOptions = {}
dialog.setBuildOption(buildOption)
//设置弹窗视图
dialog.setContentView(wrapBuilder(dialogView), data)
//注册事件,接收视图发送的事件,获取事件参数的同时,支持设置返回值
dialog.addEvent<number>('key', (result: number | undefined) => {
return 123
})
dialog.addEvent<string>('key', (result: string | undefined) => {
})
//删除单个事件
dialog.deleteEvent('key')
//给组件视图发送事件(不是dialog.addEvent注册的事件)并获取事件的返回值
const result1 = dialog.sendEvent('key')
const result2 = dialog.sendEvent('key', '自定义类型参数')
//清除Dialog注册的所有事件
dialog.clearDialogRegisterEvent()
//清除视图注册的所有事件
dialog.clearComponentRegisterEvent()
//清除Dialog注册的事件+当前组件注册的事件
dialog.clearAllEvent()
//设置tag,在dialog和组件视图中都可以通过getTag可以获取
dialog.setTag('xxx')
//添加tag,支持各种类型,在dialog和组件视图中都可以通过getTag(key)可以获取
dialog.addTag('key', 'value')
//获取tag,不传key获取setTag('xxx')的值,传key获取addTag('key', 'value')的值
//在dialog和组件视图中都可以获取
dialog.getTag('key')
//删除tag
dialog.deleteTag('key')
//清除tag,true:清除所有tag,false:只清除addTag('key', 'value')的值,不清除setTag('xxx')的值
dialog.clearTag(true)
//显示弹窗
dialog.show()
//隐藏弹窗,下次直接调用show方法即可再次显示
dialog.hide()
//销毁弹窗,下次显示必须重新实例化
dialog.dismiss()
//隐藏弹窗/浮层/sheet,显示下一个(用于有序弹窗/浮层/sheet)
dialog.hideAndShowNext()
//隐藏并销毁弹窗/浮层/sheet,显示下一个(用于有序弹窗/浮层/sheet)
dialog.dismissAndShowNext()
//判断弹窗是否显示
dialog.isShowing()
//判断弹窗是否隐藏
dialog.isHide()
//判断弹窗是否销毁
dialog.isDismiss()
//(和官方文档一致)用于更新WrappedBuilder对象封装的builder函数参数,与constructor传入的参数类型保持一致
const newData: ComponentParam<string> = new ComponentParam('自定义参数')
dialog.update(newData)
//触发ComponentContent中的自定义组件的复用
dialog.reuse()
//触发ComponentContent中自定义组件的回收
dialog.recycle()
//传递系统环境变化事件,触发节点的全量更新
dialog.updateConfiguration()
组件视图
txt
import { ComponentParam } from '@zhongrui/easy_dialog'
@ComponentV2
export struct DialogView {
//固定写法
@Param param: ComponentParam<string> = new ComponentParam('')
@Local title: string = ''
aboutToAppear(): void {
//获取参数
this.title = this.param.data
//注册接收Dialog发送的事件
this.param.addEvent<string>('key', (result: string | undefined) => {
return 'xxx'
})
this.param.addEvent<number>('key', () => {
})
//删除注册的事件
this.param.deleteEvent('key')
//给Dialog发送事件并获取返回值
const result1 = this.param.sendEvent('key')
const result2 = this.param.sendEvent('key', 'xxx')
//隐藏弹窗/浮层/sheet
this.param.hide()
//隐藏并销毁弹窗/浮层/sheet
this.param.dismiss()
//隐藏弹窗/浮层/sheet,显示下一个(用于有序弹窗/浮层/sheet)
this.param.hideAndShowNext()
//隐藏并销毁弹窗/浮层/sheet,显示下一个(用于有序弹窗/浮层/sheet)
this.param.dismissAndShowNext()
//设置tag,在dialog和组件视图中都可以通过getTag可以获取
this.param.setTag('xxx')
//添加tag,支持各种类型,在dialog和组件视图中都可以通过getTag(key)可以获取
this.param.addTag('key', 'value')
//获取tag,不传key获取setTag('xxx')的值,传key获取addTag('key', 'value')的值
//在dialog和组件视图中都可以获取
this.param.getTag('key')
//删除tag
this.param.deleteTag('key')
//清除tag,true:清除所有tag,false:只清除addTag('key', 'value')的值,不清除setTag('xxx')的值
this.param.clearTag(true)
//清除Dialog注册的事件
this.param.clearDialogRegisterEvent()
//清除当前组件注册的事件
this.param.clearComponentRegisterEvent()
//清除Dialog注册的事件+当前组件注册的事件
this.param.clearAllEvent()
}
build() {
Text(this.title)
}
}
差异部分
Dialog(自定义弹窗)
typescript
const data: ComponentParam<string> = new ComponentParam('自定义参数')
data.setData('xxx')
const dialog = new BaseDialog(this.getUIContext())
dialog.setContentView(wrapBuilder(dialogView), data)
//(和官方文档一致)设置弹窗参数
const options: promptAction.BaseDialogOptions = {}
dialog.setOption(options)
//(和官方文档一致)在弹窗显示的情况下可以更新弹窗参数
const dialogOption: promptAction.BaseDialogOptions = {}
dialog.updateDialog(dialogOption)
dialog.show()
Overlay(浮层)
typescript
const data: ComponentParam<string> = new ComponentParam('自定义参数')
data.setData('xxx')
const dialog = new BaseOverlay(this.getUIContext())
dialog.setContentView(wrapBuilder(dialogView), data)
//(和官方文档一致)设置OverlayManager参数
const options: OverlayManagerOptions = {}
dialog.setOverlayManagerOption(options)
//新增视图节点在OverlayManager上的层级位置,当index ≥ 0时,越大
dialog.setIndex(1)
dialog.show()
bindSheet(半模态弹窗)
typescript
const data: ComponentParam<string> = new ComponentParam('自定义参数')
data.setData('xxx')
const dialog = new BaseSheet(this.getUIContext(), 'contentId')
dialog.setContentView(wrapBuilder(dialogView), data)
//(和官方文档一致)设置半模态参数
const options: SheetOptions = {}
dialog.setSheetOption(options)
dialog.show()
历史文章
HarmonyOS NEXT多环境+多渠道+自定义路径输出+自定义名称一键打app和hap包
HarmonyOS NEXT数据列表加载更多(无需监听列表滑到最底部)



