HarmonyOS鸿蒙Next中如何封装通用的Dialog组件?

HarmonyOS鸿蒙Next中如何封装通用的Dialog组件? 应用中需要频繁使用确认对话框、选择器对话框等,如何封装通用的Dialog组件避免重复代码?
如何实现优雅的回调处理?

3 回复

实现代码

/**
 * 确认对话框
 */
[@CustomDialog](/user/CustomDialog)
export struct ConfirmDialog {
  controller: CustomDialogController;
  title: string = '提示';
  message: string = '';
  confirmText: string = '确定';
  cancelText: string = '取消';
  onConfirm?: () => void;
  onCancel?: () => void;
  
  build() {
    Column({ space: 16 }) {
      // 标题
      Text(this.title)
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .width('100%')
      
      // 消息内容
      Text(this.message)
        .fontSize(14)
        .fontColor('#666')
        .width('100%')
        .margin({ top: 8, bottom: 16 })
      
      // 按钮组
      Row({ space: 12 }) {
        Button(this.cancelText)
          .fontSize(16)
          .backgroundColor('#f5f5f5')
          .fontColor('#333')
          .layoutWeight(1)
          .onClick(() => {
            this.controller.close();
            if (this.onCancel) {
              this.onCancel();
            }
          })
        
        Button(this.confirmText)
          .fontSize(16)
          .backgroundColor('#ff6b6b')
          .fontColor('#fff')
          .layoutWeight(1)
          .onClick(() => {
            this.controller.close();
            if (this.onConfirm) {
              this.onConfirm();
            }
          })
      }
      .width('100%')
    }
    .padding(20)
    .backgroundColor(Color.White)
    .borderRadius(12)
  }
}

/**
 * 单选对话框
 */
[@CustomDialog](/user/CustomDialog)
export struct SelectDialog {
  controller: CustomDialogController;
  title: string = '请选择';
  options: string[] = [];
  selectedIndex: number = 0;
  onSelect?: (index: number, value: string) => void;
  
  [@State](/user/State) currentIndex: number = 0;
  
  aboutToAppear() {
    this.currentIndex = this.selectedIndex;
  }
  
  build() {
    Column({ space: 12 }) {
      // 标题
      Text(this.title)
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .width('100%')
        .padding({ bottom: 12 })
      
      // 选项列表
      List({ space: 0 }) {
        ForEach(this.options, (option: string, index: number) => {
          ListItem() {
            Row() {
              Text(option)
                .fontSize(16)
                .fontColor(this.currentIndex === index ? '#ff6b6b' : '#333')
                .layoutWeight(1)
              
              if (this.currentIndex === index) {
                Text('✓')
                  .fontSize(18)
                  .fontColor('#ff6b6b')
              }
            }
            .width('100%')
            .padding(12)
            .backgroundColor(this.currentIndex === index ? '#fff5f5' : Color.White)
            .borderRadius(8)
            .onClick(() => {
              this.currentIndex = index;
            })
          }
        })
      }
      .height(Math.min(this.options.length * 48, 300))
      
      // 确定按钮
      Button('确定')
        .width('100%')
        .backgroundColor('#ff6b6b')
        .fontColor('#fff')
        .margin({ top: 12 })
        .onClick(() => {
          this.controller.close();
          if (this.onSelect) {
            this.onSelect(this.currentIndex, this.options[this.currentIndex]);
          }
        })
    }
    .padding(20)
    .backgroundColor(Color.White)
    .borderRadius(12)
    .width('80%')
  }
}

/**
 * 输入对话框
 */
[@CustomDialog](/user/CustomDialog)
export struct InputDialog {
  controller: CustomDialogController;
  title: string = '输入';
  placeholder: string = '请输入';
  defaultValue: string = '';
  inputType: InputType = InputType.Normal;
  maxLength: number = 50;
  onConfirm?: (value: string) => void;
  
  [@State](/user/State) inputValue: string = '';
  
  aboutToAppear() {
    this.inputValue = this.defaultValue;
  }
  
  build() {
    Column({ space: 16 }) {
      // 标题
      Text(this.title)
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .width('100%')
      
      // 输入框
      TextInput({ text: this.inputValue, placeholder: this.placeholder })
        .type(this.inputType)
        .maxLength(this.maxLength)
        .onChange((value: string) => {
          this.inputValue = value;
        })
        .width('100%')
        .padding(12)
        .borderRadius(8)
        .backgroundColor('#f5f5f5')
      
      // 按钮组
      Row({ space: 12 }) {
        Button('取消')
          .fontSize(16)
          .backgroundColor('#f5f5f5')
          .fontColor('#333')
          .layoutWeight(1)
          .onClick(() => {
            this.controller.close();
          })
        
        Button('确定')
          .fontSize(16)
          .backgroundColor('#ff6b6b')
          .fontColor('#fff')
          .layoutWeight(1)
          .onClick(() => {
            this.controller.close();
            if (this.onConfirm) {
              this.onConfirm(this.inputValue);
            }
          })
      }
      .width('100%')
    }
    .padding(20)
    .backgroundColor(Color.White)
    .borderRadius(12)
  }
}

