玩转HarmonyOS APP系统对话框

举报
Jack20 发表于 2026/06/20 16:10:15 2026/06/20
【摘要】 HarmonyOS APP开发中的系统对话框:AlertDialog、ActionSheet、TextPickerDialog、DatePickerDialog、自定义系统对话框📌 核心要点:掌握 HarmonyOS 五大系统对话框的使用场景与配置技巧,从简单确认到复杂选择,用最少的代码实现最规范的交互 一、背景与动机你一定遇到过这样的场景——用户点了"删除"按钮,你需要在弹出一个确认框...

HarmonyOS APP开发中的系统对话框:AlertDialog、ActionSheet、TextPickerDialog、DatePickerDialog、自定义系统对话框

📌 核心要点:掌握 HarmonyOS 五大系统对话框的使用场景与配置技巧,从简单确认到复杂选择,用最少的代码实现最规范的交互


一、背景与动机

你一定遇到过这样的场景——用户点了"删除"按钮,你需要在弹出一个确认框,问一句"确定要删除吗?"。或者用户需要选择一个日期,你不想自己画日历组件,太麻烦了。再或者,你需要让用户从几个选项中选一个,但又不想跳转到新页面。

这些场景都有一个共同点:需要一个轻量的、模态的、系统级的交互界面。这就是系统对话框的用武之地。

系统对话框就像餐厅里的服务员——你不用自己跑厨房(跳转页面),也不用自己端盘子(自定义弹窗),只需要跟服务员说一声(调用API),他就会帮你把事情办好(弹出对话框),然后把结果告诉你(回调函数)。

HarmonyOS 提供了五种系统对话框:AlertDialog(警告对话框)、ActionSheet(列表选择)、TextPickerDialog(文本选择)、DatePickerDialog(日期选择),以及自定义对话框。它们各有各的绝活,今天我们一个一个来拆解。


二、核心原理

2.1 系统对话框分类与适用场景

图片.png

flowchart TD
    A[系统对话框] --> B{交互类型}
    B --> C[确认/警告]
    B --> D[列表选择]
    B --> E[滚轮选择]
    B --> F[自定义内容]
    
    C --> G[AlertDialog<br/>警告对话框]
    D --> H[ActionSheet<br/>列表选择弹窗]
    
    E --> I{选择内容}
    I --> J[TextPickerDialog<br/>文本滚轮选择]
    I --> K[DatePickerDialog<br/>日期滚轮选择]
    
    F --> L[CustomDialog<br/>自定义对话框]
    
    G --> M[特点: 标题+内容+按钮<br/>场景: 删除确认/退出确认]
    H --> N[特点: 选项列表+取消<br/>场景: 分享/操作菜单]
    J --> O[特点: 滚轮+确认<br/>场景: 城市选择/分类选择]
    K --> P[特点: 年月日滚轮<br/>场景: 日期选择/生日设置]
    L --> Q[特点: 完全自定义<br/>场景: 复杂表单/加载动画]
    
    classDef primary fill:#4CAF50,stroke:#388E3C,color:#fff
    classDef warning fill:#FF9800,stroke:#F57C00,color:#fff
    classDef error fill:#F44336,stroke:#D32F2F,color:#fff
    classDef info fill:#2196F3,stroke:#1976D2,color:#fff
    classDef purple fill:#9C27B0,stroke:#7B1FA2,color:#fff
    
    class A primary
    class B,C,D,E,F info
    class G,H,J,K,L warning
    class M,N,O,P,Q purple

2.2 对话框生命周期

所有系统对话框都遵循统一的生命周期:创建 → 显示 → 用户交互 → 回调 → 销毁。它们是模态的——弹出后用户必须先处理对话框,才能继续操作应用。

2.3 关键API对比

对话框 调用方式 主要参数 返回结果
AlertDialog AlertDialog.show() title, message, buttons 按钮索引
ActionSheet ActionSheet.show() title, message, buttons 按钮索引
TextPickerDialog TextPickerDialog.show() range, selected 选中值和索引
DatePickerDialog DatePickerDialog.show() selected, min, max 年月日
CustomDialog @CustomDialog 装饰器 完全自定义 自定义回调

三、代码实战

3.1 AlertDialog 与 ActionSheet:确认与选择

AlertDialog 适合需要用户明确确认的场景(删除、退出、授权),ActionSheet 适合提供一组操作选项让用户选择。

