HarmonyOS 鸿蒙Next中自定义弹窗customDialog弹出和关闭

HarmonyOS 鸿蒙Next中自定义弹窗customDialog弹出和关闭 不依赖UI组件的全局自定义弹出框 (openCustomDialog)(推荐)-使用弹出框 (Dialog)-使用弹窗-UI开发 (ArkTS声明式开发范式)-ArkUI(方舟UI框架)-应用框架 - 华为HarmonyOS开发者

文档中的完整示例部分使用顶层函数来构建自定义弹窗

没有对变量进行双向绑定,我希望可以双向传递,对变量进行修改,不方便使用顶层函数,于是就写在了Index结构内

直接使用closeDialog关闭会直接退出程序,不会迁移使用提供的PromptActionClass,求教

// --- 自定义弹窗组件 添加列表 ---
@Builder
CustomDialogComponent()
{
  Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceAround })
  {
    TextInput({ placeholder: '请输入新建列表名' })
      .height('30%')
      .showUnderline(true)
      .onChange((value) => {
        this.listName = value
      })
      .onSubmit(() => {
        this.listOfList.push({ title: this.listName })
        this.listName = '无标题列表'
      })
    Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceAround, alignItems: ItemAlign.Center })
    {
      Button('确定创建')
        .type(ButtonType.Normal)
        .fontColor('#0a59f7')
        .backgroundColor(Color.Transparent)
        .enabled(this.listName === '无标题列表' ? false : true)
        .onClick(() => {
        })
      Divider().vertical(true)
        .margin(5)
        .color('#0a59f7')
      Button('取消')
        .type(ButtonType.Normal)
        .backgroundColor(Color.Transparent)
        .fontColor('#999')
        .onClick(() => {
          PromptActionClass.closeDialog()
        })
    }.height('30%')
  }.width('80%')
  .height('20%')
}

更多关于HarmonyOS 鸿蒙Next中自定义弹窗customDialog弹出和关闭的实战教程也可以访问 https://www.itying.com/category-93-b0.html

10 回复

推荐使用 API version 18 引入的新接口 presentCustomDialogDialogController,这样可以避免直接管理 dialogId,实现更简洁的弹窗控制。同时,您的弹窗内容定义在结构体内,可以直接访问状态变量,实现双向绑定。

解决方案

  • 使用 DialogController 控制弹窗:通过 presentCustomDialog 接口打开弹窗时传入 DialogController 实例,然后在弹窗内通过调用 controller.close() 来关闭弹窗,无需处理 dialogId
  • 双向变量绑定:由于弹窗 @Builder 定义在结构体内,直接修改 @State 变量即可实现双向绑定。
  • 错误处理:使用 try-catch.catch 处理异步操作错误。

修改后的代码示例

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

@Entry
@Component
struct Index {
  // 获取UIContext和PromptAction
  private uiContext: UIContext = this.getUIContext();
  private promptAction: PromptAction = this.uiContext.getPromptAction();
  // 创建DialogController实例用于控制弹窗
  private dialogController: promptAction.DialogController = new promptAction.DialogController();

  @State listName: string = "无标题列表";
  @State listOfList: Array<{ title: string }> = [];

  // 自定义弹窗组件
  @Builder
  CustomDialogComponent() {
    Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceAround }) {
      TextInput({ placeholder: '请输入新建列表名', text: this.listName })
        .height('30%')
        .showUnderline(true)
        .onChange((value: string) => {
          this.listName = value; // 直接修改状态变量,实现双向绑定
        })
        .onSubmit(() => {
          this.listOfList.push({ title: this.listName });
          this.listName = '无标题列表';
        })
      Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceAround, alignItems: ItemAlign.Center }) {
        Button('确定创建')
          .type(ButtonType.Normal)
          .fontColor('#0a59f7')
          .backgroundColor(Color.Transparent)
          .enabled(this.listName === '无标题列表' ? false : true)
          .onClick(() => {
            this.listOfList.push({ title: this.listName });
            this.listName = '无标题列表';
            this.dialogController.close(); // 通过Controller关闭弹窗
          })
        Divider().vertical(true)
          .margin(5)
          .color('#0a59f7')
        Button('取消')
          .type(ButtonType.Normal)
          .backgroundColor(Color.Transparent)
          .fontColor('#999')
          .onClick(() => {
            this.dialogController.close(); // 通过Controller关闭弹窗
          })
      }.height('30%')
    }.width('80%')
    .height('20%')
  }

  // 打开弹窗
  openDialog() {
    this.promptAction.presentCustomDialog(
      () => { this.CustomDialogComponent() }, // Builder函数
      this.dialogController, // 传入Controller
      {} // 可选配置,例如弹窗样式
    ).then((dialogId: number) => {
      console.info(`弹窗打开成功,ID: ${dialogId}`);
      // 不需要保存dialogId,因为使用Controller关闭
    }).catch((error: BusinessError) => {
      console.error(`打开弹窗失败: code=${error.code}, message=${error.message}`);
    });
  }

  build() {
    Row() {
      Column() {
        Button('打开弹窗')
          .onClick(() => {
            this.openDialog(); // 点击打开弹窗
          })
        // 其他UI内容,例如显示列表
        List() {
          ForEach(this.listOfList, (item: { title: string }) => {
            ListItem() {
              Text(item.title).fontSize(16)
            }
          })
        }
      }
      .width('100%')
      .height('100%')
    }
    .height('100%')
  }
}

关键点说明

  • DialogController 使用DialogController 实例用于控制弹窗的关闭,调用 close() 方法即可,无需处理 dialogId,简化了多弹窗管理。
  • 双向绑定:弹窗内的 TextInput 直接绑定到 @State listName,修改时自动更新父组件状态。
  • API 版本presentCustomDialog 从 API version 18 开始支持,适用于您的环境(API 17以上)。
  • 错误处理:使用 .catch 捕获打开弹窗时的错误,确保稳定性。

