28、鸿蒙Harmony Next开发:不依赖UI组件的全局气泡提示 (openPopup)和不依赖UI组件的全局菜单 (openMenu)、Toast

发布于:2025-07-19 ⋅ 阅读:(10) ⋅ 点赞:(0)

目录

不依赖UI组件的全局气泡提示 (openPopup)

弹出气泡

创建ComponentContent

绑定组件信息

设置弹出气泡样式

更新气泡样式

关闭气泡

在HAR包中使用全局气泡提示

不依赖UI组件的全局菜单 (openMenu)

弹出菜单

创建ComponentContent

绑定组件信息

设置弹出菜单样式

更新菜单样式

关闭菜单

在HAR包中使用全局菜单

Toast

使用建议

即时反馈模式对比

创建即时反馈

显示关闭即时反馈


不依赖UI组件的全局气泡提示 (openPopup)

气泡提示(Popup)在使用时依赖绑定UI组件,否则无法使用。从API version 18开始,可以通过使用全局接口openPopup的方式,在无UI组件的场景下直接或封装使用,例如在事件回调中使用或封装后对外提供能力。

弹出气泡

通过openPopup可以弹出气泡。

promptAction.openPopup(contentNode, { id: targetId }, {
  enableArrow: true
})
  .then(() => {
    console.info('openPopup success');
  })
  .catch((err: BusinessError) => {
    console.error('openPopup error: ' + err.code + ' ' + err.message);
  })

创建ComponentContent

通过调用openPopup接口弹出其气泡,需要提供用于定义自定义弹出框的内容ComponentContent。其中,wrapBuilder(buildText)封装自定义组件,new Params(this.message)是自定义组件的入参,可以缺省,也可以传入基础数据类型。

private contentNode: ComponentContent<Object> = new ComponentContent(uiContext, wrapBuilder(buildText), this.message);

如果在wrapBuilder中包含其他组件(例如:PopupChip组件),则ComponentContent应采用带有四个参数的构造函数constructor,其中options参数应传递{ nestingBuilderSupported: true }。

@Builder
export function buildText(params: Params) {
  Popup({
    // 类型设置图标内容
    icon: {
      image: $r('app.media.app_icon'),
      width: 32,
      height: 32,
      fillColor: Color.White,
      borderRadius: 10
    } as PopupIconOptions,
    // 设置文字内容
    title: {
      text: `This is a Popup title 1`,
      fontSize: 20,
      fontColor: Color.Black,
      fontWeight: FontWeight.Normal
    } as PopupTextOptions,
    // 设置文字内容
    message: {
      text: `This is a Popup message 1`,
      fontSize: 15,
      fontColor: Color.Black
    } as PopupTextOptions,
    // 设置按钮内容
    buttons: [{
      text: 'confirm',
      action: () => {
        console.info('confirm button click');
      },
      fontSize: 15,
      fontColor: Color.Black,
    },
      {
        text: 'cancel',
        action: () => {
          console.info('cancel button click');
        },
        fontSize: 15,
        fontColor: Color.Black
      },] as [PopupButtonOptions?, PopupButtonOptions?]
  })
}

let contentNode: ComponentContent<Object> = new ComponentContent(uiContext, wrapBuilder(buildText), this.message, { nestingBuilderSupported: true });

绑定组件信息

通过调用openPopup接口弹出气泡,需要提供绑定组件的信息TargetInfo。若未传入有效的target,则无法弹出气泡。

let frameNode: FrameNode | null = this.ctx.getFrameNodeByUniqueId(this.getUniqueId());
let targetId = frameNode?.getChild(0)?.getUniqueId();

设置弹出气泡样式

通过调用openPopup接口弹出气泡,可以设置PopupCommonOptions属性调整气泡样式。

private options: PopupCommonOptions = { enableArrow: true };

更新气泡样式

通过updatePopup可以更新气泡的样式。支持全量更新和增量更新其气泡样式,不支持更新showInSubWindow、focusable、onStateChange、onWillDismiss和transition。

promptAction.updatePopup(contentNode, {
  enableArrow: false
}, true)
  .then(() => {
    console.info('updatePopup success');
  })
  .catch((err: BusinessError) => {
    console.error('updatePopup error: ' + err.code + ' ' + err.message);
  })

关闭气泡

通过调用closePopup可以关闭气泡。

promptAction.closePopup(contentNode)
  .then(() => {
    console.info('closePopup success');
  })
  .catch((err: BusinessError) => {
    console.error('closePopup error: ' + err.code + ' ' + err.message);
  })

由于updatePopupclosePopup依赖content来更新或者关闭指定的气泡,开发者需自行维护传入的content。

在HAR包中使用全局气泡提示

以下示例通过HAR包封装一个Popup,从而对外提供气泡的弹出、更新和关闭能力。