// AlertDialogActionSheet.ets
// AlertDialog 与 ActionSheet 实战

@Entry
@Component
struct AlertDialogActionSheetDemo {
  // 操作日志
  @State actionLog: string = '等待操作...';
  // 是否显示自定义对话框
  @State showCustomDialog: boolean = false;

  // AlertDialog:删除确认
  private showDeleteConfirm(): void {
    AlertDialog.show({
      title: '确认删除',
      message: '此操作将永久删除该文件,无法恢复。确定要继续吗?',
      autoCancel: true, // 点击遮罩层是否关闭
      alignment: DialogAlignment.Center,
      buttons: [
        {
          text: '取消',
          color: '#9E9E9E',
          action: () => {
            this.actionLog = '用户取消了删除操作';
          }
        },
        {
          text: '确定删除',
          color: '#F44336',
          action: () => {
            this.actionLog = '文件已删除 ✓';
          }
        }
      ]
    });
  }

  // AlertDialog:退出确认(三按钮)
  private showExitConfirm(): void {
    AlertDialog.show({
      title: '保存更改',
      message: '您有未保存的更改,是否在退出前保存?',
      autoCancel: true,
      buttons: [
        {
          text: '不保存',
          color: '#F44336',
          action: () => {
            this.actionLog = '退出且不保存';
          }
        },
        {
          text: '取消',
          color: '#9E9E9E',
          action: () => {
            this.actionLog = '取消退出,继续编辑';
          }
        },
        {
          text: '保存',
          color: '#4CAF50',
          action: () => {
            this.actionLog = '保存更改并退出 ✓';
          }
        }
      ]
    });
  }

  // ActionSheet:分享菜单
  private showShareMenu(): void {
    ActionSheet.show({
      title: '分享到',
      message: '选择分享方式',
      autoCancel: true,
      buttons: [
        { text: '微信', color: '#07C160' },
        { text: 'QQ', color: '#12B7F5' },
        { text: '微博', color: '#E6162D' },
        { text: '复制链接', color: '#E0E0E0' },
      ],
      cancelButton: () => {
        this.actionLog = '取消分享';
      },
      confirmationButton: () => {
        // ActionSheet的确认回调
      },
      callback: (index: number) => {
        const platforms = ['微信', 'QQ', '微博', '复制链接'];
        this.actionLog = `分享到: ${platforms[index]}`;
      }
    });
  }

  // ActionSheet:图片操作菜单
  private showImageActions(): void {
    ActionSheet.show({
      title: '图片操作',
      message: '选择操作方式',
      autoCancel: true,
      buttons: [
        { text: '查看大图', color: '#2196F3' },
        { text: '保存到相册', color: '#4CAF50' },
        { text: '设为头像', color: '#FF9800' },
        { text: '删除图片', color: '#F44336' },
      ],
      cancelButton: () => {
        this.actionLog = '取消操作';
      },
      callback: (index: number) => {
        const actions = ['查看大图', '保存到相册', '设为头像', '删除图片'];
        this.actionLog = `执行: ${actions[index]}`;
      }
    });
  }

