鴻蒙HarmonyOS NEXT開發:安全區域、沉浸式頁面開發實踐,軟鍵盤布局適配解決方案

一、安全區域

安全區域定義為頁面的顯示區域,其默認不與系統設置的非安全區域(如狀態欄、導航欄)重疊,以確保開發者設計的界面均布局于安全區域內。然而,當Web組件啟用沉浸式模式時,網頁元素可能會出現與狀態欄或導航欄重疊的問題。具體示例如圖1所示,中間部分的區域即為安全區域,而頂部狀態欄、屏幕挖孔區域和底部導航條則被界定為避讓區,Web組件開啟沉浸式效果時,網頁內底部元素與導航條發生重疊。

image.png

提供屬性方法允許開發者設置組件繪制內容突破安全區域的限制,通過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])
  }
}
image.png

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、重要信息被軟鍵盤遮擋

例如下面這個電子郵件的示例,內容由三部分組成:標題欄、內容區域和底部操作欄。當點擊輸入內容的輸入框,軟鍵盤會擋住底部的操作欄,影響用戶體驗,如下圖所示:

image.png

開發者可以通過設置軟鍵盤的避讓模式為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)
  }
}
image.png

4、軟鍵盤彈出導致布局錯位

例如下面這樣的一個聊天界面,頂部是一個自定義的標題,下方為可滾動聊天消息區域,底部是消息輸入框。但是由于軟鍵盤避讓默認是上抬模式,會把整個頁面向上抬起,所以標題也會被頂上去,如下圖所示。現在需求希望頂部標題固定,點擊底部輸入框軟鍵盤彈起的時候,標題不上抬,只有內容區域上抬。

image.png

需要給對應的組件設置 .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%')
  }
}
0ce12a80ee7645d994376966d69d2e00.gif

5、自定義彈窗被鍵盤頂起

在軟鍵盤系統避讓機制中介紹過,彈窗為避讓軟鍵盤會進行避讓,整體向上抬,這樣可能會影響用戶體驗。比如下面這個評論里列表的彈窗,使用@CustomDialog實現的。當用戶點擊彈窗底部的輸入框的時候,彈窗會整體上抬,輸入框上抬的距離也過多。

image.png

為了解決以上問題,可以使用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);
    });
  }
image.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容