一、安全區域
安全區域定義為頁面的顯示區域,其默認不與系統設置的非安全區域(如狀態欄、導航欄)重疊,以確保開發者設計的界面均布局于安全區域內。然而,當Web組件啟用沉浸式模式時,網頁元素可能會出現與狀態欄或導航欄重疊的問題。具體示例如圖1所示,中間部分的區域即為安全區域,而頂部狀態欄、屏幕挖孔區域和底部導航條則被界定為避讓區,Web組件開啟沉浸式效果時,網頁內底部元素與導航條發生重疊。
提供屬性方法允許開發者設置組件繪制內容突破安全區域的限制,通過expandSafeArea屬性支持組件不改變布局情況下擴展其繪制區域至安全區外,通過設置setKeyboardAvoidMode來配置虛擬鍵盤彈出時頁面的避讓模式。頁面中有標題欄等文字不希望和非安全區重疊時,建議對組件設置expandSafeArea屬性達到沉浸式效果,也可以直接通過窗口接口setWindowLayoutFullScreen設置沉浸式。
說明
默認攝像頭挖孔區域不為非安全區域,頁面不避讓挖孔。
從API Version 12開始,可在module.json5中添加配置項, 攝像頭挖孔區域視為非安全區,實現頁面默認避讓挖孔:
"metadata": [
{
"name": "avoid_cutout",
"value": "true",
}
],
二、expandSafeArea屬性
控制組件擴展其安全區域。
expandSafeArea(types?: Array<SafeAreaType>, edges?: Array<SafeAreaEdge>)
三、軟鍵盤避讓模式
當用戶在輸入時,為了確保輸入框不會被鍵盤遮擋,系統提供了避讓模式來解決這一問題。開發者可以通過setKeyboardAvoidMode控制虛擬鍵盤抬起時頁面的避讓模式,避讓模式有上抬模式和壓縮模式兩種,鍵盤抬起時默認頁面避讓模式為上抬模式。
1、設置虛擬鍵盤抬起時頁面的避讓模式
setKeyboardAvoidMode(value: KeyboardAvoidMode): void
2、獲取虛擬鍵盤抬起時的頁面避讓模式
getKeyboardAvoidMode(): KeyboardAvoidMode
四、相關示例
1、實現沉浸式效果
通過設置expandSafeArea屬性向頂部和底部擴展安全區實現沉浸式效果。
// xxx.ets
@Entry
@Component
struct SafeAreaExample1 {
build() {
Column() {
// ......
}
.height('100%')
.width('100%')
.backgroundImage($r('app.media.bg'))
.backgroundImageSize(ImageSize.Cover)
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
}
}
2、滾動列表底部延伸場景
在列表滾動場景中,滾動時內容可與導航條區域重合,滾動到底部時,底部內容需避讓導航條。
設置列表組件List組件的expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM]),擴展列表底部到安全區域。此時List組件顯示區域擴大,滾動時列表內容可在導航條區域顯示。
// FaqList.ets
@Entry
@Component
struct FaqListPage {
listData: string[] = ['問題1', '問題2', '問題3', '問題4', '問題5', '問題6', '問題7', '問題8', '問題9'];
build() {
Column({ space: 10 }) {
Row() {
Text('常見問題')
.fontSize(22)
}
.height(100)
List({ space: 10 }) {
ForEach(this.listData, (item: string) => {
ListItem() {
Column({ space: 10 }) {
Text(item)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
Text(item + '內容')
.fontSize(16)
.fontColor('#999999')
}
.alignItems(HorizontalAlign.Start)
.padding(15)
.width('100%')
.height(150)
.borderRadius(10)
.backgroundColor(Color.White)
}
}, (item: string) => item)
ListItem() {
Text('已加載全部')
.width('100%')
.textAlign(TextAlign.Center)
.opacity(0.6)
.padding(10)
}
}
.padding(10)
.layoutWeight(1)
.scrollBar(BarState.Off)
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
}
.backgroundColor('#f1f3f5')
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
}
}
將滾動到底部的提示添加在列表項末尾,由于設置expandSafeArea屬性不影響子組件的布局,所以滾動到底部時提示文字默認會避讓導航條。
常見效果 | 運行效果 |
---|---|
image.png
|
image.png
|
3、重要信息被軟鍵盤遮擋
例如下面這個電子郵件的示例,內容由三部分組成:標題欄、內容區域和底部操作欄。當點擊輸入內容的輸入框,軟鍵盤會擋住底部的操作欄,影響用戶體驗,如下圖所示:
開發者可以通過設置軟鍵盤的避讓模式為KeyboardAvoidMode.RESIZE(壓縮模式),來解決底部操作欄被遮擋的問題,設置該屬性后,軟鍵盤的避讓會通過壓縮內容區域的高度來實現。示例代碼如下:
import { KeyboardAvoidMode } from '@kit.ArkUI';
@Entry
@Component
struct MailPage {
aboutToAppear(): void {
this.getUIContext().setKeyboardAvoidMode(KeyboardAvoidMode.RESIZE);
}
aboutToDisappear(): void {
this.getUIContext().setKeyboardAvoidMode(KeyboardAvoidMode.OFFSET);
}
build() {
Column() {
// 標題欄
this.NavigationTitle()
// 內容區域
this.EmailContent()
// 操作欄
this.BottomToolbar()
}
.height('100%')
.width('100%')
.backgroundColor('#f1f3f5')
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
}
@Builder
NavigationTitle() {
Row() {
Image($r('app.media.arrow_left'))
.width(24)
.height(24)
.margin({ right: 16 })
Text('新建電子郵件')
.fontSize(20)
.fontWeight(FontWeight.Bold)
Blank()
Image($r('app.media.paperplane'))
.width(24)
.height(24)
}
.width('100%')
.height(56)
.padding({
left: 24,
right: 24
})
}
@Builder
EmailContent() {
Column() {
this.RowInfo('發件人')
this.RowInfo('收件人')
this.RowInfo('主題')
Row() {
TextArea({ placeholder: '請輸入郵件正文' })
.height('100%')
.backgroundColor('#f1f3f5')
}
.layoutWeight(1)
.alignItems(VerticalAlign.Top)
.width('100%')
.margin({ top: 12 })
}.width('100%')
.layoutWeight(1)
.padding({ left: 24, right: 24 })
.margin({ top: 8 })
}
@Builder
RowInfo(param: string) {
Row() {
Text(`${param}`)
.fontColor('#6f7780')
.fontSize(16)
TextInput({ placeholder: `請輸入${param}` })
.width('100%')
.backgroundColor('#f1f3f5')
}
.width('100%')
.height(48)
.border({
width: { top: 1 },
color: '#e8ebed'
})
}
@Builder
BottomToolbar() {
Row({ space: 24 }) {
Image($r('app.media.folder'))
.ImageSize()
Image($r('app.media.picture'))
.ImageSize()
Image($r('app.media.arrow_up_circle'))
.ImageSize()
Image($r('app.media.share2'))
.ImageSize()
}
.width('100%')
.height(56)
.padding({ left: 24, right: 24 })
.border({
width: { top: 1 },
color: '#E8EBED'
})
}
@Styles
ImageSize() {
.height(24)
.width(24)
}
}
4、軟鍵盤彈出導致布局錯位
例如下面這樣的一個聊天界面,頂部是一個自定義的標題,下方為可滾動聊天消息區域,底部是消息輸入框。但是由于軟鍵盤避讓默認是上抬模式,會把整個頁面向上抬起,所以標題也會被頂上去,如下圖所示。現在需求希望頂部標題固定,點擊底部輸入框軟鍵盤彈起的時候,標題不上抬,只有內容區域上抬。
需要給對應的組件設置 .expandSafeArea([SafeAreaType.KEYBOARD])}屬性,使標題組件不避讓鍵盤,示例代碼如下:
- 先設置窗口為全屏模式。
// EntryAbility.ets
onWindowStageCreate(windowStage: window.WindowStage): void {
// Main window is created, set main page for this ability
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
// 獲取該WindowStage實例下的主窗口。
const mainWindow = windowStage.getMainWindowSync();
// 設置主窗口的布局是否為沉浸式布局。
mainWindow.setWindowLayoutFullScreen(true).then(() => {
hilog.info(0x0000, 'testTag', 'Succeeded in setting the window layout to full-screen mode');
}).catch((err: BusinessError) => {
hilog.info(0x0000, 'testTag', 'Failed to set the window layout to full-screen mode. Cause: %{public}s', JSON.stringify(err) ?? '');
})
// ...
}
- 再設置標題組件不避讓鍵盤
// ContactPage.ets
@Component
export struct ContactPage {
build() {
Column() {
Row() { // 頂部自定義標題欄
// ...
}
.height('12%')
.expandSafeArea([SafeAreaType.KEYBOARD]) // 標題組件不避讓鍵盤
.zIndex(1)
List() { // 聊天消息區域
// ...
}
.height('76%')
Column(){ // 底部消息輸入框
// ...
}
.height('12%')
}
.width('100%')
.height('100%')
}
}
5、自定義彈窗被鍵盤頂起
在軟鍵盤系統避讓機制中介紹過,彈窗為避讓軟鍵盤會進行避讓,整體向上抬,這樣可能會影響用戶體驗。比如下面這個評論里列表的彈窗,使用@CustomDialog實現的。當用戶點擊彈窗底部的輸入框的時候,彈窗會整體上抬,輸入框上抬的距離也過多。
為了解決以上問題,可以使用Navigation.Dialog,通過設置NavDestination的mode為NavDestinationMode.DIALOG彈窗類型,此時整個NavDestination默認透明顯示,示例代碼如下:
// NavDestinationModeDemo.ets
import { Chat, chatList } from '../model/CommentData';
@Entry
@Component
struct NavDestinationModeDemo {
@Provide('NavPathStack') pageStack: NavPathStack = new NavPathStack()
@Builder
PagesMap(name: string) {
if (name === 'DialogPage') {
DialogPage()
}
}
build() {
Navigation(this.pageStack) {
Column() {
Button('點擊評論')
.onClick(() => {
this.pageStack.pushPathByName('DialogPage', '');
})
}
.height('100%')
.width('100%')
.justifyContent(FlexAlign.Center)
}
.mode(NavigationMode.Stack)
.navDestination(this.PagesMap)
}
}
@Component
export struct DialogPage {
@Consume('NavPathStack') pageStack: NavPathStack;
build() {
NavDestination() {
Stack({ alignContent: Alignment.Bottom }) {
Column() {
Row() {
Text('評論')
.fontSize(20)
.fontWeight(FontWeight.Bold)
Blank()
Button() {
Image($r('app.media.cancel'))
.width(18)
}
.padding(10)
.backgroundColor('rgba(0,0,0,0.05)')
.onClick(() => {
this.pageStack.pop();
})
}
.padding(15)
.width('100%')
List({space:20}) {
ForEach(chatList, (item: Chat) => {
ListItem() {
Row({space:10}) {
Image(item.profile)
.width(40)
.height(40)
.borderRadius(40)
Column({ space: 10 }) {
Text(item.nickname)
.fontSize(16)
.fontColor('#999999')
Text(item.content)
.fontSize(16)
.fontColor('#333333')
}
.width('100%')
.justifyContent(FlexAlign.Start)
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.justifyContent(FlexAlign.Start)
.alignItems(VerticalAlign.Top)
}
})
}
.scrollBar(BarState.Off)
.width('100%')
.layoutWeight(1)
TextInput({ placeholder: '寫評論' })
.height(40)
.width('100%')
}
.borderRadius({
topLeft: 32,
topRight: 32
})
.backgroundColor(Color.White)
.height('75%')
.width('100%')
.padding(10)
}
.height('100%')
.width('100%')
}
.backgroundColor('rgba(0,0,0,0.2)')
.hideTitleBar(true)
.mode(NavDestinationMode.DIALOG)
}
}
此外還需要設置軟鍵盤避讓模式為壓縮模式,示例代碼如下:
// EntryAbility.ets
import { KeyboardAvoidMode, window } from '@kit.ArkUI';
onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent('pages/CustomDialogDemoPage', (err) => {
// 設置虛擬鍵盤抬起時壓縮頁面大小為減去鍵盤的高度
windowStage.getMainWindowSync().getUIContext().setKeyboardAvoidMode(KeyboardAvoidMode.RESIZE);
});
}