谁能告诉我一个HarmonyOS鸿蒙Next简单好用的对话框组件!

谁能告诉我一个HarmonyOS鸿蒙Next简单好用的对话框组件!

要求:有“关闭”或“确定”按钮的对话框。支持代码主动关闭它(不是让用户触摸屏幕)。并且是系统SDK中提供的要简单好用!

就像iOS的UIAlertView一样简单好用!

14 回复

【背景知识】

  • 自定义弹窗在HarmonyOS中是一种灵活的用户交互方式,用于显示临时信息或获取用户输入。
  • setInterval是一个常用的JavaScript函数,用于按照指定的周期(以毫秒计)来执行注册的回调函数。这个函数在开发中非常有用,尤其是在需要定期执行某些操作的场景中。
  • aboutToAppear函数在创建自定义组件的新实例后,在执行其build()函数之前执行。允许在aboutToAppear函数中改变状态变量,更改将在后续执行build()函数中生效。

【解决方案】
用户在非触摸屏幕的场景下关闭自定义弹窗的一种实现方案是借助setTimeout或者setInterval之类的功能,本方案的实现思路是在aboutToAppear生命周期中调用setInterval方法实现一个倒计时功能,在倒计时结束后调用关闭弹窗的方法并清理interval任务。
具体demo如下:

@CustomDialog
@Component
struct CustomDialogExample {
  @State timeout: number = 10;
  @State interval: number = 0;
  controller?: CustomDialogController;
  cancel: () => void = () => {}
  confirm: () => void = () => {}

  // 生命周期中通过setInterval方式对关闭按钮进行倒计时
  aboutToAppear(): void {
    this.interval = setInterval(() => {
      this.timeout -= 1;
      if (this.timeout <= 0) { // 倒计时结束,调用关闭弹窗方法并取消interval任务
        this.closeDialog();
      }
      console.info(`interval timeout = ${this.timeout}`);
    }, 1000)
  }

  closeDialog() {
    clearInterval(this.interval); //取消interval任务
    this.controller?.close(); // 关闭自定义弹窗
    console.info(`closeDialog success and interval = ${this.interval}`);
  }

  build() {
    Column() {
      Text('这是自定义弹窗')
        .fontSize(30)
        .height(100)
      Button('点我关闭弹窗' + this.timeout)
        .onClick(() => {
          if (this.controller != undefined) {
            this.controller.close();
            clearInterval(this.interval); // 手动关闭,取消interval任务
          }
        })
        .margin(20)
    }
  }
}

@Entry
@Component
struct CustomDialogUser {
  dialogController: CustomDialogController | null = new CustomDialogController({
    builder: CustomDialogExample({
      cancel: () => { this.onCancel(); },
      confirm: () => { this.onAccept(); },
      timeout: 20
    }),
    cancel: this.existApp,
    autoCancel: true,
    onWillDismiss: (dismissDialogAction: DismissDialogAction) => {
      if (dismissDialogAction.reason == DismissReason.PRESS_BACK) {
        dismissDialogAction.dismiss();
      }
      if (dismissDialogAction.reason == DismissReason.TOUCH_OUTSIDE) {
        dismissDialogAction.dismiss();
      }
    },
    alignment: DialogAlignment.Center,
    offset: { dx: 0, dy: -20 },
    customStyle: false,
    cornerRadius: 20,
    width: 300,
    height: 200,
    borderWidth: 1,
    borderStyle: BorderStyle.Dashed, // 使用borderStyle属性,需要和borderWidth属性一起使用
    borderColor: Color.Blue, // 使用borderColor属性,需要和borderWidth属性一起使用
    backgroundColor: Color.White,
    shadow: ({ radius: 20, color: Color.Grey, offsetX: 50, offsetY: 0 }),
  })

  // 在自定义组件即将析构销毁时将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 })
  }
}

【总结】
自定义弹窗既可以用户触摸屏幕手动关闭也可以自动关闭,无论哪一种都是通过执行指定代码命令完成的,只是触发方式不同。弹窗自动关闭的触发方式有很多种,如某个状态变量的改变、订阅或者接收到指定消息、倒计时等,也可以同时支持多种方式。上述代码示例即是同时支持触摸屏幕手动关闭和倒计时结束自动关闭弹窗两种方式。

