【HarmonyOS Next】鸿蒙应用弹框和提示气泡详解(一)
- 2025-03-20 上海
本文字数:5259 字
阅读完需:约 17 分钟

【HarmonyOS Next】鸿蒙应用弹框和提示气泡详解(一)
一、前言
在应用开发中,弹框 Dialog 和提示气泡 Toast 使用频繁,移动开发同学较为熟悉。但在鸿蒙响应式布局里,早期的弹框 Dialog 和提示气泡 Toast 因与 UI 紧密绑定,在纯逻辑类文件中无法使用。
后来 API 迭代对其升级,如今可在纯逻辑类中使用,实现与 UI 解耦。
迭代优化过程
从 page 界面 UI 上弹出 => 挂靠子窗口实现弹出 => UI 框架层预留挂靠节点
由此可见,与 UI 强绑定的实现方式 API 已不推荐。

二、鸿蒙中的弹框使用
目前鸿蒙 HarmonyOS 对于弹框、提示气泡及相关延申组件(浮层,Popup,Menu, bindSheet, bindContentCover),都是挂靠到 UI 框架预留节点进行添加渲染。
1. 弹框的实现
弹框有两种方式:
(1) 系统定制弹框,可直接使用
系统定制弹框依业务复杂度有两种封装方式:基础弹框(警告弹框,列表弹窗)


@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)
@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) 自定义弹框
创建节点对象并添加自定义弹框布局:
@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 弹出自定义弹框:
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:
this.getContext().getPromptAction().openCustomDialog(PromptActionClass.contentNode,{ alignment: DialogAlignment.Top, offset: { dx: 0, dy: 50 } })
.then(() => {
console.info('OpenCustomDialog complete.')
})
2. 气泡的实现
气泡有对齐方式和悬浮态设置,但不能设置字体大小和颜色。自定义字体大小和颜色需用自定义弹框或 Popup。
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)
}
}
三、源码示例
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%')
}
}
版权声明: 本文为 InfoQ 作者【GeorgeGcs】的原创文章。
原文链接:【http://xie.infoq.cn/article/b24ff23238751e9ed012e3260】。文章转载请联系作者。

GeorgeGcs
路漫漫其修远兮,吾将上下而求索。 2024-12-24 加入
历经腾讯,宝马,研究所,金融。 待过私企,外企,央企。 深耕大应用开发领域十年。 OpenHarmony,HarmonyOS,Flutter,H5,Android,IOS。 目前任职鸿蒙应用架构师。 HarmonyOS官方认证创作先锋
评论