• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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![zh-cn_image_0000001511740524](figures/zh-cn_image_0000001511740524.png)
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![PopupOnStateChange](figures/PopupOnStateChange.gif)
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![zh-cn_other_0000001500740342](figures/zh-cn_other_0000001500740342.jpeg)
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![popup_transition](figures/popup_transition.gif)
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![zh-cn_other_0000001500900234](figures/zh-cn_other_0000001500900234.jpeg)
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![image](figures/UIpopupStyle.gif)
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![image](figures/avoidKeyboard.gif)
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![popupStateStyle](figures/popupStateStyle.gif)
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```