【HarmonyOS Next】鸿蒙应用弹框和提示气泡详解(一)
一、前言
应用开发中我们会很频繁的接触,弹框Dialog和提示气泡Toast的使用。
移动开发的同学会比较熟悉。不过在鸿蒙的响应式布局中,使用鸿蒙早期提供的弹框Dialog和提示气泡Toast,会很不方便。因为是和UI绑定在一起,在纯逻辑类文件中使用不了。
因为该历史问题,后续API迭代时,对鸿蒙的弹框Dialog和提示气泡Toast进行了升级。目前可以在纯逻辑类中使用,与UI进行了解耦。
迭代优化过程 :
从page界面UI上弹出 =》挂靠子窗口实现弹出=》UI框架层预留挂靠节点

根据迭代过程,我们可以发现,目前和UI强绑定的实现方式API已标注不推荐。
二、鸿蒙中的弹框使用
综上所述,目前鸿蒙HarmonyOS对于弹框和提示气泡,以及延申的组件(浮层,Popup,Menu, bindSheet, bindContentCover)
实现方案都是挂靠到UI框架的预留节点上,进行添加渲染。
1.弹框的实现
目前弹框有两种方式:
(1)系统定制弹框,可以直接使用。
系统定制弹框根据业务复杂度的不同,也有两种方式封装。
分别是基础弹框(警告弹框,列表弹窗):

