鸿蒙HarmonyOS开发:安全区域、沉浸式页面开发实践,软键盘布局适配解决方案

一、安全区域

安全区域定义为页面的显示区域,其默认不与系统设置的非安全区域(如状态栏、导航栏)重叠,以确保开发者设计的界面均布局于安全区域内。然而,当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属性不影响子组件的布局,所以滚动到底部时提示文字默认会避让导航条。

常见效果 运行效果
在这里插入图片描述 在这里插入图片描述
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);
    });
  }

在这里插入图片描述

点击下方按钮添加微信,领取资料和学习方案。一键三连+关注,你的支持是我创作的动力。在这里,我乐于倾囊相授。

猜你喜欢

转载自blog.csdn.net/shanghai597/article/details/144670177
今日推荐