// library/src/main/ets/components/MainPage.ets

import { BusinessError } from '@kit.BasicServicesKit';
import { ComponentContent, TargetInfo, PromptAction } from '@kit.ArkUI';

export class PromptActionClass {
  private promptAction: PromptAction | null = null;
  private contentNode: ComponentContent<Object> | null = null;
  private options: PopupCommonOptions | null = null;
  private target: TargetInfo | null = null;
  private isPartialUpdate: boolean = false;

  public setPromptAction(promptAction: PromptAction) {
    this.promptAction = promptAction;
  }

  public setContentNode(node: ComponentContent<Object>) {
    this.contentNode = node;
  }

  public setTarget(target: TargetInfo) {
    this.target = target;
  }

  public setOptions(options: PopupCommonOptions) {
    this.options = options;
  }

  public setIsPartialUpdate(isPartialUpdate: boolean) {
    this.isPartialUpdate = isPartialUpdate;
  }

  public openPopup() {
    if (this.promptAction != null) {
      this.promptAction.openPopup(this.contentNode, this.target, this.options)
        .then(() => {
          console.info('openPopup success');
        })
        .catch((err: BusinessError) => {
          console.error('openPopup error: ' + err.code + ' ' + err.message);
        })
    }
  }

  public closePopup() {
    if (this.promptAction != null) {
      this.promptAction.closePopup(this.contentNode)
        .then(() => {
          console.info('closePopup success');
        })
        .catch((err: BusinessError) => {
          console.error('closePopup error: ' + err.code + ' ' + err.message);
        })
    }
  }

  public updatePopup(options: PopupCommonOptions) {
    if (this.promptAction != null) {
      this.promptAction.updatePopup(this.contentNode, options, this.isPartialUpdate)
        .then(() => {
          console.info('updatePopup success');
        })
        .catch((err: BusinessError) => {
          console.error('updatePopup error: ' + err.code + ' ' + err.message);
        })
    }
  }
}
// entry/src/main/ets/pages/Index.ets

import { PromptActionClass } from "library";
import { ComponentContent, PromptAction } from '@kit.ArkUI';

class Params {
  text: string = "";
  promptActionClass: PromptActionClass = new PromptActionClass();

  constructor(text: string, promptActionClass: PromptActionClass) {
    this.text = text;
    this.promptActionClass = promptActionClass;
  }
}

@Builder
function buildText(params: Params) {
  Column() {
    Text(params.text)
      .fontSize(20)
      .margin({ top: 10 })
    Button('Update')
      .margin({ top: 10 })
      .width(100)
      .onClick(() => {
        params.promptActionClass.updatePopup({
          enableArrow: false,
        });
      })
    Button('Close')
      .margin({ top: 10 })
      .width(100)
      .onClick(() => {
        params.promptActionClass.closePopup();
      })
  }.width(130).height(150)
}

@Entry
@Component
struct Index {
  @State message: string = "hello";
  private uiContext: UIContext = this.getUIContext();
  private promptAction: PromptAction = this.uiContext.getPromptAction();
  private promptActionClass: PromptActionClass = new PromptActionClass();
  private targetId: number = 0;
  private contentNode: ComponentContent<Object> =
    new ComponentContent(this.uiContext, wrapBuilder(buildText), new Params(this.message, this.promptActionClass));
  private options: PopupCommonOptions = { enableArrow: true };

  build() {
    Column() {
      Button("openPopup")
        .margin({ top: 50, left: 100 })
        .onClick(() => {
          let frameNode: FrameNode | null = this.uiContext.getFrameNodeByUniqueId(this.getUniqueId());
          let targetId = frameNode?.getChild(0)?.getUniqueId();
          if (targetId == undefined) {
            this.targetId = 0;
          } else {
            this.targetId = targetId;
          }
          this.promptActionClass.setPromptAction(this.promptAction);
          this.promptActionClass.setContentNode(this.contentNode);
          this.promptActionClass.setOptions(this.options);
          this.promptActionClass.setIsPartialUpdate(false);
          this.promptActionClass.setTarget({ id: this.targetId });
          this.promptActionClass.openPopup();
        })
    }
  }
}

 

不依赖UI组件的全局菜单 (openMenu)

菜单控制 (Menu)在使用时依赖绑定UI组件,否则无法使用。从API version 18开始,可以通过使用全局接口openMenu的方式,在无UI组件的场景下直接或封装使用,例如在事件回调中使用或封装后对外提供能力。

弹出菜单

通过openMenu可以弹出菜单。

promptAction.openMenu(contentNode, { id: targetId }, {
  enableArrow: true
})
  .then(() => {
    console.info('openMenu success');
  })
  .catch((err: BusinessError) => {
    console.error('openMenu error: ' + err.code + ' ' + err.message);
  })

