1# 气泡提示(Popup) 2<!--Kit: ArkUI--> 3<!--Subsystem: ArkUI--> 4<!--Owner: @liyi0309--> 5<!--Designer: @liyi0309--> 6<!--Tester: @lxl007--> 7<!--Adviser: @HelloCrease--> 8Popup属性可绑定在组件上显示气泡弹窗提示,设置弹窗内容、交互逻辑和显示状态。主要用于屏幕录制、信息弹出提醒等显示状态。 9 10气泡分为两种类型,一种是系统提供的气泡[PopupOptions](../reference/apis-arkui/arkui-ts/ts-universal-attributes-popup.md#popupoptions类型说明),一种是开发者可以自定义的气泡[CustomPopupOptions](../reference/apis-arkui/arkui-ts/ts-universal-attributes-popup.md#custompopupoptions8类型说明)。其中,PopupOptions通过配置primaryButton和secondaryButton来设置带按钮的气泡;CustomPopupOptions通过配置[builder](../../application-dev/ui/state-management/arkts-builder.md)来设置自定义的气泡。其中系统提供的气泡PopupOptions,字体的最大放大倍数为2。 11 12气泡可以通过配置[mask](../reference/apis-arkui/arkui-ts/ts-universal-attributes-popup.md#popupoptions类型说明)来实现模态和非模态窗口,mask为true或者颜色值的时候,气泡为模态窗口,mask为false时,气泡为非模态窗口。 13 14多个气泡同时弹出时,子窗内显示的气泡比主窗内显示的气泡层级高,所处窗口相同时,后面弹出的气泡层级比先弹出的气泡层级高。 15 16## 文本提示气泡 17 18文本提示气泡常用于展示带有文本的信息提示,适用于无交互的场景。Popup属性需绑定组件,当bindPopup属性的参数show为true时,会弹出气泡提示。 19 20在Button组件上绑定Popup属性,每次点击Button按钮时,handlePopup会切换布尔值。当值为true时,触发bindPopup弹出气泡。 21 22```ts 23@Entry 24@Component 25struct PopupExample { 26 @State handlePopup: boolean = false; 27 28 build() { 29 Column() { 30 Button('PopupOptions') 31 .onClick(() => { 32 this.handlePopup = !this.handlePopup; 33 }) 34 .bindPopup(this.handlePopup, { 35 message: 'This is a popup with PopupOptions', 36 }) 37 }.width('100%').padding({ top: 5 }) 38 } 39} 40``` 41 42 43 44## 添加气泡状态变化的事件 45 46通过onStateChange参数为气泡添加状态变化的事件回调,可以判断气泡的当前显示状态。 47 48```ts 49@Entry 50@Component 51struct PopupExample { 52 @State handlePopup: boolean = false; 53 54 build() { 55 Column() { 56 Button('PopupOptions') 57 .onClick(() => { 58 this.handlePopup = !this.handlePopup; 59 }) 60 .bindPopup(this.handlePopup, { 61 message: 'This is a popup with PopupOptions', 62 onStateChange: (e)=> { // 返回当前的气泡状态 63 if (!e.isVisible) { 64 this.handlePopup = false; 65 } 66 } 67 }) 68 }.width('100%').padding({ top: 5 }) 69 } 70} 71``` 72 73 74 75## 带按钮的提示气泡 76 77通过primaryButton、secondaryButton属性为气泡最多设置两个Button按钮,通过此按钮进行简单的交互,开发者可以通过配置action参数来设置想要触发的操作。 78 79```ts 80@Entry 81@Component 82struct PopupExample22 { 83 @State handlePopup: boolean = false; 84 85 build() { 86 Column() { 87 Button('PopupOptions').margin({ top: 200 }) 88 .onClick(() => { 89 this.handlePopup = !this.handlePopup; 90 }) 91 .bindPopup(this.handlePopup, { 92 message: 'This is a popup with PopupOptions', 93 primaryButton: { 94 value: 'Confirm', 95 action: () => { 96 this.handlePopup = !this.handlePopup; 97 console.info('confirm Button click'); 98 } 99 }, 100 secondaryButton: { 101 value: 'Cancel', 102 action: () => { 103 this.handlePopup = !this.handlePopup; 104 } 105 }, 106 onStateChange: (e) => { 107 if (!e.isVisible) { 108 this.handlePopup = false; 109 } 110 } 111 }) 112 }.width('100%').padding({ top: 5 }) 113 } 114} 115``` 116 117 118 119## 气泡的动画 120 121通过定义transition,可以控制气泡的进场和出场动画效果。 122 123```ts 124// xxx.ets 125@Entry 126@Component 127struct PopupExample { 128 @State handlePopup: boolean = false; 129 @State customPopup: boolean = false; 130 131 // popup构造器定义弹框内容 132 @Builder popupBuilder() { 133 Row() { 134 Text('Custom Popup with transitionEffect').fontSize(10) 135 }.height(50).padding(5) 136 } 137 138 build() { 139 Flex({ direction: FlexDirection.Column }) { 140 // PopupOptions 类型设置弹框内容 141 Button('PopupOptions') 142 .onClick(() => { 143 this.handlePopup = !this.handlePopup; 144 }) 145 .bindPopup(this.handlePopup, { 146 message: 'This is a popup with transitionEffect', 147 placement: Placement.Top, 148 showInSubWindow: false, 149 onStateChange: (e) => { 150 if (!e.isVisible) { 151 this.handlePopup = false; 152 } 153 }, 154 // 设置弹窗显示动效为透明度动效与平移动效的组合效果,无退出动效 155 transition:TransitionEffect.asymmetric( 156 TransitionEffect.OPACITY.animation({ duration: 1000, curve: Curve.Ease }).combine( 157 TransitionEffect.translate({ x: 50, y: 50 })), 158 TransitionEffect.IDENTITY) 159 }) 160 .position({ x: 100, y: 150 }) 161 162 // CustomPopupOptions 类型设置弹框内容 163 Button('CustomPopupOptions') 164 .onClick(() => { 165 this.customPopup = !this.customPopup; 166 }) 167 .bindPopup(this.customPopup, { 168 builder: this.popupBuilder, 169 placement: Placement.Top, 170 showInSubWindow: false, 171 onStateChange: (e) => { 172 if (!e.isVisible) { 173 this.customPopup = false; 174 } 175 }, 176 // 设置弹窗显示动效与退出动效为缩放动效 177 transition:TransitionEffect.scale({ x: 1, y: 0 }).animation({ duration: 500, curve: Curve.Ease }) 178 }) 179 .position({ x: 80, y: 300 }) 180 }.width('100%').padding({ top: 5 }) 181 } 182} 183``` 184 185 186 187## 自定义气泡 188 189开发者可以使用CustomPopupOptions的builder创建自定义气泡,\@Builder中可以放自定义的内容。除此之外,还可以通过popupColor等参数控制气泡样式。 190 191```ts 192@Entry 193@Component 194struct Index { 195 @State customPopup: boolean = false; 196 // popup构造器定义弹框内容 197 @Builder popupBuilder() { 198 Row({ space: 2 }) { 199 // $r('app.media.icon')需要替换为开发者所需的图像资源文件。 200 Image($r("app.media.icon")).width(24).height(24).margin({ left: 5 }) 201 Text('This is Custom Popup').fontSize(15) 202 }.width(200).height(50).padding(5) 203 } 204 build() { 205 Column() { 206 Button('CustomPopupOptions') 207 .position({x:100,y:200}) 208 .onClick(() => { 209 this.customPopup = !this.customPopup; 210 }) 211 .bindPopup(this.customPopup, { 212 builder: this.popupBuilder, // 气泡的内容 213 placement:Placement.Bottom, // 气泡的弹出位置 214 popupColor:Color.Pink, // 气泡的背景色 215 onStateChange: (e) => { 216 if (!e.isVisible) { 217 this.customPopup = false; 218 } 219 } 220 }) 221 } 222 .height('100%') 223 } 224} 225``` 226 227使用者通过配置placement参数将弹出的气泡放到需要提示的位置。弹窗构造器会触发弹出提示信息,来引导使用者完成操作,也让使用者有更好的UI体验。 228 229 230 231## 气泡样式 232 233气泡除了可以通过builder实现自定义气泡,还可以通过接口设置气泡的样式和显示效果。 234 235背景颜色:气泡的背景色默认为透明,但是会有一个默认的模糊效果,手机上为COMPONENT\_ULTRA\_THICK。 236蒙层样式:气泡默认有蒙层,且蒙层的颜色为透明。 237显示大小:气泡大小由内部的builder大小或者message的长度决定的。 238显示位置:气泡默认显示在宿主组件的下方,可以通过Placement接口来配置其显示位置以及对齐方向。 239以下示例通过设置popupColor(背景颜色)、mask(蒙层样式)、width(气泡宽度)、placement(显示位置)实现气泡的样式。 240 241```ts 242// xxx.ets 243 244@Entry 245@Component 246struct PopupExample { 247 @State handlePopup: boolean = false; 248 249 build() { 250 Column({ space: 100 }) { 251 Button('PopupOptions') 252 .onClick(() => { 253 this.handlePopup = !this.handlePopup; 254 }) 255 .bindPopup(this.handlePopup, { 256 width: 200, 257 message: 'This is a popup.', 258 popupColor: Color.Red, // 设置气泡的背景色 259 mask: { 260 color: '#33d9d9d9' 261 }, 262 placement: Placement.Top, 263 backgroundBlurStyle: BlurStyle.NONE // 去除背景模糊效果需要关闭气泡的模糊背景 264 }) 265 } 266 .width('100%') 267 } 268} 269``` 270 271 272 273## 气泡避让软键盘 274 275当软键盘弹出时,气泡默认不会对其避让,可能导致气泡被软键盘覆盖,这时需要设置keyboardAvoidMode为KeyboardAvoidMode.DEFAULT,来使气泡避让键盘。这时如果当前没有位置放下气泡时,气泡会从预设位置平移覆盖宿主组件。 276 277```ts 278// xxx.ets 279 280@Entry 281@Component 282struct PopupExample { 283 @State handlePopup: boolean = false; 284 285 @Builder popupBuilder() { 286 Column({ space: 2 }) { 287 Text('Custom Popup').fontSize(20) 288 .borderWidth(2) 289 TextInput() 290 }.width(200).padding(5) 291 } 292 293 build() { 294 Column({ space: 100 }) { 295 TextInput() 296 Button('PopupOptions') 297 .onClick(() => { 298 this.handlePopup = !this.handlePopup; 299 }) 300 .bindPopup(this.handlePopup!!, { 301 width: 200, 302 builder: this.popupBuilder(), 303 placement: Placement.Bottom, 304 mask: false, 305 autoCancel: false, 306 keyboardAvoidMode: KeyboardAvoidMode.DEFAULT 307 }) 308 .position({x: 100, y: 300}) 309 } 310 .width('100%') 311 } 312} 313``` 314 315 316 317 318## 设置气泡内的多态效果 319 320目前使用@Builder自定义气泡内容时,默认不支持多态样式,可以使用@Component新建一个组件实现按下气泡中的内容时背景变色。 321 322```ts 323@Entry 324@Component 325struct PopupPage { 326 private menus: Array<string> = ["扫一扫", "创建群聊", "电子工卡"] 327 328 // popup构造器定义弹框内容 329 @Builder 330 popupItemBuilder(name: string, action: string) { 331 PopupItemChild({ childName: name, childAction: action }) 332 } 333 334 // popup构造器定义弹框内容 335 @Builder 336 popupBuilder() { 337 Column() { 338 ForEach( 339 this.menus, 340 (item: string, index) => { 341 this.popupItemBuilder(item, String(index)) 342 }, 343 (item: string, index) => { 344 return item 345 }) 346 } 347 .padding(8) 348 } 349 350 @State customPopup: boolean = false; 351 352 build() { 353 Column() { 354 Button('click me') 355 .onClick(() => { 356 this.customPopup = !this.customPopup 357 }) 358 .bindPopup( 359 this.customPopup, 360 { 361 builder: this.popupBuilder, // 气泡的内容 362 placement: Placement.Bottom, // 气泡的弹出位置 363 popupColor: Color.White, // 气泡的背景色 364 onStateChange: (event) => { 365 if (!event.isVisible) { 366 this.customPopup = false 367 } 368 } 369 }) 370 } 371 .width('100%') 372 .justifyContent(FlexAlign.Center) 373 } 374} 375 376@Component 377struct PopupItemChild { 378 @Prop childName: string = ''; 379 @Prop childAction: string = ''; 380 381 build() { 382 Row({ space: 8 }) { 383 // $r('app.media.startIcon')需要替换为开发者所需的图像资源文件。 384 Image($r('app.media.startIcon')) 385 .width(24) 386 .height(24) 387 Text(this.childName) 388 .fontSize(16) 389 } 390 .width(130) 391 .height(50) 392 .padding(8) 393 .onClick(() => { 394 this.getUIContext().getPromptAction().showToast({ message: '选中了' + this.childName }) 395 }) 396 .stateStyles({ 397 normal: { 398 .backgroundColor(Color.White) 399 }, 400 pressed: { 401 .backgroundColor('#1fbb7d') 402 } 403 }) 404 } 405} 406``` 407 408 409 410## 气泡支持避让中轴 411 412从API version 18起,气泡支持中轴避让功能。从API version 20开始,在2in1设备上默认启用(仅在窗口处于瀑布模式时产生避让)。开发者可通过[PopupOptions](../reference/apis-arkui/arkui-ts/ts-universal-attributes-popup.md#popupoptions类型说明)中的enableHoverMode属性,控制气泡是否启用中轴避让。 413 414> **说明:** 415> - 如果气泡的点击位置在中轴区域,则气泡不会避让。 416> - 2in1设备上需同时满足窗口处于瀑布模式才会产生避让。 417 418```ts 419@Entry 420@Component 421struct Index { 422 @State message: string = 'Hello World'; 423 @State index: number = 0; 424 @State arrayStr: Array<string> = ['上半屏', '中轴', '下半屏']; 425 @State enableHoverMode: boolean | undefined = true; 426 @State showInSubwindow: boolean = false; 427 @State placement: Placement | undefined = undefined; 428 @State isShow: boolean = false; 429 430 build() { 431 RelativeContainer() { 432 Column() { 433 Button('区域:' + this.arrayStr[this.index]) 434 .onClick(() => { 435 if (this.index < 2) { 436 this.index++ 437 } else { 438 this.index = 0 439 } 440 }) 441 442 Button('子窗显示:' + (this.showInSubwindow ? '子窗' : '非子窗')) 443 .onClick(() => { 444 this.showInSubwindow = !this.showInSubwindow 445 }) 446 447 Button('hoverMode开启:' + this.enableHoverMode) 448 .onClick(() => { 449 if (this.enableHoverMode == undefined) { 450 this.enableHoverMode = true 451 } else if (this.enableHoverMode == true) { 452 this.enableHoverMode = false 453 } else { 454 this.enableHoverMode = undefined 455 } 456 }) 457 } 458 459 Row() { 460 Button('Popup') 461 .fontWeight(FontWeight.Bold) 462 .bindPopup(this.isShow, { 463 message: 'popup', 464 enableHoverMode: this.enableHoverMode, 465 showInSubWindow: this.showInSubwindow, 466 }) 467 .onClick(() => { 468 this.isShow = !this.isShow 469 }) 470 } 471 .alignRules({ 472 center: { anchor: '__container__', align: VerticalAlign.Center }, 473 middle: { anchor: '__container__', align: HorizontalAlign.Center } 474 }) 475 .margin({ 476 top: this.index == 2 ? 330 : this.index == 1 ? 50 : 0, 477 bottom: this.index == 0 ? 330 : 0 478 }) 479 } 480 .height('100%') 481 .width('100%') 482 } 483} 484```