/**
 * 加载对话框
 */
[@CustomDialog](/user/CustomDialog)
export struct LoadingDialog {
  controller: CustomDialogController;
  message: string = '加载中...';
  
  build() {
    Column({ space: 16 }) {
      LoadingProgress()
        .width(50)
        .height(50)
        .color('#ff6b6b')
      
      Text(this.message)
        .fontSize(14)
        .fontColor('#666')
    }
    .padding(30)
    .backgroundColor(Color.White)
    .borderRadius(12)
  }
}

使用示例

@Entry
@Component
struct DemoPage {
  private confirmDialogController: CustomDialogController | null = null;
  private selectDialogController: CustomDialogController | null = null;
  private inputDialogController: CustomDialogController | null = null;
  private loadingDialogController: CustomDialogController | null = null;
  
  // 显示确认对话框
  showConfirmDialog() {
    this.confirmDialogController = new CustomDialogController({
      builder: ConfirmDialog({
        title: '删除确认',
        message: '确定要删除这条记录吗?此操作不可恢复。',
        confirmText: '删除',
        cancelText: '取消',
        onConfirm: () => {
          console.log('用户点击了删除');
          this.deleteRecord();
        },
        onCancel: () => {
          console.log('用户取消了删除');
        }
      }),
      autoCancel: true,
      alignment: DialogAlignment.Center
    });
    this.confirmDialogController.open();
  }
  
  // 显示选择对话框
  showSelectDialog() {
    this.selectDialogController = new CustomDialogController({
      builder: SelectDialog({
        title: '选择关系',
        options: ['朋友', '同事', '亲戚', '同学', '其他'],
        selectedIndex: 0,
        onSelect: (index: number, value: string) => {
          console.log(`选择了: ${value}`);
        }
      }),
      autoCancel: true,
      alignment: DialogAlignment.Center
    });
    this.selectDialogController.open();
  }
  
  // 显示输入对话框
  showInputDialog() {
    this.inputDialogController = new CustomDialogController({
      builder: InputDialog({
        title: '添加备注',
        placeholder: '请输入备注内容',
        defaultValue: '',
        maxLength: 100,
        onConfirm: (value: string) => {
          console.log(`输入内容: ${value}`);
        }
      }),
      autoCancel: true,
      alignment: DialogAlignment.Center
    });
    this.inputDialogController.open();
  }
  
  // 显示加载对话框
  async showLoadingDialog() {
    this.loadingDialogController = new CustomDialogController({
      builder: LoadingDialog({
        message: '正在保存...'
      }),
      autoCancel: false,
      alignment: DialogAlignment.Center
    });
    this.loadingDialogController.open();
    
    // 模拟异步操作
    await this.saveData();
    
    // 关闭加载对话框
    this.loadingDialogController.close();
  }
  
  async deleteRecord() {
    // 删除逻辑
  }
  
  async saveData() {
    // 保存逻辑
  }
  
  build() {
    Column({ space: 16 }) {
      Button('确认对话框').onClick(() => this.showConfirmDialog())
      Button('选择对话框').onClick(() => this.showSelectDialog())
      Button('输入对话框').onClick(() => this.showInputDialog())
      Button('加载对话框').onClick(() => this.showLoadingDialog())
    }
    .padding(20)
  }
}

原理解析

1. @CustomDialog装饰器

[@CustomDialog](/user/CustomDialog)
export struct ConfirmDialog {
  controller: CustomDialogController;
}
  • 标记为自定义对话框组件
  • 必须包含controller属性
  • 通过controller控制显示/隐藏

2. 回调函数传递

onConfirm?: () => void;
  • 使用可选属性定义回调
  • 调用前检查是否存在
  • 支持传递参数

3. @State状态管理

[@State](/user/State) currentIndex: number = 0;
  • 对话框内部状态
  • 响应用户交互
  • 触发UI更新

最佳实践

  1. 统一风格: 所有对话框使用相同的样式和动画
  2. 回调处理: 使用可选回调,调用前检查
  3. 自动关闭: 设置autoCancel: true支持点击外部关闭
  4. 内存管理: 对话框关闭后controller置null
  5. 异步操作: 加载对话框配合async/await使用

避坑指南

  1. 忘记close: 必须手动调用controller.close()
  2. 重复打开: 打开前检查controller是否已存在
  3. 内存泄漏: 组件销毁时关闭所有对话框
  4. 回调丢失: 箭头函数保持this指向
  5. 样式覆盖: 使用width/height限制对话框大小

