1# 自定义弹窗 (CustomDialog) 2 3 4CustomDialog是自定义弹窗,可用于广告、中奖、警告、软件更新等与用户交互响应操作。开发者可以通过CustomDialogController类显示自定义弹窗。具体用法请参考[自定义弹窗](../reference/apis-arkui/arkui-ts/ts-methods-custom-dialog-box.md)。 5 6> **说明:** 7> 8> 当前,ArkUI弹窗均为非页面级弹窗,在页面路由跳转时,如果开发者未调用close方法将其关闭,弹窗将不会自动关闭。若需实现在跳转页面时弹窗同步关闭的场景,建议使用Navigation。具体使用方法,请参考[组件导航子页面显示类型的弹窗类型](arkts-navigation-navigation.md#页面显示类型)。 9 10## 创建自定义弹窗 11 121. 使用\@CustomDialog装饰器装饰自定义弹窗,可在此装饰器内自定义弹窗内容。 13 14 ```ts 15 @CustomDialog 16 struct CustomDialogExample { 17 controller: CustomDialogController = new CustomDialogController({ 18 builder: CustomDialogExample({}), 19 }) 20 21 build() { 22 Column() { 23 Text('我是内容') 24 .fontSize(20) 25 .margin({ top: 10, bottom: 10 }) 26 } 27 } 28 } 29 ``` 30 313. 创建构造器,与装饰器呼应相连。 32 33 ```ts 34 @Entry 35 @Component 36 struct CustomDialogUser { 37 dialogController: CustomDialogController = new CustomDialogController({ 38 builder: CustomDialogExample(), 39 }) 40 } 41 ``` 42 434. 点击与onClick事件绑定的组件使弹窗弹出。 44 45 ```ts 46 @Entry 47 @Component 48 struct CustomDialogUser { 49 dialogController: CustomDialogController = new CustomDialogController({ 50 builder: CustomDialogExample(), 51 }) 52 53 build() { 54 Column() { 55 Button('click me') 56 .onClick(() => { 57 this.dialogController.open() 58 }) 59 }.width('100%').margin({ top: 5 }) 60 } 61 } 62 ``` 63 64  65 66 67## 弹窗的交互 68 69弹窗可用于数据交互,完成用户一系列响应操作。 70 71 721. 在\@CustomDialog装饰器内添加按钮,同时添加数据函数。 73 74 ```ts 75 @CustomDialog 76 struct CustomDialogExample { 77 cancel?: () => void 78 confirm?: () => void 79 controller: CustomDialogController 80 81 build() { 82 Column() { 83 Text('我是内容').fontSize(20).margin({ top: 10, bottom: 10 }) 84 Flex({ justifyContent: FlexAlign.SpaceAround }) { 85 Button('cancel') 86 .onClick(() => { 87 this.controller.close() 88 if (this.cancel) { 89 this.cancel() 90 } 91 }).backgroundColor(0xffffff).fontColor(Color.Black) 92 Button('confirm') 93 .onClick(() => { 94 this.controller.close() 95 if (this.confirm) { 96 this.confirm() 97 } 98 }).backgroundColor(0xffffff).fontColor(Color.Red) 99 }.margin({ bottom: 10 }) 100 } 101 } 102 } 103 ``` 104 1052. 页面内需要在构造器内进行接收,同时创建相应的函数操作。 106 107 ```ts 108 @Entry 109 @Component 110 struct CustomDialogUser { 111 dialogController: CustomDialogController = new CustomDialogController({ 112 builder: CustomDialogExample({ 113 cancel: ()=> { this.onCancel() }, 114 confirm: ()=> { this.onAccept() }, 115 }), 116 }) 117 118 onCancel() { 119 console.info('Callback when the first button is clicked') 120 } 121 122 onAccept() { 123 console.info('Callback when the second button is clicked') 124 } 125 126 build() { 127 Column() { 128 Button('click me') 129 .onClick(() => { 130 this.dialogController.open() 131 }) 132 }.width('100%').margin({ top: 5 }) 133 } 134 } 135 ``` 136 137  138 139 3.可通过弹窗中的按钮实现路由跳转,同时获取跳转页面向当前页传入的参数。 140 141 ```ts 142 // Index.ets 143 @CustomDialog 144 struct CustomDialogExample { 145 @Link textValue: string 146 controller?: CustomDialogController 147 cancel: () => void = () => { 148 } 149 confirm: () => void = () => { 150 } 151 152 build() { 153 Column({ space: 20 }) { 154 if (this.textValue != '') { 155 Text(`第二个页面的内容为:${this.textValue}`) 156 .fontSize(20) 157 } else { 158 Text('是否获取第二个页面的内容') 159 .fontSize(20) 160 } 161 Flex({ justifyContent: FlexAlign.SpaceAround }) { 162 Button('cancel') 163 .onClick(() => { 164 if (this.controller != undefined) { 165 this.controller.close() 166 this.cancel() 167 } 168 }).backgroundColor(0xffffff).fontColor(Color.Black) 169 Button('confirm') 170 .onClick(() => { 171 if (this.controller != undefined && this.textValue != '') { 172 this.controller.close() 173 } else if (this.controller != undefined) { 174 this.getUIContext().getRouter().pushUrl({ 175 url: 'pages/Index2' 176 }) 177 this.controller.close() 178 } 179 }).backgroundColor(0xffffff).fontColor(Color.Red) 180 }.margin({ bottom: 10 }) 181 }.borderRadius(10).padding({ top: 20 }) 182 } 183 } 184 185 @Entry 186 @Component 187 struct CustomDialogUser { 188 @State textValue: string = '' 189 dialogController: CustomDialogController | null = new CustomDialogController({ 190 builder: CustomDialogExample({ 191 cancel: () => { 192 this.onCancel() 193 }, 194 confirm: () => { 195 this.onAccept() 196 }, 197 textValue: $textValue 198 }) 199 }) 200 201 // 在自定义组件即将析构销毁时将dialogController置空 202 aboutToDisappear() { 203 this.dialogController = null // 将dialogController置空 204 } 205 206 onPageShow() { 207 const params = this.getUIContext().getRouter().getParams() as Record<string, string>; // 获取传递过来的参数对象 208 if (params) { 209 this.dialogController?.open() 210 this.textValue = params.info as string; // 获取info属性的值 211 } 212 } 213 214 onCancel() { 215 console.info('Callback when the first button is clicked') 216 } 217 218 onAccept() { 219 console.info('Callback when the second button is clicked') 220 } 221 222 exitApp() { 223 console.info('Click the callback in the blank area') 224 } 225 226 build() { 227 Column() { 228 Button('click me') 229 .onClick(() => { 230 if (this.dialogController != null) { 231 this.dialogController.open() 232 } 233 }).backgroundColor(0x317aff) 234 }.width('100%').margin({ top: 5 }) 235 } 236 } 237 ``` 238 239 ```ts 240 // Index2.ets 241 @Entry 242 @Component 243 struct Index2 { 244 @State message: string = '点击返回'; 245 build() { 246 Column() { 247 Button(this.message) 248 .fontSize(50) 249 .fontWeight(FontWeight.Bold).onClick(() => { 250 this.getUIContext().getRouter().back({ 251 url: 'pages/Index', 252 params: { 253 info: 'Hello World' 254 } 255 }); 256 }) 257 }.width('100%').height('100%').margin({ top: 20 }) 258 } 259 } 260 ``` 261 262  263 264## 弹窗的动画 265 266弹窗通过定义openAnimation控制弹窗出现动画的持续时间,速度等参数。 267 268```ts 269@CustomDialog 270struct CustomDialogExample { 271 controller?: CustomDialogController 272 273 build() { 274 Column() { 275 Text('Whether to change a text?').fontSize(16).margin({ bottom: 10 }) 276 } 277 } 278} 279 280@Entry 281@Component 282struct CustomDialogUser { 283 @State textValue: string = '' 284 @State inputValue: string = 'click me' 285 dialogController: CustomDialogController | null = new CustomDialogController({ 286 builder: CustomDialogExample(), 287 openAnimation: { 288 duration: 1200, 289 curve: Curve.Friction, 290 delay: 500, 291 playMode: PlayMode.Alternate, 292 onFinish: () => { 293 console.info('play end') 294 } 295 }, 296 autoCancel: true, 297 alignment: DialogAlignment.Bottom, 298 offset: { dx: 0, dy: -20 }, 299 gridCount: 4, 300 customStyle: false, 301 backgroundColor: 0xd9ffffff, 302 cornerRadius: 10, 303 }) 304 305 // 在自定义组件即将析构销毁时将dialogController置空 306 aboutToDisappear() { 307 this.dialogController = null // 将dialogController置空 308 } 309 310 build() { 311 Column() { 312 Button(this.inputValue) 313 .onClick(() => { 314 if (this.dialogController != null) { 315 this.dialogController.open() 316 } 317 }).backgroundColor(0x317aff) 318 }.width('100%').margin({ top: 5 }) 319 } 320} 321``` 322 323 324 325## 弹窗的样式 326弹窗通过定义宽度、高度、背景色、阴影等参数来控制样式。 327 328```ts 329@CustomDialog 330struct CustomDialogExample { 331 controller?: CustomDialogController 332 333 build() { 334 Column() { 335 Text('我是内容').fontSize(16).margin({ bottom: 10 }) 336 } 337 } 338} 339 340@Entry 341@Component 342struct CustomDialogUser { 343 @State textValue: string = '' 344 @State inputValue: string = 'click me' 345 dialogController: CustomDialogController | null = new CustomDialogController({ 346 builder: CustomDialogExample(), 347 autoCancel: true, 348 alignment: DialogAlignment.Center, 349 offset: { dx: 0, dy: -20 }, 350 gridCount: 4, 351 customStyle: false, 352 backgroundColor: 0xd9ffffff, 353 cornerRadius: 20, 354 width: '80%', 355 height: '100px', 356 borderWidth: 1, 357 borderStyle: BorderStyle.Dashed,//使用borderStyle属性,需要和borderWidth属性一起使用 358 borderColor: Color.Blue,//使用borderColor属性,需要和borderWidth属性一起使用 359 shadow: ({ radius: 20, color: Color.Grey, offsetX: 50, offsetY: 0}), 360 }) 361 362 // 在自定义组件即将析构销毁时将dialogController置空 363 aboutToDisappear() { 364 this.dialogController = null // 将dialogController置空 365 } 366 367 build() { 368 Column() { 369 Button(this.inputValue) 370 .onClick(() => { 371 if (this.dialogController != null) { 372 this.dialogController.open() 373 } 374 }).backgroundColor(0x317aff) 375 }.width('100%').margin({ top: 5 }) 376 } 377} 378``` 379 380 381## 嵌套自定义弹窗 382 383通过第一个弹窗打开第二个弹窗时,最好将第二个弹窗定义在第一个弹窗的父组件处,通过父组件传给第一个弹窗的回调来打开第二个弹窗。 384 385```ts 386@CustomDialog 387struct CustomDialogExampleTwo { 388 controllerTwo?: CustomDialogController 389 @State message: string = "I'm the second dialog box." 390 @State showIf: boolean = false; 391 build() { 392 Column() { 393 if (this.showIf) { 394 Text("Text") 395 .fontSize(30) 396 .height(100) 397 } 398 Text(this.message) 399 .fontSize(30) 400 .height(100) 401 Button("Create Text") 402 .onClick(()=>{ 403 this.showIf = true; 404 }) 405 Button ('Close Second Dialog Box') 406 .onClick(() => { 407 if (this.controllerTwo != undefined) { 408 this.controllerTwo.close() 409 } 410 }) 411 .margin(20) 412 } 413 } 414} 415@CustomDialog 416struct CustomDialogExample { 417 openSecondBox?: ()=>void 418 controller?: CustomDialogController 419 420 build() { 421 Column() { 422 Button ('Open Second Dialog Box and close this box') 423 .onClick(() => { 424 this.controller!.close(); 425 this.openSecondBox!(); 426 }) 427 .margin(20) 428 }.borderRadius(10) 429 } 430} 431@Entry 432@Component 433struct CustomDialogUser { 434 @State inputValue: string = 'Click Me' 435 dialogController: CustomDialogController | null = new CustomDialogController({ 436 builder: CustomDialogExample({ 437 openSecondBox: ()=>{ 438 if (this.dialogControllerTwo != null) { 439 this.dialogControllerTwo.open() 440 } 441 } 442 }), 443 cancel: this.exitApp, 444 autoCancel: true, 445 alignment: DialogAlignment.Bottom, 446 offset: { dx: 0, dy: -20 }, 447 gridCount: 4, 448 customStyle: false 449 }) 450 dialogControllerTwo: CustomDialogController | null = new CustomDialogController({ 451 builder: CustomDialogExampleTwo(), 452 alignment: DialogAlignment.Bottom, 453 offset: { dx: 0, dy: -25 } }) 454 455 aboutToDisappear() { 456 this.dialogController = null 457 this.dialogControllerTwo = null 458 } 459 460 onCancel() { 461 console.info('Callback when the first button is clicked') 462 } 463 464 onAccept() { 465 console.info('Callback when the second button is clicked') 466 } 467 468 exitApp() { 469 console.info('Click the callback in the blank area') 470 } 471 build() { 472 Column() { 473 Button(this.inputValue) 474 .onClick(() => { 475 if (this.dialogController != null) { 476 this.dialogController.open() 477 } 478 }).backgroundColor(0x317aff) 479 }.width('100%').margin({ top: 5 }) 480 } 481} 482``` 483 484 485由于自定义弹窗在状态管理侧有父子关系,如果将第二个弹窗定义在第一个弹窗内,那么当父组件(第一个弹窗)被销毁(关闭)时,子组件(第二个弹窗)内无法再继续创建新的组件。 486 487## 相关实例 488 489针对自定义弹窗开发,有以下相关实例可供参考: 490 491- [自定义弹窗(ArkTS)(API9)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/CustomDialog) 492 493- [构建多种样式弹窗(ArkTS)(API9)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/MultipleDialog) 494 495- [目标管理(ArkTS)(API9)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/TargetManagement)