問題描述
應用中需要頻繁使用確認對話框、選擇器對話框等,如何封裝通用的 Dialog 組件避免重複代碼?如何實現優雅的回調處理?
關鍵字: CustomDialog、對話框封裝、組件複用、回調處理、UI 組件
解決方案
完整代碼
/**
* 確認對話框
*/
@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
export struct SelectDialog {
controller: CustomDialogController;
title: string = '請選擇';
options: string[] = [];
selectedIndex: number = 0;
onSelect?: (index: number, value: string) => void;
@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
export struct InputDialog {
controller: CustomDialogController;
title: string = '輸入';
placeholder: string = '請輸入';
defaultValue: string = '';
inputType: InputType = InputType.Normal;
maxLength: number = 50;
onConfirm?: (value: string) => void;
@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
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
export struct ConfirmDialog {
controller: CustomDialogController;
}
- 標記為自定義對話框組件
- 必須包含 controller 屬性
- 通過 controller 控制顯示/隱藏
2. 回調函數傳遞
onConfirm?: () => void;
- 使用可選屬性定義回調
- 調用前檢查是否存在
- 支持傳遞參數
3. @State 狀態管理
@State currentIndex: number = 0;
- 對話框內部狀態
- 響應用户交互
- 觸發 UI 更新
最佳實踐
- 統一風格: 所有對話框使用相同的樣式和動畫
- 回調處理: 使用可選回調,調用前檢查
- 自動關閉: 設置 autoCancel: true 支持點擊外部關閉
- 內存管理: 對話框關閉後 controller 置 null
- 異步操作: 加載對話框配合 async/await 使用
避坑指南
- 忘記 close: 必須手動調用 controller.close()
- 重複打開: 打開前檢查 controller 是否已存在
- 內存泄漏: 組件銷燬時關閉所有對話框
- 回調丟失: 箭頭函數保持 this 指向
- 樣式覆蓋: 使用 width/height 限制對話框大小
效果展示
- 確認對話框:標題 + 消息 + 雙按鈕
- 選擇對話框:標題 + 列表 + 確定按鈕
- 輸入對話框:標題 + 輸入框 + 雙按鈕
- 加載對話框:加載動畫 + 提示文字
相關資源
- 鴻蒙學習資源