1# 弹窗的使用
2
3## 场景说明
4应用中经常用到弹窗,比如警告弹窗、日期选择弹窗、文本选择弹窗以及其他自定义弹窗等等。本例将为大家介绍如何使用不同的弹窗。
5
6## 效果呈现
7本例最终效果如下:
8
9![multi-dialogue](figures/multi-dialogue.gif)
10
11示例中共涉及四类弹窗:
12- 警告弹窗:提示信息尚未保存。
13- 日期滑动选择器弹窗:选择出生日期。
14- 文本滑动选择器弹窗:选择性别。
15- 自定义弹窗:填写兴趣爱好。
16
17> ![icon-note.gif](../device-dev/public_sys-resources/icon-note.gif) **说明:**
18> 自定义弹窗可以根据业务需要自行定义弹窗的形式和内容,比如文本输入、单选、多选等等,本例以文本输入为例进行介绍。
19
20## 运行环境
21本例基于以下环境开发,开发者也可以基于其他适配的版本进行开发:
22
23- IDE: DevEco Studio 3.1 Release
24- SDK: Ohos_sdk_public 3.2.12.5(API Version 9 Release)
25
26
27## 实现思路
28本例中涉及的4类弹窗及实现方案如下:
29- 警告弹窗:使用AlertDialog实现。
30- 日期滑动选择器弹窗:使用DatePickerDialog实现。
31- 文本滑动选择器弹窗:使用TextPickerDialog实现。
32- 自定义弹窗:使用CustomDialogController实现。
33
34## 开发步骤
35由于本例重点讲解对话框的使用,所以开发步骤会着重讲解相关实现,不相关的内容不做介绍,全量代码可参考完整代码章节。
361. 首先,使用AlertDialog实现警告弹窗。
37
38    通过message参数设置告警信息,alignment设置弹窗在界面中垂直方向的对齐方式;通过primaryButton和secondaryButton添加按钮。
39    具体代码如下:
40
41    ```ts
42    alertDialog(context: Context.UIAbilityContext) {
43      AlertDialog.show({
44        // 通过message设置告警信息
45        message: '当前数据未保存,是否确认离开?',
46        // 通过alignment设置弹窗在界面垂直方向的对齐方式,此处设置为底部对齐
47        alignment: DialogAlignment.Bottom,
48        // 通过offset设置基于对齐位置的便宜量
49        offset: {
50          dx: 0,
51          dy: -20
52        },
53        // 弹窗中左起第一个按钮
54        primaryButton: {
55          value: '取消',
56          action: () => {
57            console.info('Callback cancel button is clicked');
58          }
59        },
60        // 弹窗中左起第二个按钮
61        secondaryButton: {
62          value: '确定',
63          action: () => {
64            // Exiting the app.
65            context.terminateSelf();
66            console.info('Callback definite button is clicked');
67          }
68        }
69      });
70    }
71    ```
722. 使用DatePickerDialog实现日期滑动选择器弹窗。
73
74    通过start和end分别设置日期区间的起始时间和末尾时间;通过lunar设置使用农历还是阳历;使用onAccept监听选择的日期,本例中通过变量selectedDate将选中的日期设置给参数selected,这样弹窗弹出时的日期就默认为上次选中的日期。
75    具体代码如下:
76
77    ```ts
78    datePickerDialog(dateCallback) {
79      DatePickerDialog.show({
80        start: new Date('1900-1-1'),
81        end: new Date('2100-1-1'),
82        // 通过变量selectedDate将选中的日期设置给参数selected
83        selected: this.selectedDate,
84        lunar: false,
85        // 使用onAccept监听选择的日期
86        onAccept: (value: DatePickerResult) => {
87          let year = value.year;
88          let month = value.month + 1;
89          let day = value.day;
90          let birthdate: string = this.getBirthDateValue(year, month, day);
91          // 通过setFullYear将选中的日期传递给变量selectedDate
92          this.selectedDate.setFullYear(value.year, value.month, value.day)
93          // 返回选中的日期
94          dateCallback(birthdate);
95        }
96      });
97    }
98    ```
993. 使用TextPickerDialog实现文本滑动选择器弹窗。
100
101    通过range设置文本选择项,使用onAccept监听选择的文本项,本例中通过变量selectedGender将选中的性别的索引设置给参数selected,这样弹窗弹出时的性别就默认为上次选中的性别。
102    具体代码如下:
103
104    ```ts
105    textPickerDialog(sexArray: Resource, sexCallback) {
106      // 判断文本项的列表是否为空
107      if (this.isEmptyArr(sexArray)) {
108        console.error('sex is null');
109        return;
110      }
111      TextPickerDialog.show({
112        // 通过range设置文本选择项
113        range: sexArray,
114        // 通过变量selectedGender将选中的性别的索引设置给参数selected
115        selected: this.selectedGender,
116        // 使用onAccept监听选择的文本项
117        onAccept: (result: TextPickerResult) => {
118          sexCallback(result.value);
119          // 获取选中项的索引
120          this.selectedGender = result.index
121        },
122        onCancel: () => {
123          console.info('TextPickerDialog onCancel');
124        }
125      });
126    }
127    ```
1284. 使用CustomDialogController实现自定义弹窗。
129
130    当现有弹窗不能满足业务诉求时,开发者可以自行设计弹窗的样式。在实现自定义弹窗时,需要将弹窗的UI放在被@CustomDialog修饰的自定义组件中,然后使用CustomDialogController的实例来控制弹窗的弹出和关闭。
131    具体代码如下:
132    ```ts
133    // 使用@CustomDialog修饰自定义弹窗
134    @CustomDialog
135    struct CustomDialogFrame{
136      ...
137      // 定义CustomDialogController
138      controller: CustomDialogController
139
140      build(){
141        Column() {
142          Text('兴趣爱好').fontSize(20).margin({ top: 10, bottom: 10 })
143          TextInput({ placeholder: '', text: this.textValue }).height(60).width('90%')
144            .onChange((value: string) => {
145              this.textValue = value
146            })
147          Flex({ justifyContent: FlexAlign.SpaceAround }) {
148            Button('取消')
149              .onClick(() => {
150                // 点击‘取消’,弹窗关闭
151                this.controller.close()
152              })
153              .backgroundColor('')
154              .fontColor('#007DFF')
155            Button('保存')
156              .onClick(() => {
157                this.inputValue = this.textValue
158                // 点击‘保存’,弹窗关闭
159                this.controller.close()
160              })
161              .backgroundColor(0xffffff)
162              .fontColor('#007DFF')
163          }.margin({ bottom: 10 })
164        }.justifyContent(FlexAlign.Start)
165      }
166    }
167    ...
168      // 实例化自定义弹窗
169      customDialogController: CustomDialogController = new CustomDialogController({
170        // 使用上文创建的自定义弹窗进行实例化
171        builder: CustomDialogFrame({
172          textValue: $textValue,
173          inputValue: $inputValue
174        }),
175        alignment: DialogAlignment.Bottom,
176        offset: {
177          dx: 0,
178          dy: -20
179        }
180      });
181    ...
182    ```
183## 完整代码
184本例完整代码如下:
185```ts
186import Context from '@ohos.app.ability.common';
187import hilog from '@ohos.hilog';
188
189@Component
190struct TextFrame{
191  @Link content: string;
192  private textImage:Resource;
193  private text:string;
194  onTextClick:()=>void;
195
196  build(){
197    Row(){
198      Image(this.textImage)
199        .width(24)
200        .height(24)
201        .margin({left:12})
202      Text(this.text)
203        .fontSize(16)
204        .margin({ left:12 })
205        .height(24)
206      Text(this.content)
207        .fontSize(16)
208        .textAlign(TextAlign.End)
209        .textOverflow({ overflow: TextOverflow.Ellipsis })
210        .maxLines(1)
211        .margin({
212          left: 16,
213          right: 7
214        })
215        .layoutWeight(1)
216        .width('100%')
217      Image($r('app.media.ic_arrow'))
218        .width(12)
219        .height(24)
220        .margin({ right: 14 })
221    }
222    .margin({ top: 24 })
223    .borderRadius(24)
224    .backgroundColor(Color.White)
225    .width('93.3%')
226    .height(64)
227    .onClick(this.onTextClick)
228  }
229}
230
231@Component
232struct InputFrame{
233  private inputImage: Resource;
234  private hintText: string;
235
236  build(){
237    Row() {
238      Image(this.inputImage)
239        .width(24)
240        .height(24)
241        .margin({ left: 12 })
242      TextInput({ placeholder: this.hintText })
243        .fontSize(16)
244        .padding({ left: 12 })
245        .placeholderColor('#99000000')
246        .backgroundColor(Color.White)
247        .fontWeight(FontWeight.Normal)
248        .fontStyle(FontStyle.Normal)
249        .fontColor(Color.Black)
250        .margin({ right: 32 })
251        .layoutWeight(1)
252        .height(48)
253    }
254    .margin({ top: 24 })
255    .borderRadius(24)
256    .backgroundColor(Color.White)
257    .width('93.3%')
258    .height(64)
259  }
260}
261
262@CustomDialog
263struct CustomDialogFrame{
264  @Link textValue: string
265  @Link inputValue: string
266  controller: CustomDialogController
267
268  build(){
269    Column() {
270      Text('兴趣爱好').fontSize(20).margin({ top: 10, bottom: 10 })
271      TextInput({ placeholder: '', text: this.textValue }).height(60).width('90%')
272        .onChange((value: string) => {
273          this.textValue = value
274        })
275      Flex({ justifyContent: FlexAlign.SpaceAround }) {
276        Button('取消')
277          .onClick(() => {
278            this.controller.close()
279          }).backgroundColor('').fontColor('#007DFF')
280        Button('保存')
281          .onClick(() => {
282            this.inputValue = this.textValue
283            this.controller.close()
284          }).backgroundColor(0xffffff).fontColor('#007DFF')
285      }.margin({ bottom: 10 })
286    }.justifyContent(FlexAlign.Start)
287  }
288}
289
290@Entry
291@Component
292struct Index {
293  @State birthdate: string = '';
294  @State sex: string = '';
295  @State textValue: string = '';
296  @State inputValue: string = '';
297  selectedDate: Date = new Date("2010-1-1")
298  selectedGender:number = 0
299  private sexArray: Resource = $r('app.strarray.sex_array');
300  customDialogController: CustomDialogController = new CustomDialogController({
301    builder: CustomDialogFrame({
302      textValue: $textValue,
303      inputValue: $inputValue
304    }),
305    alignment: DialogAlignment.Bottom,
306    offset: {
307      dx: 0,
308      dy: -20
309    }
310  });
311
312  alertDialog(context: Context.UIAbilityContext) {
313    AlertDialog.show({
314      message: '当前数据未保存,是否确认离开?',
315      alignment: DialogAlignment.Bottom,
316      offset: {
317        dx: 0,
318        dy: -20
319      },
320      primaryButton: {
321        value: '取消',
322        action: () => {
323          console.info('Callback cancel button is clicked');
324        }
325      },
326      secondaryButton: {
327        value: '确定',
328        action: () => {
329          // Exiting the app.
330          context.terminateSelf();
331          console.info('Callback definite button is clicked');
332        }
333      }
334    });
335  }
336
337  datePickerDialog(dateCallback) {
338    DatePickerDialog.show({
339      start: new Date('1900-1-1'),
340      end: new Date('2100-1-1'),
341      selected: this.selectedDate,
342      lunar: false,
343      onAccept: (value: DatePickerResult) => {
344        let year = value.year;
345        let month = value.month + 1;
346        let day = value.day;
347        let birthdate: string = this.getBirthDateValue(year, month, day);
348        this.selectedDate.setFullYear(value.year, value.month, value.day)
349        dateCallback(birthdate);
350      }
351    });
352  }
353
354  textPickerDialog(sexArray: Resource, sexCallback) {
355    if (this.isEmptyArr(sexArray)) {
356      console.error('sex is null');
357      return;
358    }
359    TextPickerDialog.show({
360      range: sexArray,
361      selected: this.selectedGender,
362      onAccept: (result: TextPickerResult) => {
363        sexCallback(result.value);
364        this.selectedGender = result.index
365      },
366      onCancel: () => {
367        console.info('TextPickerDialog onCancel');
368      }
369    });
370  }
371
372  getBirthDateValue(year: number, month: number, day: number): string {
373    let birthdate: string = `${year}${'年'}${month}` +
374    `${'月'}${day}${'日'}`;
375    return birthdate;
376  }
377
378  isEmpty(obj): boolean {
379    return obj === undefined || obj === null || obj === '';
380  }
381
382  isEmptyArr(array): boolean {
383    return this.isEmpty(array) || array.length === 0;
384  }
385
386  build() {
387    Row() {
388      Column() {
389        Row(){
390          Image($r('app.media.ic_back'))
391            .width(26)
392            .height(26)
393            .alignSelf(ItemAlign.Start)
394            .margin({
395              left: '7.2%',
396              top: 19
397            })
398            .onClick(() => {
399              let context = getContext(this) as Context.UIAbilityContext;
400              this.alertDialog(context);
401            })
402          Text('个人信息')
403            .fontColor(Color.Black)
404            .fontSize(20)
405            .margin({ top: 20,left:20 })
406            .alignSelf(ItemAlign.Center)
407        }.width('100%')
408        Image($r('app.media.ic_avatar'))
409          .width(56)
410          .height(56)
411          .alignSelf(ItemAlign.Center)
412          .margin({ top: '5.5%' })
413        Text('头像')
414          .fontColor(Color.Black)
415          .fontSize(16)
416          .margin({ top: '2.1%' })
417          .alignSelf(ItemAlign.Center)
418        InputFrame({
419          inputImage: $r('app.media.ic_nickname'),
420          hintText: '昵称'
421        })
422        TextFrame({
423          textImage: $r('app.media.ic_birthdate'),
424          text: '出生日期',
425          content: $birthdate,
426          onTextClick: () => {
427            this.datePickerDialog((birthValue: string) => {
428              this.birthdate = birthValue;
429            });
430          }
431        })
432        TextFrame({
433          textImage: $r('app.media.ic_sex'),
434          text: '性别',
435          content: $sex,
436          onTextClick: () => {
437            this.textPickerDialog(this.sexArray, (sexValue: string) => {
438              this.sex = sexValue;
439            });
440          }
441        })
442        InputFrame({
443          inputImage: $r('app.media.ic_signature'),
444          hintText: '个性签名'
445        })
446        TextFrame({
447          textImage: $r('app.media.ic_hobbies'),
448          text: '兴趣爱好',
449          content: $textValue,
450          onTextClick: () => {
451            this.customDialogController.open();
452          }
453        })
454      }
455      .backgroundColor('#F5F5F5')
456      .height('100%')
457      .width('100%')
458    }
459    .height('100%')
460  }
461}
462```
463## 参考
464- [警告弹窗](../application-dev/reference/apis-arkui/arkui-ts/ts-methods-alert-dialog-box.md)
465- [日期滑动选择器弹窗](../application-dev/reference/apis-arkui/arkui-ts/ts-methods-datepicker-dialog.md)
466- [文本滑动选择器弹窗](../application-dev/reference/apis-arkui/arkui-ts/ts-methods-textpicker-dialog.md)
467- [自定义弹窗](../application-dev/ui/arkts-common-components-custom-dialog.md)