1# 基础自定义弹出框 (CustomDialog)(不推荐) 2<!--Kit: ArkUI--> 3<!--Subsystem: ArkUI--> 4<!--Owner: @houguobiao--> 5<!--Designer: @houguobiao--> 6<!--Tester: @lxl007--> 7<!--Adviser: @HelloCrease--> 8CustomDialog是自定义弹出框,可用于广告、中奖、警告、软件更新等与用户交互响应操作。开发者可以通过CustomDialogController类显示自定义弹出框。具体用法请参考[自定义弹出框](../reference/apis-arkui/arkui-ts/ts-methods-custom-dialog-box.md)。 9 10> **说明:** 11> 12> 当前,ArkUI弹出框默认为非页面级弹出框,在页面路由跳转时,如果开发者未调用close方法将其关闭,弹出框将不会自动关闭。若需实现在跳转页面时覆盖弹出框的场景,可以使用[组件导航子页面显示类型的弹窗类型](arkts-navigation-navigation.md#页面显示类型)或者[页面级弹出框](arkts-embedded-dialog.md)。 13 14默认为模态弹窗且有蒙层,不可与蒙层下方控件进行交互(不支持点击和手势等向下透传)。可以通过配置[CustomDialogControllerOptions](../reference/apis-arkui/arkui-ts/ts-methods-custom-dialog-box.md#customdialogcontrolleroptions对象说明)中的isModal属性来实现模态和非模态弹窗,详细说明可参考[弹窗的种类](arkts-dialog-overview.md#弹窗的种类)。 15 16当isModal为true时,弹出框为模态弹窗,且弹窗周围的蒙层区不支持透传。isModal为false时,弹出框为非模态弹窗,且弹窗周围的蒙层区可以透传。因此如果需要同时允许弹出框的交互和弹出框外页面的交互行为,需要将弹出框设置为非模态。 17 18## 生命周期 19 20从API version 19开始,自定义弹出框提供了生命周期函数用于通知用户该弹出框的生命周期。生命周期的触发时序依次为:onWillAppear -> onDidAppear -> onWillDisappear -> onDidDisappear。 21 22| 名称 |类型| 说明 | 23| ----------------- | ------ | ---------------------------- | 24| onWillAppear | Callback<void> | 弹出框显示动效前的事件回调。 | 25| onDidAppear | Callback<void> | 弹出框弹出后的事件回调。 | 26| onWillDisappear | Callback<void> | 弹出框退出动效前的事件回调。 | 27| onDidDisappear | Callback<void> | 弹出框消失后的事件回调。 | 28 29## 创建自定义弹出框 30 311. 使用\@CustomDialog装饰器装饰自定义弹出框,可在此装饰器内自定义弹出框内容。CustomDialogController需在@Component内定义。 32 33 ```ts 34 @CustomDialog 35 struct CustomDialogExample { 36 controller: CustomDialogController 37 38 build() { 39 Column() { 40 Text('我是内容') 41 .fontSize(20) 42 }.height(60).justifyContent(FlexAlign.Center) 43 } 44 } 45 ``` 462. 创建构造器,与装饰器呼应相连。 47 48 ```ts 49 @Entry 50 @Component 51 struct CustomDialogUser { 52 dialogController: CustomDialogController = new CustomDialogController({ 53 builder: CustomDialogExample(), 54 }) 55 } 56 ``` 573. 点击与onClick事件绑定的组件使弹出框弹出。 58 59 ```ts 60 @Entry 61 @Component 62 struct CustomDialogUser { 63 dialogController: CustomDialogController = new CustomDialogController({ 64 builder: CustomDialogExample(), 65 }) 66 67 build() { 68 Column() { 69 Button('click me') 70 .onClick(() => { 71 this.dialogController.open(); 72 }) 73 }.width('100%').margin({ top: 5 }) 74 } 75 } 76 ``` 77 78  79 80## 弹出框的交互 81 82弹出框可用于数据交互,完成用户一系列响应操作。 83 841. 在\@CustomDialog装饰器内添加按钮和数据函数。 85 86 ```ts 87 @CustomDialog 88 struct CustomDialogExample { 89 cancel: () => void = () => { 90 } 91 confirm: () => void = () => { 92 } 93 controller: CustomDialogController; 94 95 build() { 96 Column() { 97 Text('我是内容').fontSize(20).margin({ top: 10, bottom: 10 }) 98 Flex({ justifyContent: FlexAlign.SpaceAround }) { 99 Button('cancel') 100 .onClick(() => { 101 this.controller.close(); 102 if (this.cancel) { 103 this.cancel(); 104 } 105 }).backgroundColor(0xffffff).fontColor(Color.Black) 106 Button('confirm') 107 .onClick(() => { 108 this.controller.close(); 109 if (this.confirm) { 110 this.confirm(); 111 } 112 }).backgroundColor(0xffffff).fontColor(Color.Red) 113 }.margin({ bottom: 10 }) 114 } 115 } 116 } 117 ``` 1182. 页面内需要在构造器内进行接收,同时创建相应的函数操作。 119 120 ```ts 121 @Entry 122 @Component 123 struct CustomDialogUser { 124 dialogController: CustomDialogController = new CustomDialogController({ 125 builder: CustomDialogExample({ 126 cancel: ()=> { this.onCancel() }, 127 confirm: ()=> { this.onAccept() }, 128 }), 129 }); 130 131 onCancel() { 132 console.info('Callback when the first button is clicked'); 133 } 134 135 onAccept() { 136 console.info('Callback when the second button is clicked'); 137 } 138 139 build() { 140 Column() { 141 Button('click me') 142 .onClick(() => { 143 this.dialogController.open(); 144 }) 145 }.width('100%').margin({ top: 5 }) 146 } 147 } 148 ``` 149 150  151 152 3.可通过弹出框中的按钮实现路由跳转,同时获取跳转页面向当前页传入的参数。 153 154 ```ts 155 // Index.ets 156 @CustomDialog 157 struct CustomDialogExample { 158 @Link textValue: string; 159 controller?: CustomDialogController; 160 cancel: () => void = () => { 161 } 162 confirm: () => void = () => { 163 } 164 165 build() { 166 Column({ space: 20 }) { 167 if (this.textValue != '') { 168 Text(`第二个页面的内容为:${this.textValue}`) 169 .fontSize(20) 170 } else { 171 Text('是否获取第二个页面的内容') 172 .fontSize(20) 173 } 174 Flex({ justifyContent: FlexAlign.SpaceAround }) { 175 Button('cancel') 176 .onClick(() => { 177 if (this.controller != undefined) { 178 this.controller.close(); 179 this.cancel(); 180 } 181 }).backgroundColor(0xffffff).fontColor(Color.Black) 182 Button('confirm') 183 .onClick(() => { 184 if (this.controller != undefined && this.textValue != '') { 185 this.controller.close(); 186 } else if (this.controller != undefined) { 187 this.getUIContext().getRouter().pushUrl({ 188 url: 'pages/Index2' 189 }); 190 this.controller.close(); 191 } 192 }).backgroundColor(0xffffff).fontColor(Color.Red) 193 }.margin({ bottom: 10 }) 194 }.borderRadius(10).padding({ top: 20 }) 195 } 196 } 197 198 @Entry 199 @Component 200 struct CustomDialogUser { 201 @State textValue: string = ''; 202 dialogController: CustomDialogController | null = new CustomDialogController({ 203 builder: CustomDialogExample({ 204 cancel: () => { 205 this.onCancel() 206 }, 207 confirm: () => { 208 this.onAccept() 209 }, 210 textValue: this.textValue 211 }) 212 }); 213 214 // 在自定义组件即将析构销毁时将dialogController置空 215 aboutToDisappear() { 216 this.dialogController = null; // 将dialogController置空 217 } 218 219 onPageShow() { 220 const params = this.getUIContext().getRouter().getParams() as Record<string, string>; // 获取传递过来的参数对象 221 if (params) { 222 this.dialogController?.open(); 223 this.textValue = params.info as string; // 获取info属性的值 224 } 225 } 226 227 onCancel() { 228 console.info('Callback when the first button is clicked'); 229 } 230 231 onAccept() { 232 console.info('Callback when the second button is clicked'); 233 } 234 235 exitApp() { 236 console.info('Click the callback in the blank area'); 237 } 238 239 build() { 240 Column() { 241 Button('click me') 242 .onClick(() => { 243 if (this.dialogController != null) { 244 this.dialogController.open(); 245 } 246 }).backgroundColor(0x317aff) 247 }.width('100%').margin({ top: 5 }) 248 } 249 } 250 ``` 251 252 ```ts 253 // Index2.ets 254 @Entry 255 @Component 256 struct Index2 { 257 @State message: string = '点击返回'; 258 259 build() { 260 Column() { 261 Button(this.message) 262 .type(ButtonType.Capsule) 263 .onClick(() => { 264 this.getUIContext().getRouter().back({ 265 url: 'pages/Index', 266 params: { 267 info: 'Hello World' 268 } 269 }); 270 }) 271 }.width('100%').height('100%').margin({ top: 20 }) 272 } 273 } 274 ``` 275 276  277 278## 弹出框的动画 279 280弹出框通过定义openAnimation控制弹出框出现动画的持续时间,速度等参数。 281 282```ts 283@CustomDialog 284struct CustomDialogExample { 285 controller?: CustomDialogController; 286 287 build() { 288 Column() { 289 Text('Whether to change a text?').fontSize(16).margin({ bottom: 10 }) 290 } 291 } 292} 293 294@Entry 295@Component 296struct CustomDialogUser { 297 @State textValue: string = ''; 298 @State inputValue: string = 'click me'; 299 dialogController: CustomDialogController | null = new CustomDialogController({ 300 builder: CustomDialogExample(), 301 openAnimation: { 302 duration: 1200, 303 curve: Curve.Friction, 304 delay: 500, 305 playMode: PlayMode.Alternate, 306 onFinish: () => { 307 console.info('play end') 308 } 309 }, 310 autoCancel: true, 311 alignment: DialogAlignment.Bottom, 312 offset: { dx: 0, dy: -20 }, 313 gridCount: 4, 314 customStyle: false, 315 backgroundColor: 0xd9ffffff, 316 cornerRadius: 10, 317 }); 318 319 // 在自定义组件即将析构销毁时将dialogController置空 320 aboutToDisappear() { 321 this.dialogController = null; // 将dialogController置空 322 } 323 324 build() { 325 Column() { 326 Button(this.inputValue) 327 .onClick(() => { 328 if (this.dialogController != null) { 329 this.dialogController.open(); 330 } 331 }).backgroundColor(0x317aff) 332 }.width('100%').margin({ top: 5 }) 333 } 334} 335``` 336 337 338 339## 弹出框的样式 340 341通过定义弹出框的宽度、高度、背景色、阴影等参数,控制其样式。 342 343```ts 344@CustomDialog 345struct CustomDialogExample { 346 controller?: CustomDialogController; 347 348 build() { 349 Column() { 350 Text('我是内容').fontSize(16).margin({ bottom: 10 }) 351 } 352 } 353} 354 355@Entry 356@Component 357struct CustomDialogUser { 358 @State textValue: string = ''; 359 @State inputValue: string = 'click me'; 360 dialogController: CustomDialogController | null = new CustomDialogController({ 361 builder: CustomDialogExample(), 362 autoCancel: true, 363 alignment: DialogAlignment.Center, 364 offset: { dx: 0, dy: -20 }, 365 gridCount: 4, 366 customStyle: false, 367 backgroundColor: 0xd9ffffff, 368 cornerRadius: 20, 369 width: '80%', 370 height: '100px', 371 borderWidth: 1, 372 borderStyle: BorderStyle.Dashed,//使用borderStyle属性,需要和borderWidth属性一起使用 373 borderColor: Color.Blue,//使用borderColor属性,需要和borderWidth属性一起使用 374 shadow: ({ radius: 20, color: Color.Grey, offsetX: 50, offsetY: 0}), 375 }); 376 377 // 在自定义组件即将析构销毁时将dialogController置空 378 aboutToDisappear() { 379 this.dialogController = null; // 将dialogController置空 380 } 381 382 build() { 383 Column() { 384 Button(this.inputValue) 385 .onClick(() => { 386 if (this.dialogController != null) { 387 this.dialogController.open(); 388 } 389 }).backgroundColor(0x317aff) 390 }.width('100%').margin({ top: 5 }) 391 } 392} 393``` 394 395 396 397## 嵌套自定义弹出框 398 399通过第一个弹出框打开第二个弹出框时,最好将第二个弹出框定义在第一个弹出框的父组件处,通过父组件传给第一个弹出框的回调来打开第二个弹出框。 400 401```ts 402@CustomDialog 403struct CustomDialogExampleTwo { 404 controllerTwo?: CustomDialogController; 405 @State message: string = "I'm the second dialog box."; 406 @State showIf: boolean = false; 407 408 build() { 409 Column() { 410 if (this.showIf) { 411 Text("Text") 412 .fontSize(30) 413 .height(100) 414 } 415 Text(this.message) 416 .fontSize(30) 417 .height(100) 418 Button("Create Text") 419 .onClick(() => { 420 this.showIf = true; 421 }) 422 Button('Close Second Dialog Box') 423 .onClick(() => { 424 if (this.controllerTwo != undefined) { 425 this.controllerTwo.close(); 426 } 427 }) 428 .margin(20) 429 } 430 } 431} 432 433@CustomDialog 434struct CustomDialogExample { 435 openSecondBox?: () => void 436 controller?: CustomDialogController 437 438 build() { 439 Column() { 440 Button('Open Second Dialog Box and close this box') 441 .onClick(() => { 442 this.controller!.close(); 443 this.openSecondBox!(); 444 }) 445 .margin(20) 446 }.borderRadius(10) 447 } 448} 449 450@Entry 451@Component 452struct CustomDialogUser { 453 @State inputValue: string = 'Click Me'; 454 dialogController: CustomDialogController | null = new CustomDialogController({ 455 builder: CustomDialogExample({ 456 openSecondBox: () => { 457 if (this.dialogControllerTwo != null) { 458 this.dialogControllerTwo.open() 459 } 460 } 461 }), 462 cancel: this.exitApp, 463 autoCancel: true, 464 alignment: DialogAlignment.Bottom, 465 offset: { dx: 0, dy: -20 }, 466 gridCount: 4, 467 customStyle: false 468 }); 469 dialogControllerTwo: CustomDialogController | null = new CustomDialogController({ 470 builder: CustomDialogExampleTwo(), 471 alignment: DialogAlignment.Bottom, 472 offset: { dx: 0, dy: -25 } 473 }); 474 475 aboutToDisappear() { 476 this.dialogController = null; 477 this.dialogControllerTwo = null; 478 } 479 480 onCancel() { 481 console.info('Callback when the first button is clicked'); 482 } 483 484 onAccept() { 485 console.info('Callback when the second button is clicked'); 486 } 487 488 exitApp() { 489 console.info('Click the callback in the blank area'); 490 } 491 492 build() { 493 Column() { 494 Button(this.inputValue) 495 .onClick(() => { 496 if (this.dialogController != null) { 497 this.dialogController.open(); 498 } 499 }).backgroundColor(0x317aff) 500 }.width('100%').margin({ top: 5 }) 501 } 502} 503``` 504 505 506 507由于自定义弹出框在状态管理侧有父子关系,如果将第二个弹出框定义在第一个弹出框内,那么当父组件(第一个弹出框)被销毁(关闭)时,子组件(第二个弹出框)内无法再继续创建新的组件。 508 509## 实现弹出框的物理返回拦截 510 511执行点击遮障层关闭、侧滑(左滑或右滑)、三键Back、键盘ESC关闭等交互操作时,如果注册了[CustomDialogControllerOptions](../reference/apis-arkui/arkui-ts/ts-methods-custom-dialog-box.md#customdialogcontrolleroptions对象说明)中的onWillDismiss回调函数,弹出框不会立即关闭。在回调函数中,通过[DismissDialogAction](../reference/apis-arkui/arkui-ts/ts-methods-custom-dialog-box.md#dismissdialogaction12)中的reason属性获取阻拦关闭弹出框的操作类型,根据原因决定是否关闭弹出框。 512 513```ts 514@CustomDialog 515struct CustomDialogExample { 516 cancel: () => void = () => { 517 } 518 confirm: () => void = () => { 519 } 520 controller?: CustomDialogController; 521 522 build() { 523 Column() { 524 Text('Are you sure?') 525 .fontSize(20) 526 .margin({ 527 top: 10, 528 bottom: 10 529 }) 530 Row() { 531 Button('cancel') 532 .onClick(() => { 533 if (this.controller != undefined) { 534 this.controller.close(); 535 } 536 }) 537 .backgroundColor(0xffffff) 538 .fontColor(Color.Black) 539 Button('confirm') 540 .onClick(() => { 541 if (this.controller != undefined) { 542 this.controller.close(); 543 } 544 }) 545 .backgroundColor(0xffffff) 546 .fontColor(Color.Red) 547 } 548 .width('100%') 549 .justifyContent(FlexAlign.SpaceAround) 550 .margin({ bottom: 10 }) 551 } 552 } 553} 554 555@Entry 556@Component 557struct InterceptCustomDialog { 558 dialogController: CustomDialogController = new CustomDialogController({ 559 builder: CustomDialogExample({ 560 cancel: () => { 561 this.onCancel(); 562 }, 563 confirm: () => { 564 this.onAccept(); 565 } 566 }), 567 onWillDismiss: (dismissDialogAction: DismissDialogAction) => { 568 console.info('dialog onWillDismiss reason: ' + dismissDialogAction.reason); 569 // 1、PRESS_BACK 点击三键back、侧滑(左滑/右滑)、键盘ESC。 570 // 2、TOUCH_OUTSIDE 点击遮障层时 571 // 3、CLOSE_BUTTON 点击关闭按钮 572 if (dismissDialogAction.reason === DismissReason.PRESS_BACK) { 573 // 处理业务逻辑后通过dismiss主动关闭对话框 574 // dismissDialogAction.dismiss(); 575 } 576 if (dismissDialogAction.reason === DismissReason.TOUCH_OUTSIDE) { 577 // dismissDialogAction.dismiss(); 578 } 579 }, 580 alignment: DialogAlignment.Bottom, 581 offset: { dx: 0, dy: -20 } 582 }) 583 584 onCancel() { 585 console.info('Callback when the first button is clicked'); 586 } 587 588 onAccept() { 589 console.info('Callback when the second button is clicked'); 590 } 591 592 build() { 593 Column() { 594 Button('click me') 595 .onClick(() => { 596 this.dialogController.open(); 597 }) 598 } 599 .width('100%') 600 } 601} 602``` 603 604 605 606## 设置弹出框避让软键盘的距离 607 608为显示弹出框的独立性,弹出框弹出时会与周边进行避让,包括状态栏、导航条以及键盘等留有间距。故当软键盘弹出时,默认情况下,弹出框会自动避开软键盘,并与之保持16vp的距离。从API version 15开始,开发者可以利用[CustomDialogControllerOptions](../reference/apis-arkui/arkui-ts/ts-methods-custom-dialog-box.md#customdialogcontrolleroptions对象说明)中的keyboardAvoidMode和keyboardAvoidDistance这两个配置项,来设置弹出框在软键盘弹出时的行为,包括是否需要避开软键盘以及与软键盘之间的距离。 609设置软键盘间距时,需要将keyboardAvoidMode值设为KeyboardAvoidMode.DEFAULT。 610 611```ts 612// xxx.ets 613import { LengthMetrics } from '@kit.ArkUI' 614 615@CustomDialog 616struct CustomDialogExample { 617 controller?: CustomDialogController; 618 build() { 619 Column() { 620 Column() { 621 Text('keyboardAvoidDistance: 0vp') 622 .fontSize(20) 623 .margin({ bottom: 36 }) 624 TextInput({ placeholder: '' }) 625 }.backgroundColor('#FFF0F0F0') 626 } 627 } 628} 629 630@Entry 631@Component 632struct Index { 633 dialogController: CustomDialogController | null = new CustomDialogController({ 634 builder: CustomDialogExample({ 635 }), 636 autoCancel: true, 637 gridCount: 4, 638 showInSubWindow: true, 639 isModal: true, 640 customStyle: false, 641 cornerRadius: 30, 642 alignment:DialogAlignment.Bottom, 643 keyboardAvoidMode: KeyboardAvoidMode.DEFAULT, // 软键盘弹出时,弹出框自动避让 644 keyboardAvoidDistance: LengthMetrics.vp(0) // 软键盘弹出时与弹出框的距离为0vp 645 }) 646 647 build() { 648 Row() { 649 Row({ space: 20 }) { 650 Text('打开弹窗') 651 .fontSize(30) 652 .onClick(() => { 653 if (this.dialogController != null) { 654 this.dialogController.open(); 655 } 656 }) 657 } 658 .width('100%') 659 } 660 .height('100%') 661 } 662} 663``` 664 665  666 667## 获取弹出框的状态 668 669在业务模块中,页面上可能会同时出现多个弹出框。为避免重复打开相同的弹出框,建议在显示弹出框前,先通过控制器检查其当前状态。如果弹出框已处于显示状态,则不应再次打开。 670从API version 20开始,新增了getState接口,用于获取弹出框的当前状态。具体的弹出框状态信息,请参见[CommonState](../reference/apis-arkui/js-apis-promptAction.md#commonstate20枚举说明)枚举的详细说明。 671 672以下示例通过[getDialogController](../reference/apis-arkui/arkui-ts/ts-custom-component-api.md#getdialogcontroller18)和[CustomDialogController](../reference/apis-arkui/arkui-ts/ts-methods-custom-dialog-box.md#customdialogcontroller)两种方法,实现了获取弹出框当前状态的功能。 673 674```ts 675// xxx.ets 676@CustomDialog 677struct CustomDialogExample { 678 controller?: CustomDialogController 679 680 build() { 681 Column() { 682 Button('点我查询弹窗状态:通过自定义组件自带controller') 683 .onClick(() => { 684 if (this.getDialogController() != undefined) { 685 console.info('state:' + this.getDialogController().getState()) 686 } else { 687 console.info('state: no exist') 688 } 689 }).margin(20) 690 Button('点我查询弹窗状态:通过CustomDialogController ') 691 .onClick(() => { 692 console.info('state:' + this.controller?.getState()) 693 }).margin(20) 694 Button('点我关闭弹窗') 695 .onClick(() => { 696 if (this.getDialogController() != undefined) { 697 this.getDialogController().close() 698 } 699 }).margin(20) 700 701 } 702 } 703} 704 705@Entry 706@Component 707struct CustomDialogUser { 708 dialogController: CustomDialogController | null = new CustomDialogController({ 709 builder: CustomDialogExample({ 710 }), 711 autoCancel: false 712 }) 713 714 build() { 715 Column() { 716 Button('click me') 717 .onClick(() => { 718 if (this.dialogController != null) { 719 this.dialogController.open() 720 } 721 }) 722 }.width('100%').margin({ top: 5 }) 723 } 724} 725``` 726 727## 相关实例 728 729针对自定义弹出框开发,有以下相关实例可供参考: 730 731- [自定义弹出框(ArkTS)(API9)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/CustomDialog) 732- [构建多种样式弹出框(ArkTS)(API9)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/MultipleDialog) 733- [目标管理(ArkTS)(API9)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/TargetManagement) 734 735 736