更多关于谁能告诉我一个HarmonyOS鸿蒙Next简单好用的对话框组件!的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


不错不错,就是有点费代码!和脑细胞!

没有简单好用的吗?

自定义弹窗是通用逻辑,这个实现思路简单的说就是弹窗出现时通过setInterval启动个倒计时,倒计时结束后调用关闭弹窗的代码(this.controller?.close();),

SimpleDialog.ets

import { UIContext, PromptAction } from '@ohos.arkui.UIContext';
import { ComponentContent } from '@kit.ArkUI';

import { DialogContentVM } from './viewmodels/DialogContentVM';

export class SimpleDialog {

  private uiContext: UIContext;

  private promptAction: PromptAction;

  private dialogContent: DialogContentVM = new DialogContentVM();

  private node: ComponentContent<DialogContentVM> | null = null;

  private _close: () => void = () => {
    if (this.node) {
      this.promptAction.closeCustomDialog(this.node);
      this.node = null;
    }
  };

  private confirm: () => void = () => this._close();

  private cancel: () => void = () => this._close();

  private willDismiss: () => boolean = () => true;

  public setMessage(value: string): SimpleDialog {
    this.dialogContent.message = value;
    return this;
  }

  public setConfirmButtonText(value: string): SimpleDialog {
    this.dialogContent.confirmButtonText = value;
    return this;
  }

  public setCancelButtonText(value: string): SimpleDialog {
    this.dialogContent.cancelButtonText = value;
    return this;
  }

  public onWillDismiss(callback: () => boolean): SimpleDialog {
    this.willDismiss = callback;
    return this;
  }

  public onConfirm(callback: () => void): SimpleDialog {
    this.confirm = () => {
      this._close();
      callback();
    };
    this.dialogContent.confirm = this.confirm;
    return this;
  }

  public onCancel(callback: () => void): SimpleDialog {
    this.cancel = () => {
      this._close();
      callback();
    };
    this.dialogContent.cancel = this.cancel;
    return this;
  }

  public open(): void {
    if (this.node) {
      return;
    }
    this.node = new ComponentContent(this.uiContext, wrapBuilder(buildSimpleDialog), this.dialogContent);
    this.promptAction
      .openCustomDialog(this.node, {
        onWillDismiss: () => {
          if (this.willDismiss()) {
            this._close();
          }
        }
      })
      .catch(() => this.node = null);
  }

  public close(): void {
    this._close();
  }

  constructor(uiContext: UIContext) {
    this.uiContext = uiContext;
    this.promptAction = uiContext.getPromptAction();
    this.dialogContent.confirm = this.confirm;
    this.dialogContent.cancel = this.cancel;
  }
}

@Builder
function buildSimpleDialog(content: DialogContentVM): void {
  Column() {
    Text(content.message)
      .fontSize(16)
      .fontColor('#E6000000')
      .fontWeight(600)
      .margin({ bottom: 8 })
    Row() {
      Button() {
        Text(content.cancelButtonText)
          .fontColor($r('sys.color.mask_secondary'))
      }
      .width('50%')
      .height(40)
      .fontWeight(500)
      .type(ButtonType.Normal)
      .backgroundColor(Color.Transparent)
      .onClick(() => content.cancel())
      Button() {
        Text(content.confirmButtonText)
          .fontColor('#FFE64566')
      }
      .width('50%')
      .height(40)
      .fontWeight(500)
      .type(ButtonType.Normal)
      .backgroundColor(Color.Transparent)
      .onClick(() => content.confirm())
    }
    .width('100%')
  }
  .width('90%')
  .padding(16)
  .justifyContent(FlexAlign.Start)
  .backgroundColor(Color.White)
  .borderRadius(24)
}

./viewmodels/DialogContentVM

@ObservedV2
export class DialogContentVM {
  @Trace
  public message: string = '';

  @Trace
  public confirmButtonText: string = '确定';

  @Trace
  public cancelButtonText: string = '取消';

  @Trace
  public confirm: () => void = () => {};

  @Trace
  public cancel: () => void = () => {};
}

使用方式

@Entry
@ComponentV2
struct Index {

  private uiContext: UIContext = this.getUIContext();

  private timerId: number | undefined = undefined;