  build() {
    Column() {
      // 标题区域
      Column() {
        Text('系统对话框实战')
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FFFFFF')
        Text('AlertDialog & ActionSheet')
          .fontSize(14)
          .fontColor('#FFFFFFAA')
          .margin({ top: 4 })
      }
      .width('100%')
      .padding({ top: 56, left: 20, right: 20, bottom: 20 })
      .linearGradient({
        direction: GradientDirection.BottomRight,
        colors: [['#1A1A2E', 0], ['#16213E', 1]]
      })

      // 操作日志
      Column() {
        Text('操作日志')
          .fontSize(13)
          .fontColor('#9E9E9E')
          .margin({ bottom: 6 })
        Text(this.actionLog)
          .fontSize(15)
          .fontColor('#4CAF50')
          .fontWeight(FontWeight.Medium)
      }
      .width('100%')
      .padding(14)
      .margin({ left: 16, right: 16, top: 12 })
      .borderRadius(10)
      .backgroundColor('#1E1E2E')
      .alignItems(HorizontalAlign.Start)

      // AlertDialog 示例
      Text('AlertDialog 警告对话框')
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
        .fontColor('#E0E0E0')
        .margin({ left: 20, top: 20, bottom: 8 })

      Column({ space: 8 }) {
        // 删除确认
        Row() {
          Text('🗑️')
            .fontSize(18)
          Text('删除确认(双按钮)')
            .fontSize(14)
            .fontColor('#E0E0E0')
            .margin({ left: 8 })
          Blank()
          Text('→')
            .fontSize(14)
            .fontColor('#9E9E9E')
        }
        .width('100%')
        .padding(14)
        .borderRadius(10)
        .backgroundColor('#1E1E2E')
        .onClick(() => this.showDeleteConfirm())

        // 退出确认
        Row() {
          Text('💾')
            .fontSize(18)
          Text('保存退出(三按钮)')
            .fontSize(14)
            .fontColor('#E0E0E0')
            .margin({ left: 8 })
          Blank()
          Text('→')
            .fontSize(14)
            .fontColor('#9E9E9E')
        }
        .width('100%')
        .padding(14)
        .borderRadius(10)
        .backgroundColor('#1E1E2E')
        .onClick(() => this.showExitConfirm())
      }
      .padding({ left: 16, right: 16 })

      // ActionSheet 示例
      Text('ActionSheet 列表选择')
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
        .fontColor('#E0E0E0')
        .margin({ left: 20, top: 20, bottom: 8 })

      Column({ space: 8 }) {
        // 分享菜单
        Row() {
          Text('🔗')
            .fontSize(18)
          Text('分享菜单')
            .fontSize(14)
            .fontColor('#E0E0E0')
            .margin({ left: 8 })
          Blank()
          Text('→')
            .fontSize(14)
            .fontColor('#9E9E9E')
        }
        .width('100%')
        .padding(14)
        .borderRadius(10)
        .backgroundColor('#1E1E2E')
        .onClick(() => this.showShareMenu())

        // 图片操作
        Row() {
          Text('🖼️')
            .fontSize(18)
          Text('图片操作菜单')
            .fontSize(14)
            .fontColor('#E0E0E0')
            .margin({ left: 8 })
          Blank()
          Text('→')
            .fontSize(14)
            .fontColor('#9E9E9E')
        }
        .width('100%')
        .padding(14)
        .borderRadius(10)
        .backgroundColor('#1E1E2E')
        .onClick(() => this.showImageActions())
      }
      .padding({ left: 16, right: 16 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#121212')
  }
}

3.2 TextPickerDialog 与 DatePickerDialog:滚轮选择

TextPickerDialog 适合从预定义列表中选择(城市、分类、学历),DatePickerDialog 适合选择日期(生日、预约时间)。

// PickerDialogs.ets
// TextPickerDialog 与 DatePickerDialog 实战

@Entry
@Component
struct PickerDialogsDemo {
  // 选中的城市
  @State selectedCity: string = '请选择';
  // 选中的日期
  @State selectedDate: string = '请选择';
  // 选中的学历
  @State selectedEducation: string = '请选择';
  // 选中的时间段
  @State selectedTimeSlot: string = '请选择';

  // 城市数据
  private cities: string[] = [
    '北京', '上海', '广州', '深圳', '杭州', '成都', '武汉', '南京', '西安', '重庆'
  ];
  // 学历数据
  private educations: string[] = [
    '高中及以下', '大专', '本科', '硕士', '博士', '其他'
  ];
  // 时间段数据
  private timeSlots: string[] = [
    '09:00 - 10:00', '10:00 - 11:00', '11:00 - 12:00',
    '14:00 - 15:00', '15:00 - 16:00', '16:00 - 17:00'
  ];

  // 显示城市选择器
  private showCityPicker(): void {
    TextPickerDialog.show({
      range: this.cities,
      selected: 0,
      onAccept: (value: string, index: number) => {
        this.selectedCity = `${value} (索引: ${index})`;
      },
      onCancel: () => {
        console.info('用户取消了城市选择');
      }
    });
  }

  // 显示学历选择器
  private showEducationPicker(): void {
    TextPickerDialog.show({
      range: this.educations,
      selected: 2, // 默认选中"本科"
      onAccept: (value: string, index: number) => {
        this.selectedEducation = value;
      },
      onCancel: () => {
        console.info('用户取消了学历选择');
      }
    });
  }

  // 显示时间段选择器
  private showTimeSlotPicker(): void {
    TextPickerDialog.show({
      range: this.timeSlots,
      selected: 0,
      onAccept: (value: string, index: number) => {
        this.selectedTimeSlot = value;
      },
      onCancel: () => {
        console.info('用户取消了时间段选择');
      }
    });
  }

  // 显示日期选择器
  private showDatePicker(): void {
    DatePickerDialog.show({
      selected: new Date(), // 默认选中今天
      min: new Date('1900-01-01'), // 最小日期
      max: new Date('2030-12-31'), // 最大日期
      lunar: false, // 是否显示农历
      onAccept: (value: Date) => {
        const year = value.getFullYear();
        const month = String(value.getMonth() + 1).padStart(2, '0');
        const day = String(value.getDate()).padStart(2, '0');
        this.selectedDate = `${year}-${month}-${day}`;
      },
      onCancel: () => {
        console.info('用户取消了日期选择');
      }
    });
  }

  build() {
    Column() {
      // 标题区域
      Column() {
        Text('滚轮选择器实战')
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FFFFFF')
        Text('TextPickerDialog & DatePickerDialog')
          .fontSize(14)
          .fontColor('#FFFFFFAA')
          .margin({ top: 4 })
      }
      .width('100%')
      .padding({ top: 56, left: 20, right: 20, bottom: 20 })
      .linearGradient({
        direction: GradientDirection.BottomRight,
        colors: [['#1A1A2E', 0], ['#16213E', 1]]
      })

      // 选择结果展示
      Column({ space: 10 }) {
        // 城市选择结果
        Row() {
          Text('📍 城市')
            .fontSize(14)
            .fontColor('#9E9E9E')
            .width(60)
          Text(this.selectedCity)
            .fontSize(15)
            .fontColor(this.selectedCity === '请选择' ? '#9E9E9E' : '#4CAF50')
            .fontWeight(FontWeight.Medium)
          Blank()
          Text('选择 >')
            .fontSize(13)
            .fontColor('#2196F3')
        }
        .width('100%')
        .padding(14)
        .borderRadius(10)
        .backgroundColor('#1E1E2E')
        .onClick(() => this.showCityPicker())

        // 学历选择结果
        Row() {
          Text('🎓 学历')
            .fontSize(14)
            .fontColor('#9E9E9E')
            .width(60)
          Text(this.selectedEducation)
            .fontSize(15)
            .fontColor(this.selectedEducation === '请选择' ? '#9E9E9E' : '#4CAF50')
            .fontWeight(FontWeight.Medium)
          Blank()
          Text('选择 >')
            .fontSize(13)
            .fontColor('#2196F3')
        }
        .width('100%')
        .padding(14)
        .borderRadius(10)
        .backgroundColor('#1E1E2E')
        .onClick(() => this.showEducationPicker())

        // 时间段选择结果
        Row() {
          Text('🕐 时间')
            .fontSize(14)
            .fontColor('#9E9E9E')
            .width(60)
          Text(this.selectedTimeSlot)
            .fontSize(15)
            .fontColor(this.selectedTimeSlot === '请选择' ? '#9E9E9E' : '#4CAF50')
            .fontWeight(FontWeight.Medium)
          Blank()
          Text('选择 >')
            .fontSize(13)
            .fontColor('#2196F3')
        }
        .width('100%')
        .padding(14)
        .borderRadius(10)
        .backgroundColor('#1E1E2E')
        .onClick(() => this.showTimeSlotPicker())

        // 日期选择结果
        Row() {
          Text('📅 日期')
            .fontSize(14)
            .fontColor('#9E9E9E')
            .width(60)
          Text(this.selectedDate)
            .fontSize(15)
            .fontColor(this.selectedDate === '请选择' ? '#9E9E9E' : '#4CAF50')
            .fontWeight(FontWeight.Medium)
          Blank()
          Text('选择 >')
            .fontSize(13)
            .fontColor('#2196F3')
        }
        .width('100%')
        .padding(14)
        .borderRadius(10)
        .backgroundColor('#1E1E2E')
        .onClick(() => this.showDatePicker())
      }
      .padding({ left: 16, right: 16, top: 16 })

      // 选择器说明
      Column() {
        Text('💡 选择器使用提示')
          .fontSize(14)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FF9800')
          .margin({ bottom: 8 })
        Text('• TextPickerDialog 适合预定义列表的单选场景')
          .fontSize(12)
          .fontColor('#BDBDBD')
        Text('• DatePickerDialog 支持设置最小/最大日期范围')
          .fontSize(12)
          .fontColor('#BDBDBD')
          .margin({ top: 4 })
        Text('• selected 参数控制默认选中项的索引')
          .fontSize(12)
          .fontColor('#BDBDBD')
          .margin({ top: 4 })
        Text('• onAccept 回调返回选中的值和索引')
          .fontSize(12)
          .fontColor('#BDBDBD')
          .margin({ top: 4 })
      }
      .width('100%')
      .padding(14)
      .margin({ left: 16, right: 16, top: 16 })
      .borderRadius(10)
      .backgroundColor('#1E1E2E')
      .border({ width: 1, color: '#FF980033' })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#121212')
  }
}

3.3 自定义系统对话框:CustomDialog

当系统对话框无法满足需求时(比如需要输入框、加载动画、复杂表单),就需要自定义对话框。

// CustomDialogDemo.ets
// 自定义系统对话框:CustomDialog 实战

// 自定义加载对话框
@CustomDialog
struct LoadingDialog {
  // 对话框控制器
  controller: CustomDialogController;
  // 加载提示文字
  @State loadingText: string = '加载中...';
  // 进度(0-100)
  @State progress: number = 0;
  // 定时器
  private timer: number = -1;

  aboutToAppear(): void {
    // 模拟加载进度
    this.timer = setInterval(() => {
      this.progress += Math.random() * 15;
      if (this.progress >= 100) {
        this.progress = 100;
        clearInterval(this.timer);
        this.loadingText = '加载完成!';
        // 延迟关闭
        setTimeout(() => {
          this.controller.close();
        }, 800);
      } else {
        this.loadingText = `加载中... ${Math.floor(this.progress)}%`;
      }
    }, 300);
  }

  aboutToDisappear(): void {
    clearInterval(this.timer);
  }

  build() {
    Column() {
      // 加载动画
      LoadingProgress()
        .width(48)
        .height(48)
        .color('#4CAF50')

      // 提示文字
      Text(this.loadingText)
        .fontSize(14)
        .fontColor('#E0E0E0')
        .margin({ top: 16 })

      // 进度条
      Progress({ value: this.progress, total: 100, type: ProgressType.Linear })
        .width('80%')
        .color('#4CAF50')
        .backgroundColor('#FFFFFF22')
        .margin({ top: 12 })
    }
    .padding(24)
    .borderRadius(16)
    .backgroundColor('#1E1E2E')
    .alignItems(HorizontalAlign.Center)
  }
}

// 自定义输入对话框
@CustomDialog
struct InputDialog {
  controller: CustomDialogController;
  // 输入内容
  @State inputText: string = '';
  // 标题
  title: string = '请输入';
  // 占位提示
  placeholder: string = '请输入内容...';
  // 确认回调
  onConfirm: (text: string) => void = () => {};

  build() {
    Column() {
      // 标题
      Text(this.title)
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .fontColor('#FFFFFF')

      // 输入框
      TextInput({ text: this.inputText, placeholder: this.placeholder })
        .width('100%')
        .height(44)
        .fontSize(15)
        .fontColor('#E0E0E0')
        .placeholderColor('#9E9E9E')
        .backgroundColor('#2A2A3E')
        .borderRadius(10)
        .padding({ left: 12, right: 12 })
        .margin({ top: 16 })
        .onChange((value: string) => {
          this.inputText = value;
        })

      // 按钮行
      Row({ space: 12 }) {
        Text('取消')
          .fontSize(15)
          .fontColor('#9E9E9E')
          .layoutWeight(1)
          .textAlign(TextAlign.Center)
          .padding({ top: 10, bottom: 10 })
          .borderRadius(10)
          .backgroundColor('#2A2A3E')
          .onClick(() => {
            this.controller.close();
          })

        Text('确定')
          .fontSize(15)
          .fontColor('#FFFFFF')
          .layoutWeight(1)
          .textAlign(TextAlign.Center)
          .padding({ top: 10, bottom: 10 })
          .borderRadius(10)
          .backgroundColor('#4CAF50')
          .onClick(() => {
            this.onConfirm(this.inputText);
            this.controller.close();
          })
      }
      .width('100%')
      .margin({ top: 20 })
    }
    .padding(20)
    .borderRadius(16)
    .backgroundColor('#1E1E2E')
  }
}

// 自定义评分对话框
@CustomDialog
struct RatingDialog {
  controller: CustomDialogController;
  // 评分(1-5星)
  @State rating: number = 0;
  // 评价内容
  @State comment: string = '';
  // 确认回调
  onSubmit: (rating: number, comment: string) => void = () => {};

  build() {
    Column() {
      Text('请为本次服务评分')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .fontColor('#FFFFFF')

      // 星级评分
      Row({ space: 8 }) {
        ForEach([1, 2, 3, 4, 5], (star: number) => {
          Text(star <= this.rating ? '⭐' : '☆')
            .fontSize(32)
            .onClick(() => {
              this.rating = star;
            })
        })
      }
      .margin({ top: 20 })

      // 评分描述
      Text(this.getRatingDescription())
        .fontSize(13)
        .fontColor(this.rating > 0 ? '#FF9800' : '#9E9E9E')
        .margin({ top: 8 })

      // 评价输入
      TextArea({ text: this.comment, placeholder: '请输入您的评价(选填)...' })
        .width('100%')
        .height(80)
        .fontSize(14)
        .fontColor('#E0E0E0')
        .placeholderColor('#9E9E9E')
        .backgroundColor('#2A2A3E')
        .borderRadius(10)
        .padding(12)
        .margin({ top: 16 })
        .onChange((value: string) => {
          this.comment = value;
        })

      // 提交按钮
      Text('提交评价')
        .fontSize(15)
        .fontColor('#FFFFFF')
        .width('100%')
        .textAlign(TextAlign.Center)
        .padding({ top: 12, bottom: 12 })
        .borderRadius(10)
        .backgroundColor(this.rating > 0 ? '#4CAF50' : '#4CAF5044')
        .margin({ top: 16 })
        .onClick(() => {
          if (this.rating > 0) {
            this.onSubmit(this.rating, this.comment);
            this.controller.close();
          }
        })
    }
    .padding(20)
    .borderRadius(16)
    .backgroundColor('#1E1E2E')
  }

  // 获取评分描述
  private getRatingDescription(): string {
    const descriptions: Record<number, string> = {
      0: '请点击星星评分',
      1: '非常不满意 😞',
      2: '不太满意 😐',
      3: '一般般 🙂',
      4: '比较满意 😊',
      5: '非常满意 🤩',
    };
    return descriptions[this.rating] || '';
  }
}

// 主页面
@Entry
@Component
struct CustomDialogDemoPage {
  // 对话框控制器
  private loadingDialogController: CustomDialogController = new CustomDialogController({
    builder: LoadingDialog(),
    autoCancel: false,
    customStyle: true,
    alignment: DialogAlignment.Center,
  });

  private inputDialogController: CustomDialogController = new CustomDialogController({
    builder: InputDialog({
      title: '修改昵称',
      placeholder: '请输入新的昵称',
      onConfirm: (text: string) => {
        this.nickname = text;
      }
    }),
    autoCancel: true,
    customStyle: true,
    alignment: DialogAlignment.Center,
  });

  private ratingDialogController: CustomDialogController = new CustomDialogController({
    builder: RatingDialog({
      onSubmit: (rating: number, comment: string) => {
        this.userRating = rating;
        this.userComment = comment;
      }
    }),
    autoCancel: true,
    customStyle: true,
    alignment: DialogAlignment.Center,
  });

  // 页面状态
  @State nickname: string = '鸿蒙开发者';
  @State userRating: number = 0;
  @State userComment: string = '';

  build() {
    Column() {
      // 标题区域
      Column() {
        Text('自定义对话框实战')
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FFFFFF')
        Text('CustomDialog 完全自定义')
          .fontSize(14)
          .fontColor('#FFFFFFAA')
          .margin({ top: 4 })
      }
      .width('100%')
      .padding({ top: 56, left: 20, right: 20, bottom: 20 })
      .linearGradient({
        direction: GradientDirection.BottomRight,
        colors: [['#1A1A2E', 0], ['#16213E', 1]]
      })

      // 当前状态
      Column({ space: 8 }) {
        Text(`昵称: ${this.nickname}`)
          .fontSize(14)
          .fontColor('#E0E0E0')
        Text(`评分: ${this.userRating > 0 ? '⭐'.repeat(this.userRating) : '未评分'}`)
          .fontSize(14)
          .fontColor('#E0E0E0')
        if (this.userComment) {
          Text(`评价: ${this.userComment}`)
            .fontSize(14)
            .fontColor('#E0E0E0')
        }
      }
      .width('100%')
      .padding(14)
      .margin({ left: 16, right: 16, top: 12 })
      .borderRadius(10)
      .backgroundColor('#1E1E2E')
      .alignItems(HorizontalAlign.Start)

      // 对话框触发按钮
      Text('自定义对话框')
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
        .fontColor('#E0E0E0')
        .margin({ left: 20, top: 20, bottom: 8 })

      Column({ space: 8 }) {
        // 加载对话框
        Row() {
          Text('⏳')
            .fontSize(18)
          Text('加载进度对话框')
            .fontSize(14)
            .fontColor('#E0E0E0')
            .margin({ left: 8 })
          Blank()
          Text('→')
            .fontSize(14)
            .fontColor('#9E9E9E')
        }
        .width('100%')
        .padding(14)
        .borderRadius(10)
        .backgroundColor('#1E1E2E')
        .onClick(() => {
          this.loadingDialogController.open();
        })

        // 输入对话框
        Row() {
          Text('✏️')
            .fontSize(18)
          Text('输入对话框(修改昵称)')
            .fontSize(14)
            .fontColor('#E0E0E0')
            .margin({ left: 8 })
          Blank()
          Text('→')
            .fontSize(14)
            .fontColor('#9E9E9E')
        }
        .width('100%')
        .padding(14)
        .borderRadius(10)
        .backgroundColor('#1E1E2E')
        .onClick(() => {
          this.inputDialogController.open();
        })

        // 评分对话框
        Row() {
          Text('⭐')
            .fontSize(18)
          Text('评分对话框')
            .fontSize(14)
            .fontColor('#E0E0E0')
            .margin({ left: 8 })
          Blank()
          Text('→')
            .fontSize(14)
            .fontColor('#9E9E9E')
        }
        .width('100%')
        .padding(14)
        .borderRadius(10)
        .backgroundColor('#1E1E2E')
        .onClick(() => {
          this.ratingDialogController.open();
        })
      }
      .padding({ left: 16, right: 16 })

      // CustomDialog 要点
      Column() {
        Text('📌 CustomDialog 要点')
          .fontSize(14)
          .fontWeight(FontWeight.Bold)
          .fontColor('#9C27B0')
          .margin({ bottom: 8 })
        Text('• 使用 @CustomDialog 装饰器声明')
          .fontSize(12)
          .fontColor('#BDBDBD')
        Text('• 通过 CustomDialogController 控制显隐')
          .fontSize(12)
          .fontColor('#BDBDBD')
          .margin({ top: 4 })
        Text('• customStyle: true 启用自定义样式')
          .fontSize(12)
          .fontColor('#BDBDBD')
          .margin({ top: 4 })
        Text('• autoCancel: false 禁止点击遮罩关闭')
          .fontSize(12)
          .fontColor('#BDBDBD')
          .margin({ top: 4 })
        Text('• 回调函数通过属性传递,而非事件')
          .fontSize(12)
          .fontColor('#BDBDBD')
          .margin({ top: 4 })
      }
      .width('100%')
      .padding(14)
      .margin({ left: 16, right: 16, top: 16 })
      .borderRadius(10)
      .backgroundColor('#1E1E2E')
      .border({ width: 1, color: '#9C27B033' })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#121212')
  }
}

四、踩坑与注意事项

4.1 AlertDialog 按钮数量限制

AlertDialog 最多支持 3个按钮。如果你传入4个或更多按钮,系统只会显示前3个。如果需要更多选项,请使用 ActionSheet。

4.2 ActionSheet 的回调机制

ActionSheet 的回调有点特殊——callback 参数接收的是按钮索引,而 cancelButtonconfirmationButton 是独立的回调。注意区分:

// callback: 点击选项按钮时触发
// cancelButton: 点击取消按钮时触发
ActionSheet.show({
  buttons: [...],
  callback: (index: number) => {
    // 点击了第 index 个选项
  },
  cancelButton: () => {
    // 点击了取消
  }
});

4.3 TextPickerDialog 的 range 必须是非空数组

如果传入空数组,TextPickerDialog 会直接崩溃。务必在调用前检查数据源:

// ❌ 危险:空数组会导致崩溃
TextPickerDialog.show({ range: [], ... });

// ✅ 安全:先检查
if (this.cities.length > 0) {
  TextPickerDialog.show({ range: this.cities, ... });
}

4.4 DatePickerDialog 的日期范围

minmax 参数必须合理设置。如果 selected 的日期不在 minmax 之间,对话框可能显示异常。建议始终显式设置日期范围。

4.5 CustomDialog 的生命周期

CustomDialog 的 aboutToAppearaboutToDisappear 在对话框打开和关闭时触发。如果对话框中有定时器或异步操作,务必在 aboutToDisappear 中清理,否则会造成内存泄漏。

4.6 CustomDialogController 的限制

CustomDialogController 必须作为组件的成员变量声明,不能在方法内部动态创建。这意味着你不能根据运行时条件动态切换不同的对话框——你需要预先声明所有可能用到的控制器。


五、HarmonyOS 6适配

5.1 API变更

变更项 HarmonyOS 5 HarmonyOS 6
AlertDialog AlertDialog.show() 新增 AlertDialog.showWithOptions() 支持更多配置
ActionSheet ActionSheet.show() 新增 ActionSheet.showWithIcons() 支持图标
TextPickerDialog 单列选择 新增多列联动选择
DatePickerDialog 年月日 新增 TimePickerDialog 时间选择
CustomDialog @CustomDialog 新增 @PromptAction 声明式弹窗

5.2 迁移指南

// HarmonyOS 5 写法
TextPickerDialog.show({
  range: this.cities,
  selected: 0,
  onAccept: (value: string, index: number) => { ... },
  onCancel: () => { ... }
});

// HarmonyOS 6 写法(多列联动)
TextPickerDialog.show({
  range: [this.provinces, this.citiesByProvince], // 多列数据
  selected: [0, 0], // 每列的默认选中
  onAccept: (values: string[], indices: number[]) => { ... },
  onCancel: () => { ... }
});

5.3 新增 TimePickerDialog

HarmonyOS 6 新增了 TimePickerDialog,专门用于时间选择,弥补了之前只能选日期不能选时间的短板。


六、总结

mindmap
  root((系统对话框))
    AlertDialog
      确认/警告场景
      最多3个按钮
      autoCancel控制遮罩关闭
      删除确认/退出确认
    ActionSheet
      列表选择场景
      底部弹出
      callback获取索引
      分享菜单/操作菜单
    TextPickerDialog
      滚轮文本选择
      range数据源
      selected默认选中
      城市选择/分类选择
    DatePickerDialog
      日期滚轮选择
      min/max范围限制
      selected默认日期
      生日选择/预约日期
    CustomDialog
      完全自定义
      @CustomDialog装饰器
      CustomDialogController
      customStyle: true
      回调通过属性传递
    踩坑要点
      按钮数量限制
      空数组崩溃
      日期范围校验
      定时器清理
      Controller不能动态创建
    HarmonyOS 6
      多列联动选择
      TimePickerDialog
      showWithIcons
      @PromptAction
    
    classDef primary fill:#4CAF50,stroke:#388E3C,color:#fff
    classDef warning fill:#FF9800,stroke:#F57C00,color:#fff
    classDef error fill:#F44336,stroke:#D32F2F,color:#fff
    classDef info fill:#2196F3,stroke:#1976D2,color:#fff
    classDef purple fill:#9C27B0,stroke:#7B1FA2,color:#fff
对话框 适用场景 核心参数 注意事项
AlertDialog 确认/警告 title, message, buttons 最多3个按钮
ActionSheet 列表选择 title, buttons, callback 区分callback和cancelButton
TextPickerDialog 文本滚轮 range, selected, onAccept range不能为空
DatePickerDialog 日期选择 selected, min, max 设置合理的日期范围
CustomDialog 完全自定义 @CustomDialog, Controller 清理定时器,Controller不能动态创建

系统对话框是应用交互的"第一道门面"。用对了,用户体验丝滑流畅;用错了,用户可能一脸懵逼。记住一个原则:能用系统对话框解决的,就不要自己造轮子。系统对话框经过充分测试和适配,在所有设备上都能保证一致的体验。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。