1、概 述
模态(Modal)指的是UI组件或视图的一种状态。在模态组件消失前,用户只能对处于模态的组件或视图进行响应,不能操作其他非模态的组件或视图。
ArkUI中可通过使用AlertDialog、CustomDialog、ActionSheet、Popup、Menu、ContextMenu等组件实现模态类弹窗能力。
-
AlertDialog
通常用来展示用户当前需要或必须关注的信息或操作。如用户操作一个敏感行为时响应一个二次确认的弹窗。
-
ActionSheet
当需要用户关注或确认的信息存在列表选择时使用。
-
CustomDialog
当用户需要自定义弹窗内的组件和内容时使用。
-
Popup
用于为指定的组件做信息提示。如点击一个问号图标弹出一段气泡提示。
-
Menu/ContextMenu
用于给指定的组件绑定用户可执行的操作,如长按图标展示操作选项等。
|
2、全局弹窗
全局弹窗不与任何组件绑定,一般用于针对用户触发的操作进行必要提示时使用。ArkUI当前提供了定制和自定义两类弹窗组件。
-
定制:AlertDialog、ActionSheet、promptAction.showDialog、promptAction.showActionMenu。使用此类组件需要指定显示的文本内容和按钮操作即可完成简单的交互效果。
-
自定义:CustomDialog、promptAction.openCustomDialog。需要根据场景传入自定义组件填充在弹窗中实现自定义的弹窗内容。
下面以AlertDialog、ActionSheet 和 CustomDialog为例说明相应的弹窗效果与使用方法。
2.1、AlertDialog
AlertDialog: 警告弹窗,需要向用户提问或得到用户的许可。
-
警告弹窗用来提示重要信息,但会中断当前任务,尽量提供必要的信息和有用的操作。
-
避免仅使用警告弹窗提供信息,用户不喜欢被信息丰富但不可操作的警告打断。
-
必选内容包含:标题、可选信息文本、最多3个按钮。
-
可选内容包含:输入框、icon、checkBox和HelpButton。
@Entry
@Component
struct AlertDialogExample {
build() {
Column({ space: 5 }) {
Button('two button dialog')
.onClick(() => {
AlertDialog.show(
{
title: 'title',
subtitle: 'subtitle',
message: 'text',
autoCancel: true,
alignment: DialogAlignment.Bottom,
gridCount: 4,
offset: { dx: 0, dy: -20 },
primaryButton: {
value: 'cancel',
action: () => {
console.info('Callback when the first button is clicked')
}
},
secondaryButton: {
enabled: true,
defaultFocus: true,
style: DialogButtonStyle.HIGHLIGHT,
value: 'ok',
action: () => {
console.info('Callback when the second button is clicked')
}
}
}
)
}).backgroundColor(0x317aff)
}.width('100%').margin({ top: 5 })
}
}
2.2、ActionSheet
ActionSheet: 列表选择弹窗。
-
适合展示多个操作项,尤其是除了操作列表以外没有其他的展示内容。
@Entry
@Component
struct ActionSheetExample {
build() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
Button('Click to Show ActionSheet')
.onClick(() => {
ActionSheet.show({
title: 'ActionSheet title',
subtitle: 'ActionSheet subtitle',
message: 'message',
autoCancel: true,
confirm: {
defaultFocus: true,
value: 'Confirm button',
action: () => {
console.log('Get Alert Dialog handled')
}
},
alignment: DialogAlignment.Bottom,
offset: { dx: 0, dy: -10 },
sheets: [
{
title: 'apples',
action: () => {
console.log('apples')
}
},
{
title: 'bananas',
action: () => {
console.log('bananas')
}
},
{
title: 'pears',
action: () => {
console.log('pears')
}
}
]
})
})
}.width('100%')
.height('100%')
}
}
2.3、CustomDialog
当我们需要自定义弹窗的内容和样式时,可选择CustomDialog。【更建议使用promptAction.openCustomDialog】
// xxx.ets
@CustomDialog
@Component
struct CustomDialogExample {
@Link textValue: string
@Link inputValue: string
controller?: CustomDialogController
cancel: () => void = () => {
}
confirm: () => void = () => {
}
build() {
Column() {
Text('Change text').fontSize(20).margin({ top: 10, bottom: 10 })
TextInput({ placeholder: '', text: this.textValue }).height(60).width('90%')
.onChange((value: string) => {
this.textValue = value
})
Text('Whether to change a text?').fontSize(16).margin({ bottom: 10 })
Flex({ justifyContent: FlexAlign.SpaceAround }) {
Button('cancel')
.onClick(() => {
if (this.controller != undefined) {
this.controller.close()
this.cancel()
}
}).backgroundColor(0xffffff).fontColor(Color.Black)
Button('confirm')
.onClick(() => {
if (this.controller != undefined) {
this.inputValue = this.textValue
this.controller.close()
this.confirm()
}
}).backgroundColor(0xffffff).fontColor(Color.Red)
}.margin({ bottom: 10 })
}.borderRadius(10)
}
}
@Entry
@Component
struct CustomDialogUser {
@State textValue: string = ''
@State inputValue: string = 'click me'
dialogController: CustomDialogController | null = new CustomDialogController({
builder: CustomDialogExample({
cancel: () => {
this.onCancel()
},
confirm: () => {
this.onAccept()
},
textValue: $textValue,
inputValue: $inputValue
}),
cancel: this.exitApp,
autoCancel: true,
onWillDismiss: (dismissDialogAction: DismissDialogAction) => {
console.info("reason=" + JSON.stringify(dismissDialogAction.reason))
console.log("dialog onWillDismiss")
if (dismissDialogAction.reason == DismissReason.PRESS_BACK) {
dismissDialogAction.dismiss()
}
if (dismissDialogAction.reason == DismissReason.TOUCH_OUTSIDE) {
dismissDialogAction.dismiss()
}
},
alignment: DialogAlignment.Bottom,
offset: { dx: 0, dy: -20 },
gridCount: 4,
customStyle: false,
cornerRadius: 10,
})
// 在自定义组件即将析构销毁时将dialogController置空
aboutToDisappear() {
this.dialogController = null // 将dialogController置空
}
onCancel() {
console.info('Callback when the first button is clicked')
}
onAccept() {
console.info('Callback when the second button is clicked')
}
exitApp() {
console.info('Click the callback in the blank area')
}
build() {
Column() {
Button(this.inputValue)
.onClick(() => {
if (this.dialogController != null) {
this.dialogController.open()
}
}).backgroundColor(0x317aff)
}.width('100%').margin({ top: 5 })
}
}
3、交互辅助弹窗
3.1、气泡Popup
当点击目标组件或交互区时,弹出内容在其他内容之上,可使用Popup来指示当前功能如何操作。
@Entry
@Component
struct PopupExample {
@State handlePopup: boolean = false
build() {
Flex({ direction: FlexDirection.Column }) {
// PopupOptions 类型设置弹框内容
Button('PopupOptions')
.onClick(() => {
this.handlePopup = !this.handlePopup
})
.bindPopup(this.handlePopup, {
message: 'This is a popup with PopupOptions',
placementOnTop: true,
showInSubWindow: false,
primaryButton: {
value: 'confirm',
action: () => {
this.handlePopup = !this.handlePopup
console.info('confirm Button click')
}
},
// 第二个按钮
secondaryButton: {
value: 'cancel',
action: () => {
this.handlePopup = !this.handlePopup
console.info('cancel Button click')
}
},
onStateChange: (e) => {
console.info(JSON.stringify(e.isVisible))
if (!e.isVisible) {
this.handlePopup = false
}
}
})
.position({ x: 100, y: 150 })
}.width('100%').padding({ top: 5 })
}
}
3.2、菜单Menu
很多时候需要通过交互弹出一些菜单选项,用于用户操作。此时可通过Menu和MenuItem组件组合成需要弹出的菜单选项内容,然后借助bindMenu和bindContextMenu方法将菜单和组件绑定。他们的主要区别如下:
-
bindMenu:无需预览图场景,需要在非子窗场景显示。
-
bindContextMenu: 需要预览图场景使用,只能在子窗中显示。
👉🏻 bindMenu
一种临时性弹出组件,用于展示用户可执行的操作。
⭐️ 一级Menu样式如下:
⭐️ 多级Menu样式如下:
代码示例如下:
@Entry
@Component
struct Index {
@State select: boolean = true
@Builder
MyMenu() {
Menu() {
MenuItem({ content: "菜单选项" })
MenuItem({ content: "菜单选项" })
MenuItem({ content: "菜单选项" })
MenuItem({ content: "菜单选项" })
}
}
build() {
Row() {
Column() {
Text('click to show menu')
.fontSize(50)
.fontWeight(FontWeight.Bold)
}
.bindMenu(this.MyMenu)
.width('100%')
}
.height('100%')
}
}
👉🏻 bindContextMenu
内容包括菜单、预览图、蒙层(通常在长按桌面图标时使用)
⭐️ 相对父组件区域弹出样式如下:
⭐️ 相对右键点击位置弹出样式如下:
代码示例如下:
@Entry
@Component
struct Index {
@State select: boolean = true
@Builder
MyMenu(){
Menu() {
MenuItem({ content: "菜单选项" })
MenuItem({ content: "菜单选项" })
MenuItem({ content: "菜单选项" })
MenuItem({ content: "菜单选项" })
}
}
build() {
Row() {
Column() {
Text('click to show menu')
.fontSize(50)
.fontWeight(FontWeight.Bold)
}
.bindContextMenu(this.MyMenu, ResponseType.LongPress,{
placement: Placement.Left,
preview: MenuPreviewMode.IMAGE
})
.width('100%')
}
.height('100%')
}
}
4、类PC场景
在2in1设备上,使用模态类弹窗时,会出现超出主窗口显示的场景,如下图所示。
我们可通过支持子窗口和默认子窗口实现超出应用界面效果。
4.1、支持子窗口
自定义弹窗(CustomDialog)、警告弹窗(AlertDialog)、列表选择弹窗(ActionSheet)、气泡提示(Popup)可通过showInSubWindow设置弹窗或气泡在子窗口中,从而实现超出主窗口的显示效果。示例代码如下(37行):
// xxx.ets
@CustomDialog
struct CustomDialogExample {
controller?: CustomDialogController
cancel: () => void = () => {
}
confirm: () => void = () => {
}
build() {
Column() {
Text('可展示在主窗口外的弹窗')
.fontSize(30)
.height(100)
Button('点我关闭弹窗')
.onClick(() => {
if (this.controller != undefined) {
this.controller.close()
}
})
.margin(20)
}
}
}
@Entry
@Component
struct CustomDialogUser {
dialogController: CustomDialogController | null = new CustomDialogController({
builder: CustomDialogExample({
cancel: ()=> { this.onCancel() },
confirm: ()=> { this.onAccept() }
}),
cancel: this.existApp,
autoCancel: true,
alignment: DialogAlignment.Center,
offset: { dx: 0, dy: -20 },
gridCount: 4,
showInSubWindow: true,
isModal: true,
customStyle: false,
cornerRadius: 10,
})
// 在自定义组件即将析构销毁时将dialogController置空
aboutToDisappear() {
this.dialogController = null // 将dialogController置空
}
onCancel() {
console.info('Callback when the first button is clicked')
}
onAccept() {
console.info('Callback when the second button is clicked')
}
existApp() {
console.info('Click the callback in the blank area')
}
build() {
Column() {
Button('click me')
.onClick(() => {
if (this.dialogController != null) {
this.dialogController.open()
}
}).backgroundColor(0x317aff)
}.width('100%').margin({ top: 5 })
}
}
4.2、默认子窗口
可使用 bindContextMenu为组件绑定菜单,触发方式为长按或者右键点击,弹出菜单项需要自定义。
@Entry
@Component
struct ContextMenuExample {
@Builder MenuBuilder() {
Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Text('Test menu item 1')
.fontSize(20)
.width(100)
.height(50)
.textAlign(TextAlign.Center)
Divider().height(10)
Text('Test menu item 2')
.fontSize(20)
.width(100)
.height(50)
.textAlign(TextAlign.Center)
}.width(100)
}
build() {
Column() {
Text('LongPress for menu')
}
.width('100%')
.margin({ top: 5 })
.bindContextMenu(this.MenuBuilder, ResponseType.LongPress)
}
}