如果您的项目中有多个弹窗,可以为每个弹窗创建单独的 DialogController 实例,但通常一个控制器可以管理一个弹窗实例。如果您需要同时打开多个弹窗,建议为每个弹窗创建独立的控制器。

更多关于HarmonyOS 鸿蒙Next中自定义弹窗customDialog弹出和关闭的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


您的回答中,调用closeCustomDialog(this.dialogId),传入参数为id: number,该重载的返回值是void,而不是一个promise,使用.then链式会报错,只有签名ComponentContent<T>返回一个voidpromise,请检查。而且这种写法,在我有多数弹窗时,很难维护。虽然很不礼貌,但是如果是使用AI生成的回答,很可能不可行,AI不太熟悉API17以上新接口,如果不是,那我很抱歉。

确实有问题,改了一下,你看看,但我不知道你的设备是旧机的api17还是p80的api19还是6.0的api20。 其实你想一步到位的话,直接用下面这个库就好了,不用考虑 api 问题。 https://gitcode.com/openharmony-sig/dialoghub

DialogHub底层对系统OverlayManager、BindSheet和OpenCustomDialog进行封装,提供了页面级弹窗、键盘避让、弹窗生命周期管理、弹窗模板创建、全局弹窗生命周期管理等能力。

好,谢谢,我之后看一下,最近刚从前端转过来,各种不习惯,感觉ArkTS好像什么都做了,又好像什么都没做,别别扭扭的,

直接退出程序的原因是未正确使用弹窗控制器实例,调用close方法时应当使用自定义的controller实例而非全局PromptActionClass;变量双向绑定需使用@Link/@Consume装饰器而非普通变量

解决方案

@Entry
@Component
struct Index {
  @State listName: string = '无标题列表'
  private dialogController: CustomDialogController = new CustomDialogController({
    builder: this.CustomDialogComponent()
  })

  @Builder
  CustomDialogComponent() {
    // 保持原有弹窗内容不变
    // 修改关闭触发逻辑:
    Button('取消').onClick(() => {
      this.dialogController.close() // 正确调用实例方法
    })
  }

  build() {
    Button('打开弹窗').onClick(() => {
      this.dialogController.open()
    })
  }
}

问题可能存在于

使用@Builder构建弹窗时未建立双向数据绑定,导致父组件与弹窗间的数据无法同步更新。

直接调用PromptActionClass.closeDialog()未正确关联弹窗实例,导致应用异常退出。

可以尝试如下方案

//使用状态管理实现双向绑定

@Entry
@Component
struct Index {
  @State listName: string = '无标题列表'
  @State listOfList: Array<{ title: string }> = []
  private dialogId: number | null = null // 存储弹窗实例ID

  // 使用ComponentContent形式创建解耦弹窗
  openCustomDialog() {
    const builder: ComponentContent = {
      builder: () => this.CustomDialogComponent()
    }

    this.getUIContext().getPromptAction().openCustomDialog({
      builder,
      showInSubWindow: false
    }).then((dialogId: number) => {
      this.dialogId = dialogId // 保存dialogId用于后续操作
    })
  }

  [@Builder](/user/Builder)
  CustomDialogComponent() {
    Flex({ direction: FlexDirection.Column }) {
      TextInput({ text: this.listName })
        .onChange((value: string) => {
          this.listName = value // 通过@State自动触发UI更新
        })

      Flex({ direction: FlexDirection.Row }) {
        Button('确定创建')
          .onClick(() => {
            this.listOfList.push({ title: this.listName })
            this.closeDialog()
          })

        Button('取消')
          .onClick(() => this.closeDialog())
      }
    }
  }

  // 统一关闭弹窗方法
  private closeDialog() {
    if (this.dialogId !== null) {
      this.getUIContext().getPromptAction().closeCustomDialog(this.dialogId)
      this.dialogId = null
    }
  }
}
可以尝试一下使用发布订阅的方式进行更新,可以在Index的aboutToAppear订阅事件中更新数据,在弹窗中发布事件以触发Index中的更新,记得在页面销毁时解除订阅

在HarmonyOS Next中,自定义弹窗CustomDialog通过show()方法弹出,通过close()方法关闭。开发者需继承CustomDialogController类,在aboutToAppear中设置弹窗内容。使用setCustomDialog绑定自定义组件,调用open可展示弹窗。关闭时执行close方法销毁弹窗实例。

在HarmonyOS Next中,自定义弹窗(CustomDialog)的关闭需要正确使用DialogController。你遇到的问题是因为直接调用PromptActionClass.closeDialog(),这会导致整个应用退出。

正确的做法是:

  1. 使用DialogController管理弹窗
@State dialogController: DialogController = new DialogController()
  1. @Builder中传递DialogController
[@Builder](/user/Builder)
CustomDialogComponent(dialogController: DialogController) {
  // ... 弹窗内容
  Button('取消')
    .onClick(() => {
      dialogController.close()
    })
}
  1. 打开弹窗时传递controller
this.dialogController.open(CustomDialogComponent(this.dialogController))
  1. 对于双向数据绑定,可以通过回调函数或@Link/@Consume装饰器实现:
// 使用回调函数传递数据
[@Builder](/user/Builder)
CustomDialogComponent(dialogController: DialogController, callback: (data: string) => void) {
  TextInput()
    .onChange((value) => {
      callback(value) // 将数据传递回父组件
    })
  
  Button('确定')
    .onClick(() => {
      dialogController.close()
    })
}

这样就能实现弹窗的正常关闭和数据双向传递,避免应用异常退出。

回到顶部