  private dialog: SimpleDialog = new SimpleDialog(this.uiContext)
    .setMessage('这是一个测试弹窗')
    .setConfirmButtonText('确定')
    .setCancelButtonText('取消')
    .onConfirm((): void => {
      this.uiContext.getPromptAction().showToast({ message: '你通过点击确定按钮关闭了弹窗' });
    })
    .onCancel((): void => {
      this.uiContext.getPromptAction().showToast({ message: '你通过点击取消按钮关闭了弹窗' });
    })
    .onWillDismiss(): boolean => {
      this.uiContext.getPromptAction().showToast({ message: '你通过返回false拦截了其它关闭方式' });
      return false;
    };

  public build(): void {
    Column({ space: 18 }) {
      Button('打开弹窗')
        .onClick(() => {
          clearTimeout(this.timerId);
          this.dialog.open();
        })
      Button('打开一个 3 秒后自动关闭的弹窗')
        .onClick(() => {
          clearTimeout(this.timerId);
          this.timerId = setTimeout(() => this.dialog.close(), 3000);
          this.dialog.open();
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

可以试试这个,已经封装好啦。支持链式调用,支持设置回调,支持拦截其它关闭方式(左滑屏幕、点击其它区域等)。

如果试过觉着还行,把弹窗UI换成自己需要的风格就可以直接用了。之后哪里需要就在哪里开导(import)。

写的仓促不太优雅,没有改造重新封装SimpleDialog,临时在外部用了timerId来定时关闭。如果需要更灵活的逻辑,改造起来应该不复杂的。

总的来说,HarmonyOS是一款非常优秀的操作系统,期待它能在未来带给我们更多惊喜!

使用自定义弹窗(CustomDialog)啊

2种关闭方式:

通过controller关闭弹窗。

// void CloseDialog() {
//     ArkUI_NativeDialogAPI_1 *dialogAPI = reinterpret_cast<ArcUI_NativeDialogAPI_1 *>(
//         OH_ArkUI_QueryModuleInterfaceByName(ARKUI_NATIVE_DIALOG, "ArkUI_NativeDialogAPI_1"));
//     dialogAPI->close(dialogController);
// }

通过dialogOptions关闭弹窗。

// void CloseCustomDialog() {
//     OH_ArkUI_CustomDialog_CloseDialog(id);
// }

自定义弹窗

有“关闭”或“确定”按钮,支持主动关闭,类似倒计时关闭那种么?

标题

这是第一段文本。

这是第二段文本。

还是觉得系统的弹框靠谱一点,

在HarmonyOS Next中,可以使用AlertDialog组件快速创建简单对话框。示例代码:

import { AlertDialog } from '@ohos.arkui.advanced';

let dialog = new AlertDialog({
  title: '提示',
  message: '这是一个基础对话框',
  confirm: {
    text: '确定',
    action: () => {
      console.log('点击确定');
    }
  }
});
dialog.show();

主要参数包含title、message和confirm按钮。该组件支持自定义样式和按钮事件绑定,适用于基础提示场景。使用前需在module.json5中声明ohos.permission.SYSTEM_DIALOG权限。

在HarmonyOS Next中,可以使用AlertDialog组件来实现简单的对话框功能。这个组件完全符合你的需求:

  1. 基本用法示例:
// 导入模块
import { AlertDialog } from '@ohos.alertdialog';

// 创建并显示对话框
let dialog = new AlertDialog.Builder()
  .setTitle("提示")
  .setMessage("这是一个对话框示例")
  .setPositiveButton("确定", () => {
    console.log("点击了确定");
  })
  .setNegativeButton("关闭", () => {
    console.log("点击了关闭");
  })
  .show();

// 代码主动关闭对话框
dialog.dismiss();
  1. 主要特点:
  • 支持设置标题(title)和消息内容(message)
  • 可以添加确定/取消按钮(setPositiveButton/setNegativeButton)
  • 通过show()方法显示,dismiss()方法关闭
  • 按钮点击回调支持异步操作
  1. 注意事项:
  • 需要在UI线程调用
  • 建议在页面onPageHide时主动关闭未消失的对话框
  • 支持自定义按钮文本和点击行为

这个组件设计简洁,使用方式与Android的AlertDialog类似,能满足大多数基础对话框需求。

回到顶部