创建ComponentContent

通过调用openMenu接口弹出菜单,需要提供用于定义自定义弹出框的内容ComponentContent。其中,wrapBuilder(buildText)封装自定义组件,new Params(this.message)是自定义组件的入参,可以缺省,也可以传入基础数据类型。

private contentNode: ComponentContent<Object> = new ComponentContent(uiContext, wrapBuilder(buildText), this.message);

如果在wrapBuilder中包含其他组件(例如:PopupChip组件),则ComponentContent应采用带有四个参数的构造函数constructor,其中options参数应传递{ nestingBuilderSupported: true }。

@Builder
export function buildText(params: Params) {
  Menu({
    // 类型设置图标内容
    icon: {
      image: $r('app.media.app_icon'),
      width: 32,
      height: 32,
      fillColor: Color.White,
      borderRadius: 10
    } as MenuIconOptions,
    // 设置文字内容
    title: {
      text: `This is a Menu title 1`,
      fontSize: 20,
      fontColor: Color.Black,
      fontWeight: FontWeight.Normal
    } as MenuTextOptions,
    // 设置文字内容
    message: {
      text: `This is a Menu message 1`,
      fontSize: 15,
      fontColor: Color.Black
    } as MenuTextOptions,
    // 设置按钮内容
    buttons: [{
      text: 'confirm',
      action: () => {
        console.info('confirm button click');
      },
      fontSize: 15,
      fontColor: Color.Black,
    },
      {
        text: 'cancel',
        action: () => {
          console.info('cancel button click');
        },
        fontSize: 15,
        fontColor: Color.Black
      },] as [MenuButtonOptions?, MenuButtonOptions?]
  })
}

let contentNode: ComponentContent<Object> = new ComponentContent(uiContext, wrapBuilder(buildText), this.message, { nestingBuilderSupported: true });

绑定组件信息

通过调用openMenu接口弹出菜单,需要提供绑定组件的信息TargetInfo。若未传入有效的target,则无法弹出菜单。

let frameNode: FrameNode | null = this.ctx.getFrameNodeByUniqueId(this.getUniqueId());
let targetId = frameNode?.getChild(0)?.getUniqueId();

设置弹出菜单样式

通过调用openMenu接口弹出菜单,可以设置MenuOptions属性调整菜单样式。title属性不生效。preview参数仅支持设置MenuPreviewMode类型。

private options: MenuOptions = { enableArrow: true, placement: Placement.Bottom };

更新菜单样式

通过updateMenu可以更新菜单的样式。支持全量更新和增量更新其菜单样式,不支持更新showInSubWindow、preview、previewAnimationOptions、transition、onAppear、aboutToAppear、onDisappear和aboutToDisappear。

promptAction.updateMenu(contentNode, {
  enableArrow: false
}, true)
  .then(() => {
    console.info('updateMenu success');
  })
  .catch((err: BusinessError) => {
    console.error('updateMenu error: ' + err.code + ' ' + err.message);
  })

关闭菜单

通过调用closeMenu可以关闭菜单。

promptAction.closeMenu(contentNode)
  .then(() => {
    console.info('openMenu success');
  })
 .catch((err: BusinessError) => {
   console.error('openMenu error: ' + err.code + ' ' + err.message);
 })

由于updateMenucloseMenu依赖content来更新或者关闭指定的菜单,开发者需自行维护传入的content。

在HAR包中使用全局菜单

可以通过HAR包封装一个Menu,从而对外提供菜单的弹出、更新和关闭能力。

Toast

即时反馈(Toast)是一种临时性的消息提示框,用于向用户显示简短的操作反馈或状态信息。​它通常在屏幕的底部或顶部短暂弹出,随后在一段时间后自动消失。即时反馈的主要目的是提供简洁、不打扰的信息反馈,避免干扰用户当前的操作流程。

可以通过使用UIContext中的getPromptAction方法获取当前UI上下文关联的PromptAction对象,再通过该对象调用showToast创建并显示文本提示框。

为了安全考虑,例如Toast恶意遮挡其他页面,Toast只能显示在当前的UI实例中,应用退出后,不会单独显示在桌面上。