dart
@Entry
@Component
struct AlertDialogTextPage {
build() {
Column() {
Button('showAlertDialog')
.margin(30)
.onClick(() => {
this.getUIContext().showAlertDialog(
{
title: 'title',
message: 'text',
autoCancel: true,
alignment: DialogAlignment.Center,
buttons: [{
value: 'cancel',
action: () => {
console.info('cancel')
}
},
{
enabled: true,
defaultFocus: true,
style: DialogButtonStyle.HIGHLIGHT,
value: 'ok',
action: () => {
console.info('ok')
}
}],
}
)
})
}
.width('100%')
.margin({ top: 5 })
}
}
还有带业务性质的PickerDialog弹框:
日历选择器弹窗 (CalendarPickerDialog)
日期滑动选择器弹窗 (DatePickerDialog)
时间滑动选择器弹窗 (TimePickerDialog)
文本滑动选择器弹窗 (TextPickerDialog)
dart
// 日历选择器弹窗示例 (CalendarPickerDialog)
@Entry
@Component
struct PickerDialogTextPage {
private selectedDate: Date = new Date('2024-04-23')
build() {
Column() {
Button("Show CalendarPicker Dialog")
.margin(20)
.onClick(() => {
console.info("CalendarDialog.show")
CalendarPickerDialog.show({
selected: this.selectedDate,
acceptButtonStyle: {
fontColor: '#2787d9',
fontSize: '16fp',
backgroundColor: '#f7f7f7',
borderRadius: 10
},
cancelButtonStyle: {
fontColor: Color.Red,
fontSize: '16fp',
backgroundColor: '#f7f7f7',
borderRadius: 10
},
onAccept: (date: Date)=>{
// 当弹出框再次弹出时显示选中的是上一次确定的日期
this.selectedDate = date
}
})
})
}.width('100%')
}
}
(2)自定义弹框。
需要创建节点对象,在节点对象中添加自定义弹框的布局:
dart
@State message: string = "测试文本"
/**
* 自定义弹框布局
* @param params
*/
@Builder
function buildText(params: Params) {
Column() {
Text(params.text)
.fontSize(50)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 36 })
Button('Close')
.onClick(() => {
PromptActionClass.closeDialog()
})
}.backgroundColor('#FFF0F0F0')
}
private contentNode: ComponentContent<Object> =
new ComponentContent(this.ctx, wrapBuilder(buildText), new Params(this.message));
之后获取PromptAction对象,调用openCustomDialog弹出自定义弹框:
dart
this.getContext().getPromptAction().openCustomDialog(PromptActionClass.contentNode)
.then(() => {
console.info('OpenCustomDialog complete.')
})
.catch((error: BusinessError) => {
let message = (error as BusinessError).message;
let code = (error as BusinessError).code;
console.error(`OpenCustomDialog args error code is ${code}, message is ${message}`);
})
当你需要修改自定义弹框的对齐方式,偏移量等,需要添加promptAction.BaseDialogOptions。
dart
this.getContext().getPromptAction().openCustomDialog(PromptActionClass.contentNode,{ alignment: DialogAlignment.Top, offset: { dx: 0, dy: 50 } })
.then(() => {
console.info('OpenCustomDialog complete.')
})
2.气泡的实现
Toast的使用很简单,目前只能支持自定义文本内容,气泡层级(默认在页面内,Top在页面上)
对齐方式和悬浮态。不能设置字体的大小和颜色。如果要自定义字体大小和颜色。需要使用自定义弹框或者Popup来实现。
dart
import { promptAction } from '@kit.ArkUI'
import { BusinessError } from '@kit.BasicServicesKit'
/**
* 气泡示例
*/
@Entry
@Component
struct ToastTextPage {
build() {
Column() {
Button('Show toast').fontSize(20)
.onClick(() => {
try {
this.getUIContext().getPromptAction().showToast({
message: '测试气泡',
duration: 2000,
showMode: promptAction.ToastShowMode.TOP_MOST
});
} catch (error) {
let message = (error as BusinessError).message
let code = (error as BusinessError).code
console.error(`showToast args error code is ${code}, message is ${message}`);
};
})
}
.height('100%')
.width('100%')
.justifyContent(FlexAlign.Center)
}
}
三、源码示例
dart
// PromptActionClass.ets
import { BusinessError } from '@kit.BasicServicesKit';
import { ComponentContent, promptAction } from '@kit.ArkUI';
import { PromptAction, UIContext } from '@ohos.arkui.UIContext';
/**
* 自定义弹框封装
*/
export class PromptActionClass {
static ctx: UIContext;
static contentNode: ComponentContent<Object>;
static options: promptAction.BaseDialogOptions;
static setContext(context: UIContext) {
PromptActionClass.ctx = context;
}
static setContentNode(node: ComponentContent<Object>) {
PromptActionClass.contentNode = node;
}
static setOptions(options: promptAction.BaseDialogOptions) {
PromptActionClass.options = options;
}
static openDialog() {
if (PromptActionClass.contentNode !== null) {
PromptActionClass.ctx.getPromptAction().openCustomDialog(PromptActionClass.contentNode, PromptActionClass.options)
.then(() => {
console.info('OpenCustomDialog complete.')
})
.catch((error: BusinessError) => {
let message = (error as BusinessError).message;
let code = (error as BusinessError).code;
console.error(`OpenCustomDialog args error code is ${code}, message is ${message}`);
})
}
}
static closeDialog() {
if (PromptActionClass.contentNode !== null) {
PromptActionClass.ctx.getPromptAction().closeCustomDialog(PromptActionClass.contentNode)
.then(() => {
console.info('CloseCustomDialog complete.')
})
.catch((error: BusinessError) => {
let message = (error as BusinessError).message;
let code = (error as BusinessError).code;
console.error(`CloseCustomDialog args error code is ${code}, message is ${message}`);
})
}
}
static updateDialog(options: promptAction.BaseDialogOptions) {
if (PromptActionClass.contentNode !== null) {
PromptActionClass.ctx.getPromptAction().updateCustomDialog(PromptActionClass.contentNode, options)
.then(() => {
console.info('UpdateCustomDialog complete.')
})
.catch((error: BusinessError) => {
let message = (error as BusinessError).message;
let code = (error as BusinessError).code;
console.error(`UpdateCustomDialog args error code is ${code}, message is ${message}`);
})
}
}
}
class Params {
text: string = ""
constructor(text: string) {
this.text = text;
}
}
/**
* 自定义弹框布局
* @param params
*/
@Builder
function buildText(params: Params) {
Column() {
Text(params.text)
.fontSize(50)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 36 })
Button('Close')
.onClick(() => {
PromptActionClass.closeDialog()
})
}.backgroundColor('#FFF0F0F0')
}
/**
* 首页
*/
@Entry
@Component
struct Index {
@State message: string = "hello"
private ctx: UIContext = this.getUIContext();
private contentNode: ComponentContent<Object> =
new ComponentContent(this.ctx, wrapBuilder(buildText), new Params(this.message));
@State handlePopup: boolean = false
aboutToAppear(): void {
PromptActionClass.setContext(this.ctx);
PromptActionClass.setContentNode(this.contentNode);
PromptActionClass.setOptions({ alignment: DialogAlignment.Top, offset: { dx: 0, dy: 50 } });
}
build() {
Row() {
Column() {
/**
* 显示自定义气泡 更新样式
*/
Button("open dialog and update options")
.margin({ top: 50 })
.onClick(() => {
PromptActionClass.openDialog()
setTimeout(() => {
PromptActionClass.updateDialog({
alignment: DialogAlignment.Bottom,
offset: { dx: 0, dy: -50 }
})
}, 1500)
})
/**
* 显示自定义气泡 更新内容数据
*/
Button("open dialog and update content")
.margin({ top: 50 })
.onClick(() => {
PromptActionClass.openDialog()
setTimeout(() => {
this.contentNode.update(new Params('update'))
}, 1500)
})
/**
* 气泡菜单
*/
Button('PopupOptions')
.onClick(() => {
this.handlePopup = !this.handlePopup
})
.bindPopup(this.handlePopup, {
message: 'This is a popup with PopupOptions',
})
/**
* 提示气泡
*/
Button('show Toast')
.onClick(() => {
let promptAction: PromptAction = this.ctx.getPromptAction();
try {
promptAction.showToast({
message: 'Message Info',
duration: 2000
});
} catch (error) {
let message = (error as BusinessError).message;
let code = (error as BusinessError).code;
console.error(`showToast args error code is ${code}, message is ${message}`);
};
})
/**
* 系统弹框
*/
Button('show Toast')
.onClick(() => {
let promptAction: PromptAction = this.ctx.getPromptAction();
try {
promptAction.showToast({
message: 'Message Info',
duration: 2000
});
} catch (error) {
let message = (error as BusinessError).message;
let code = (error as BusinessError).code;
console.error(`showToast args error code is ${code}, message is ${message}`);
};
})
}
.width('100%')
.height('100%')
}
.width('100%')
.height('100%')
}
}