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

目录

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

弹出气泡

创建ComponentContent

绑定组件信息

设置弹出气泡样式

更新气泡样式

关闭气泡

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

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

弹出菜单

创建ComponentContent

绑定组件信息

设置弹出菜单样式

更新菜单样式

关闭菜单

在HAR包中使用全局菜单

Toast

使用建议

即时反馈模式对比

创建即时反馈

显示关闭即时反馈


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

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

弹出气泡

通过openPopup可以弹出气泡。

TypeScript 复制代码
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)是自定义组件的入参,可以缺省,也可以传入基础数据类型。

TypeScript 复制代码
private contentNode: ComponentContent<Object> = new ComponentContent(uiContext, wrapBuilder(buildText), this.message);

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

TypeScript 复制代码
@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,则无法弹出气泡。

TypeScript 复制代码
let frameNode: FrameNode | null = this.ctx.getFrameNodeByUniqueId(this.getUniqueId());
let targetId = frameNode?.getChild(0)?.getUniqueId();

设置弹出气泡样式

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

TypeScript 复制代码
private options: PopupCommonOptions = { enableArrow: true };

更新气泡样式

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

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

关闭气泡

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

TypeScript 复制代码
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,从而对外提供气泡的弹出、更新和关闭能力。

TypeScript 复制代码
// 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);
        })
    }
  }
}
TypeScript 复制代码
// 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可以弹出菜单。

TypeScript 复制代码
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)是自定义组件的入参,可以缺省,也可以传入基础数据类型。

TypeScript 复制代码
private contentNode: ComponentContent<Object> = new ComponentContent(uiContext, wrapBuilder(buildText), this.message);

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

TypeScript 复制代码
@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,则无法弹出菜单。

TypeScript 复制代码
let frameNode: FrameNode | null = this.ctx.getFrameNodeByUniqueId(this.getUniqueId());
let targetId = frameNode?.getChild(0)?.getUniqueId();

设置弹出菜单样式

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

TypeScript 复制代码
private options: MenuOptions = { enableArrow: true, placement: Placement.Bottom };

更新菜单样式

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

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

关闭菜单

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

TypeScript 复制代码
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对齐 以宿主窗口为主窗中布局,对齐方式与宿主窗口对齐
TypeScript 复制代码
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
        });
      })
    }
  }
}

创建即时反馈

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

TypeScript 复制代码
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)
  }
}

显示关闭即时反馈

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

TypeScript 复制代码
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)
  }
}
相关推荐
啃火龙果的兔子1 小时前
修改 Lucide-React 图标样式的方法
前端·react.js·前端框架
前端 贾公子1 小时前
为何在 Vue 的 v-model 指令中不能使用可选链(Optional Chaining)?
前端·javascript·vue.js
潘多拉的面1 小时前
Vue的ubus emit/on使用
前端·javascript·vue.js
遗憾随她而去.1 小时前
js面试题 高频(1-11题)
开发语言·前端·javascript
hqxstudying4 小时前
J2EE模式---前端控制器模式
java·前端·设计模式·java-ee·状态模式·代码规范·前端控制器模式
开开心心就好5 小时前
Excel数据合并工具:零门槛快速整理
运维·服务器·前端·智能手机·pdf·bash·excel
im_AMBER6 小时前
Web开发 05
前端·javascript·react.js
Au_ust6 小时前
HTML整理
前端·javascript·html
安心不心安6 小时前
npm全局安装后,依然不是内部或外部命令,也不是可运行的程序或批处理文件
前端·npm·node.js