1# 安全区域
2
3安全区域是指页面的显示区域,默认不与系统设置的非安全区域比如状态栏、导航栏区域重叠,默认情况下开发者开发的界面都被布局在安全区域内。提供属性方法允许开发者设置组件绘制内容突破安全区域的限制,通过[expandSafeArea](#expandsafearea)属性支持组件不改变布局情况下扩展其绘制区域至安全区外,通过设置[setKeyboardAvoidMode](#setkeyboardavoidmode11)来配置虚拟键盘弹出时页面的避让模式。页面中有标题栏等文字不希望和非安全区重叠时,建议对组件设置expandSafeArea属性达到沉浸式效果,也可以直接通过窗口接口[setWindowLayoutFullScreen](../js-apis-window.md#setwindowlayoutfullscreen9)设置沉浸式。
4
5> **说明:**
6>
7> 从API Version 10开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。<br />
8> 默认摄像头挖孔区域不为非安全区域,页面不避让挖孔。<br />
9> 从API Version 12开始,可在module.json5中添加配置项, 摄像头挖孔区域视为非安全区,实现页面默认避让挖孔:<br />
10  "metadata": [<br />
11    &nbsp;&nbsp;{<br />
12    &nbsp;&nbsp;&nbsp;&nbsp;"name": "avoid_cutout",<br />
13    &nbsp;&nbsp;&nbsp;&nbsp;"value": "true",<br />
14    &nbsp;&nbsp;}<br />
15  ],<br />
16
17
18## expandSafeArea
19
20expandSafeArea(types?: Array&lt;SafeAreaType&gt;, edges?: Array&lt;SafeAreaEdge&gt;)
21
22控制组件扩展其安全区域。
23
24**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。
25
26**系统能力:** SystemCapability.ArkUI.ArkUI.Full
27
28**参数:**
29
30| 参数名 | 类型                                               | 必填 | 说明                                                         |
31| ------ | -------------------------------------------------- | ---- | ------------------------------------------------------------ |
32| types  | Array <[SafeAreaType](ts-types.md#safeareatype10)> | 否   | 配置扩展安全区域的类型。未添加[Metadata](../../apis-ability-kit/js-apis-bundleManager-metadata.md)配置项时,页面不避让挖孔, CUTOUT类型不生效。<br />默认值:[SafeAreaType.SYSTEM, SafeAreaType.CUTOUT, SafeAreaType.KEYBOARD] |
33| edges  | Array <[SafeAreaEdge](ts-types.md#safeareaedge10)> | 否   | 配置扩展安全区域的方向。<br />默认值:[SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM, SafeAreaEdge.START, SafeAreaEdge.END]<br />扩展至所有非安全区域。 |
34
35>  **说明:**
36>
37>  设置expandSafeArea属性进行组件绘制扩展时,建议组件尺寸不要设置固定宽高(百分比除外),当设置固定宽高时,扩展安全区域的方向只支持[SafeAreaEdge.TOP, SafeAreaEdge.START],扩展后的组件尺寸保持不变。
38>
39>  安全区域不会限制内部组件的布局和大小,不会裁剪内部组件。
40>
41>  当父容器是滚动容器时,设置expandSafeArea属性不生效。
42>
43>  设置expandSafeArea()时,不传参,走默认值处理;设置expandSafeArea([],[])时,相当于入参是空数组,此时设置expandSafeArea属性不生效。
44>
45>  组件设置expandSafeArea之后生效的条件为:
46>  1.typeSafeAreaType.KEYBOARD时默认生效,组件不避让键盘。
47>  2.设置其他type,组件的边界与安全区域重合时组件能够延伸到安全区域下。例如:设备顶部状态栏高度100,那么组件在屏幕中的绝对位置需要为0 <= y <= 100。
48>
49>  组件延伸到安全区域下,在安全区域处的事件,如点击事件等可能会被系统拦截,优先给状态栏等系统组件响应。
50>
51>  滚动类容器内的组件不建议设置expandSafeArea属性,如果设置,需要按照组件嵌套关系,将当前节点到滚动类祖先容器间所有直接节点设置expandSafeArea属性,否则expandSafeArea属性在滚动后可能会失效,写法参考[示例7](#示例7滚动类容器扩展安全区)。
52>
53>  expandSafeArea属性仅作用于当前组件,不会向父组件或子组件传递,因此使用过程中,所有相关组件均需配置。
54>
55>  在同时设置了expandSafeArea和position属性时,position属性会先生效,expandSafeArea属性会后生效。对于未设置position、offset等绘制属性的组件,如果组件边界没有和避让区重叠,设置expandSafeArea属性不生效,如弹窗和半模态组件。
56>
57>  对于expandSafeArea属性无法生效的场景,若要将组件部署在避让区,需要手动调整组件的坐标。
58
59## setKeyboardAvoidMode<sup>11+</sup>
60
61setKeyboardAvoidMode(value: KeyboardAvoidMode): void
62
63控制虚拟键盘抬起时页面的避让模式。
64
65**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。
66
67**系统能力:** SystemCapability.ArkUI.ArkUI.Full
68
69**参数:**
70
71| 参数名 | 类型                                                 | 必填 | 说明                                                         |
72| ------ | ---------------------------------------------------- | ---- | ------------------------------------------------------------ |
73| value  | [KeyboardAvoidMode](ts-types.md#keyboardavoidmode11) | 是   | 配置虚拟键盘抬起时页面的避让模式。<br />默认值:KeyboardAvoidMode.OFFSET,键盘抬起时默认页面避让模式为上抬模式。 |
74
75>  **说明:**
76>
77>  KeyboardAvoidMode的RESIZE模式是压缩Page的大小,Page下设置百分比宽高的组件会跟随Page压缩,直接设置宽高的组件会按设置的固定大小布局。设置KeyboardAvoidMode的RESIZE模式时,expandSafeArea([SafeAreaType.KEYBOARD],[SafeAreaEdge.BOTTOM])不生效。
78>
79>  KeyboardAvoidMode.NONE配置Page不避让键盘,Page会被抬起的键盘遮盖。
80
81## getKeyboardAvoidMode
82
83getKeyboardAvoidMode(): KeyboardAvoidMode
84
85返回虚拟键盘抬起时的页面避让模式。
86
87**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。
88
89**系统能力:** SystemCapability.ArkUI.ArkUI.Full
90
91**返回值:**
92
93| 名称                                                 | 说明                               |
94| ---------------------------------------------------- | ---------------------------------- |
95| [KeyboardAvoidMode](ts-types.md#keyboardavoidmode11) | 返回虚拟键盘抬起时的页面避让模式。 |
96
97## 示例
98
99### 示例1(实现沉浸式效果)
100
101该示例通过设置expandSafeArea属性向顶部和底部扩展安全区实现沉浸式效果。
102
103```ts
104// xxx.ets
105@Entry
106@Component
107struct SafeAreaExample1 {
108  @State text: string = ''
109  controller: TextInputController = new TextInputController()
110
111  build() {
112    Row() {
113        Column()
114          .height('100%').width('100%')
115          .backgroundImage($r('app.media.bg')).backgroundImageSize(ImageSize.Cover)
116          .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
117    }.height('100%')
118  }
119}
120```
121
122![expandSafeArea1](figures/expandSafeArea1.png)
123
124### 示例2(同时设置固定宽高和expandSafeArea属性)
125
126该示例展示了同时设置固定宽高和expandSafeArea属性的效果。
127
128```ts
129// xxx.ets
130@Entry
131@Component
132struct SafeAreaExample2 {
133  @State text: string = ''
134  controller: TextInputController = new TextInputController()
135
136  build() {
137    Column() {
138      TextInput({ text: this.text, placeholder: 'input your word...', controller: this.controller })
139        .placeholderFont({ size: 14, weight: 400 })
140        .width(320).height(40).offset({y: 120})
141        .fontSize(14).fontColor(Color.Black)
142        .backgroundColor(Color.White)
143    }
144    .height('780')
145    .width('100%')
146    .backgroundColor('rgb(179,217,235)')
147    .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
148  }
149}
150```
151
152如下图:Column组件扩展至了顶部状态栏[SafeAreaEdge.TOP],未扩展至底部导航条[SafeAreaEdge.BOTTOM],扩展后的组件高度与设置的高度一致。
153
154![expandSafeArea2](figures/expandSafeArea2.png)
155
156
157### 示例3(键盘避让时固定背景图位置)
158
159该示例通过为背景图组件设置expandSafeArea属性,来实现拉起键盘进行避让时,背景图保持不动的效果。
160
161```ts
162// xxx.ets
163@Entry
164@Component
165struct SafeAreaExample3 {
166  @State text: string = ''
167  controller: TextInputController = new TextInputController()
168
169  build() {
170    Row() {
171      Stack() {
172        Column()
173          .height('100%').width('100%')
174          .backgroundImage($r('app.media.bg')).backgroundImageSize(ImageSize.Cover)
175          .expandSafeArea([SafeAreaType.KEYBOARD, SafeAreaType.SYSTEM])
176        Column() {
177          Button('Set caretPosition 1')
178            .onClick(() => {
179              this.controller.caretPosition(1)
180            })
181          TextInput({ text: this.text, placeholder: 'input your word...', controller: this.controller })
182            .placeholderFont({ size: 14, weight: 400 })
183            .width(320).height(40).offset({y: 120})
184            .fontSize(14).fontColor(Color.Black)
185            .backgroundColor(Color.White)
186        }.width('100%').alignItems(HorizontalAlign.Center)
187      }
188    }.height('100%')
189  }
190}
191```
192
193![expandSafeArea3](figures/expandSafeArea3.png)
194
195### 示例4(设置键盘避让模式为压缩)
196
197该示例通过调用setKeyboardAvoidMode设置键盘避让模式为RESIZE模式,实现键盘抬起时page的压缩效果。
198
199```ts
200// EntryAbility.ets
201import { KeyboardAvoidMode } from '@kit.ArkUI';
202
203onWindowStageCreate(windowStage: window.WindowStage) {
204  // Main window is created, set main page for this ability
205  hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
206
207  windowStage.loadContent('pages/Index', (err, data) => {
208    let keyboardAvoidMode = windowStage.getMainWindowSync().getUIContext().getKeyboardAvoidMode();
209    // 设置虚拟键盘抬起时压缩页面大小为减去键盘的高度
210  windowStage.getMainWindowSync().getUIContext().setKeyboardAvoidMode(KeyboardAvoidMode.RESIZE);
211    if (err.code) {
212      hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
213      return;
214    }
215    hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
216  });
217}
218```
219
220```ts
221// xxx.ets
222@Entry
223@Component
224struct KeyboardAvoidExample1 {
225  build() {
226    Column() {
227      Row().height("30%").width("100%").backgroundColor(Color.Gray)
228      TextArea().width("100%").borderWidth(1)
229      Text("I can see the bottom of the page").width("100%").textAlign(TextAlign.Center).backgroundColor('rgb(179,217,235)').layoutWeight(1)
230    }.width('100%').height("100%")
231  }
232}
233```
234
235![keyboardAvoidMode1](figures/keyboardAvoidMode1.jpg)
236
237### 示例5(设置键盘避让模式为上抬)
238
239该示例通过调用setKeyboardAvoidMode设置键盘避让模式为OFFSET模式,实现键盘抬起时page的上抬效果。但当输入光标距离屏幕底部的高度大于键盘高度时,page不会抬起,如本例中所示。
240
241```ts
242// EntryAbility.ets
243import { KeyboardAvoidMode } from '@kit.ArkUI';
244
245onWindowStageCreate(windowStage: window.WindowStage) {
246  // Main window is created, set main page for this ability
247  hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
248
249  windowStage.loadContent('pages/Index', (err, data) => {
250    let keyboardAvoidMode = windowStage.getMainWindowSync().getUIContext().getKeyboardAvoidMode();
251    // 设置虚拟键盘抬起时把页面上抬直到露出光标
252  windowStage.getMainWindowSync().getUIContext().setKeyboardAvoidMode(KeyboardAvoidMode.OFFSET);
253    if (err.code) {
254      hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
255      return;
256    }
257    hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
258  });
259}
260```
261
262```ts
263// xxx.ets
264@Entry
265@Component
266struct KeyboardAvoidExample2 {
267  build() {
268    Column() {
269      Row().height("30%").width("100%").backgroundColor(Color.Gray)
270      TextArea().width("100%").borderWidth(1)
271      Text("I can see the bottom of the page").width("100%").textAlign(TextAlign.Center).backgroundColor('rgb(179,217,235)').layoutWeight(1)
272    }.width('100%').height("100%")
273  }
274}
275```
276
277![keyboardAvoidMode2](figures/keyboardAvoidMode2.jpg)
278
279### 示例6(切换避让模式)
280
281该示例通过调用setKeyboardAvoidMode来实现OFFSET、RESIZE和NONE模式之间的切换,实现三种不同的键盘避让效果。
282
283```ts
284import { hilog } from '@kit.PerformanceAnalysisKit';
285import { KeyboardAvoidMode } from '@kit.ArkUI';
286@Entry
287@Component
288
289struct KeyboardAvoidExample3 {
290  build() {
291    Column() {
292      Row({space:15}) {
293        Button('OFFSET')
294          .onClick(() => {
295            this.getUIContext().setKeyboardAvoidMode(KeyboardAvoidMode.OFFSET);
296            hilog.info(0x0000, 'keyboardAvoidMode: %{public}s', JSON.stringify(this.getUIContext().getKeyboardAvoidMode()));
297          })
298          .layoutWeight(1)
299        Button('RESIZE')
300          .onClick(() => {
301            this.getUIContext().setKeyboardAvoidMode(KeyboardAvoidMode.RESIZE);
302            hilog.info(0x0000, 'keyboardAvoidMode: %{public}s', JSON.stringify(this.getUIContext().getKeyboardAvoidMode()));
303          })
304          .layoutWeight(1)
305        Button('NONE')
306          .onClick(() => {
307            this.getUIContext().setKeyboardAvoidMode(KeyboardAvoidMode.NONE);
308            hilog.info(0x0000, 'keyboardAvoidMode: %{public}s', JSON.stringify(this.getUIContext().getKeyboardAvoidMode()));
309          })
310          .layoutWeight(1)
311      }
312      .height("30%")
313      .width("100%")
314      .backgroundColor(Color.Gray)
315
316      TextArea()
317        .width("100%")
318        .borderWidth(1)
319
320      Text("I can see the bottom of the page")
321        .width("100%")
322        .textAlign(TextAlign.Center)
323        .backgroundColor('rgb(179,217,235)')
324        .layoutWeight(1)
325
326      TextArea()
327        .width("100%")
328        .borderWidth(1)
329    }
330    .width('100%')
331    .height("100%")
332  }
333}
334```
335OFFSET模式
336
337![keyboardAvoidMode3-1](figures/keyboardAvoidMode3-1.jpg)
338
339RESIZE模式
340
341![keyboardAvoidMode3-2](figures/keyboardAvoidMode3-2.jpg)
342
343NONE模式
344
345![keyboardAvoidMode3-3](figures/keyboardAvoidMode3-3.jpg)
346
347### 示例7(滚动类容器扩展安全区)
348
349该示例通过在滚动类容器内调用expandSafeArea属性实现沉浸式效果。
350
351```ts
352class SwiperDataSource implements IDataSource {
353  private list: Array<Color> = []
354  constructor(list: Array<Color>) {
355    this.list = list
356  }
357  totalCount(): number {
358    return this.list.length
359  }
360  getData(index: number): Color {
361    return this.list[index]
362  }
363  registerDataChangeListener(listener: DataChangeListener): void {
364  }
365  unregisterDataChangeListener(listener: DataChangeListener): void {
366  }
367}
368@Entry
369@Component
370struct ExpandSafeAreaTest {
371  private swiperController: SwiperController = new SwiperController()
372  private swiperData: SwiperDataSource = new SwiperDataSource([])
373  private list: Array<Color> = [
374    Color.Pink,
375    Color.Blue,
376    Color.Green
377  ]
378  aboutToAppear(): void {
379    this.swiperData = new SwiperDataSource(this.list)
380  }
381  build() {
382    Scroll() {
383      Column() {
384        Swiper(this.swiperController) {
385          LazyForEach(this.swiperData, (item: Color, index: number) => {
386            Column() {
387              Text('banner' + index).fontSize(50).fontColor(Color.White)
388            }
389            .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
390            .width('100%')
391            .height(400)
392            .backgroundColor(item)
393          })
394        }
395        .loop(true)
396        .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
397        .clip(false)
398        Column(){
399          Text("Tab页Content").fontSize(50)
400        }.width("100%").height(1000)
401        .backgroundColor(Color.Grey)
402      }.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
403    }
404    .clip(false)
405    .edgeEffect(EdgeEffect.None)
406    .width("100%").height("100%")
407  }
408}
409```
410![expandSafeArea4](figures/expandSafeArea4.png)