使用建议

  • 合理使用弹出场景,避免过度提醒用户。
  • 可以针对以下常用场景使用即时反馈操作,例如,当用户执行某个操作时及时结果反馈,用来提示用户操作是否成功或失败;或是当应用程序的状态发生变化时提供状态更新等。
  • 注意文本的信息密度,即时反馈展示时间有限,应当避免长文本的出现。
  • Toast控件的文本应该清晰可读,字体大小和颜色应该与应用程序的主题相符。除此之外,即时反馈控件本身不应该包含任何可交互的元素,如按钮或链接。
  • 杜绝强制占位和密集弹出的提示。
  • 即时反馈作为应用内的轻量通知,应当避免内容布局占用界面内的其他元素信息,如遮盖弹出框的展示内容,从而迷惑用户弹出的内容是否属于弹出框。再或者频繁性的弹出信息内容,且每次弹出之间无时间间隔,影响用户的正常使用。也不要在短时间内频繁弹出新的即时反馈替代上一个。即时反馈的单次显示时长不要超过 3 秒钟,避免影响用户正常的行为操作。
  • 遵从系统默认弹出位置。
  • 即时反馈在系统中默认从界面底部弹出,距离底部有一定的安全间距,作为系统性的应用内提示反馈,请遵守系统默认效果,避免与其他弹出类组件内容重叠。特殊场景下可对内容布局进行规避。

即时反馈模式对比

即时反馈提供了两种显示模式,分别为DEFAULT(显示在应用内)、TOP_MOST(显示在应用之上)。

在TOP_MOST类型的Toast显示前,会创建一个全屏大小的子窗(手机上子窗大小和主窗大小一致),然后在该子窗上计算Toast的布局位置,最后显示在该子窗上。具体和DEFAULT模式Toast的差异如下:

差异点 DEFAULT TOP_MOST
是否创建子窗
层级 显示在主窗内,层级和主窗一致,一般比较低 显示在子窗中,一般比主窗层级高,比其他弹窗类组件层级高,比软键盘和权限弹窗层级低
是否避让软键盘 软键盘抬起时,必定上移软键盘的高度 软键盘抬起时,只有toast被遮挡时,才会避让,且避让后toast底部距离软键盘高度为80vp
UIExtension内布局 以UIExtension为主窗中布局,对齐方式与UIExtension对齐 以宿主窗口为主窗中布局,对齐方式与宿主窗口对齐
import { promptAction } from '@kit.ArkUI';

@Entry
@Component
struct Index {
  build() {
    Column({ space: 10 }) {
      TextInput()
      Button() {
        Text("DEFAULT类型Toast")
          .fontSize(20)
          .fontWeight(FontWeight.Bold)

      }
      .width('100%')
      .onClick(() => {
        this.getUIContext().getPromptAction().showToast({
          message: "ok,我是DEFAULT toast",
          duration: 2000,
          showMode: promptAction.ToastShowMode.DEFAULT,
          bottom: 80
        });
      })

      Button() {
        Text("TOPMOST类型Toast")
          .fontSize(20)
          .fontWeight(FontWeight.Bold)

      }
      .width('100%')
      .onClick(() => {
        this.getUIContext().getPromptAction().showToast({
          message: "ok,我是TOP_MOST toast",
          duration: 2000,
          showMode: promptAction.ToastShowMode.TOP_MOST,
          bottom: 85
        });
      })
    }
  }
}

创建即时反馈

适用于短时间内提示框自动消失的场景

import { PromptAction } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct toastExample {
  private uiContext: UIContext = this.getUIContext();
  private promptAction: PromptAction = this.uiContext.getPromptAction();

  build() {
    Column() {
      Button('Show toast').fontSize(20)
        .onClick(() => {
          try {
            this.promptAction.showToast({
              message: 'Hello World',
              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}`);
          };
        })
    }.height('100%').width('100%').justifyContent(FlexAlign.Center)
  }
}

显示关闭即时反馈

适用于提示框提留时间较长,用户操作可以提前关闭提示框的场景。

import { LengthMetrics, PromptAction } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct toastExample {
  @State toastId: number = 0;
  private uiContext: UIContext = this.getUIContext();
  private promptAction: PromptAction = this.uiContext.getPromptAction();

  build() {
    Column() {
      Button('Open Toast')
        .type(ButtonType.Capsule)
        .height(100)
        .onClick(() => {
          try {
            this.promptAction.openToast({
              message: 'Toast Massage',
              duration: 10000,
            }).then((toastId: number) => {
              this.toastId = toastId;
            });
          } catch (error) {
            let message = (error as BusinessError).message;
            let code = (error as BusinessError).code;
            console.error(`OpenToast error code is ${code}, message is ${message}`);
          };
        })
      Blank().height(50);
      Button('Close Toast')
        .height(100)
        .type(ButtonType.Capsule)
        .onClick(() => {
          try {
            this.promptAction.closeToast(this.toastId);
          } catch (error) {
            let message = (error as BusinessError).message;
            let code = (error as BusinessError).code;
            console.error(`CloseToast error code is ${code}, message is ${message}`);
          };
        })
    }.height('100%').width('100%').justifyContent(FlexAlign.Center)
  }
}


网站公告

今日签到

点亮在社区的每一天
去签到