效果展示

  • 确认对话框:标题+消息+双按钮
  • 选择对话框:标题+列表+确定按钮
  • 输入对话框:标题+输入框+双按钮
  • 加载对话框:加载动画+提示文字

更多关于HarmonyOS鸿蒙Next中如何封装通用的Dialog组件?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS Next中封装通用Dialog组件,可使用ArkTS的@CustomDialog装饰器。通过@CustomDialog定义自定义弹窗组件,结合@Prop@Link@State装饰器管理弹窗状态与数据传递。封装时需设计统一的样式布局、交互事件回调接口,并利用组件复用机制实现通用性。可封装基础弹窗模板,通过参数控制标题、内容、按钮等元素的显示与行为。

在HarmonyOS Next中封装通用Dialog组件,推荐使用ArkUI的CustomDialogController和自定义组件实现。以下是核心方案:

  1. 基础封装结构
// CommonDialog.ets
@Component
export struct CommonDialog {
  [@Prop](/user/Prop) title: ResourceStr = ''
  [@Prop](/user/Prop) content: ResourceStr = ''
  [@Prop](/user/Prop) confirmText: ResourceStr = $r('app.string.confirm')
  [@Prop](/user/Prop) cancelText: ResourceStr = $r('app.string.cancel')
  [@Link](/user/Link) isShow: boolean
  
  private controller: CustomDialogController
  
  aboutToAppear() {
    this.controller = new CustomDialogController({
      builder: this,
      cancel: () => { this.isShow = false },
      autoCancel: true
    })
  }
  
  build() {
    Column({ space: 12 }) {
      Text(this.title).fontSize(18)
      Text(this.content).fontSize(14)
      
      Flex({ justifyContent: FlexAlign.SpaceAround }) {
        Button(this.cancelText)
          .onClick(() => {
            this.controller.close()
            this.onCancel?.()
          })
        
        Button(this.confirmText)
          .onClick(() => {
            this.controller.close()
            this.onConfirm?.()
          })
      }
    }.padding(20)
  }
  
  // 回调函数
  private onConfirm?: () => void
  private onCancel?: () => void
  
  // 设置回调的链式方法
  setOnConfirm(callback: () => void): CommonDialog {
    this.onConfirm = callback
    return this
  }
  
  setOnCancel(callback: () => void): CommonDialog {
    this.onCancel = callback
    return this
  }
}
  1. 使用示例
// 在页面中使用
@State dialogVisible: boolean = false

build() {
  Column() {
    Button('显示对话框')
      .onClick(() => {
        this.dialogVisible = true
      })
    
    CommonDialog({
      title: '确认操作',
      content: '确定要执行此操作吗?',
      isShow: $dialogVisible
    })
    .setOnConfirm(() => {
      // 确认回调逻辑
      console.log('用户确认')
    })
    .setOnCancel(() => {
      // 取消回调逻辑
      console.log('用户取消')
    })
  }
}
  1. 扩展为选择器对话框
// SelectorDialog.ets
@Component
export struct SelectorDialog {
  [@Prop](/user/Prop) options: string[] = []
  [@Link](/user/Link) selectedIndex: number
  [@Link](/user/Link) isShow: boolean
  
  private controller: CustomDialogController
  
  build() {
    Column() {
      List({ space: 10 }) {
        ForEach(this.options, (item: string, index: number) => {
          ListItem() {
            Text(item)
              .onClick(() => {
                this.selectedIndex = index
                this.controller.close()
                this.onSelect?.(index, item)
              })
          }
        })
      }
    }
  }
  
  private onSelect?: (index: number, value: string) => void
  
  setOnSelect(callback: (index: number, value: string) => void): SelectorDialog {
    this.onSelect = callback
    return this
  }
}
  1. 回调处理优化
  • 使用Promise包装异步操作:
async showConfirmDialog(): Promise<boolean> {
  return new Promise((resolve) => {
    CommonDialog({
      title: '提示',
      content: '确认删除?',
      isShow: $dialogVisible
    })
    .setOnConfirm(() => resolve(true))
    .setOnCancel(() => resolve(false))
  })
}

// 使用
const result = await this.showConfirmDialog()
  1. 配置化管理 创建DialogConfig统一管理样式和文案:
export class DialogConfig {
  static readonly CONFIRM = {
    title: $r('app.string.confirm_title'),
    confirmText: $r('app.string.ok'),
    cancelText: $r('app.string.cancel')
  }
  
  static readonly WARNING = {
    title: $r('app.string.warning_title'),
    confirmColor: Color.Red
  }
}

关键点:

  • 利用CustomDialogController管理对话框生命周期
  • 使用@Prop@Link实现数据双向绑定
  • 通过链式调用设置回调函数
  • 支持Promise异步处理
  • 通过配置对象统一管理样式

这种封装方式实现了对话框的复用,回调处理清晰,且易于扩展新类型的对话框。

回到顶部