HarmonyOS 实现沉浸式效果

news/2024/12/22 14:29:40/

请添加图片描述

👨🏻‍💻 热爱摄影的程序员
👨🏻‍🎨 喜欢编码的设计师
🧕🏻 擅长设计的剪辑师
🧑🏻‍🏫 一位高冷无情的全栈工程师
欢迎分享 / 收藏 / 赞 / 在看!

典型应用全屏窗口 UI 元素包括状态栏、应用界面和底部导航条。开发应用沉浸式效果主要指通过调整状态栏、应用界面和导航条的显示效果来减少状态栏导航条等系统界面的突兀感,从而使用户获得最佳的 UI 体验。

在这里插入图片描述

开发应用沉浸式效果主要要考虑如下几个设计要素:

  1. UI 元素避让处理:导航条底部区域可以响应点击事件,除此之外的可交互 UI 元素和应用关键信息不建议放到导航条区域。
  2. 沉浸式效果处理:将状态栏和导航条颜色与界面元素颜色相匹配,不出现明显的突兀感。

针对上面的设计要求,可以通过如下两种方式实现应用沉浸式效果:

  1. 窗口全屏布局方案:调整布局系统为全屏布局,界面元素延伸到状态栏和导航条区域实现沉浸式效果,然后通过接口查询状态栏和导航条区域进行可交互元素避让处理。
  2. 组件安全区方案:布局系统保持安全区内布局(安全区:界面上排除状态栏和导航条区域),然后通过接口延伸绘制内容(如背景色,背景图)到状态栏和导航条区域实现沉浸式效果。
    该方案下,界面元素仅做绘制延伸,无法单独布局到状态栏和导航条区域,针对需要单独布局UI元素到状态栏和导航条区域的场景建议使用窗口全屏布局方案处理。

窗口全屏布局方案

窗口全屏布局方案主要涉及以下应用扩展布局,全屏显示和应用扩展布局,隐藏避让区两个应用场景。

应用扩展布局,全屏显示

可以通过调用窗口强制全屏布局接口(setWindowLayoutFullScreen())实现界面元素覆盖到状态栏和导航条,获取到状态栏和导航条高度后进行避让处理。
该布局方案相对灵活,开发者可以通过获取到状态栏和导航条的区域,从而进行避让处理。

  1. 调用 setWindowLayoutFullScreen() 接口设置窗口全屏。
onWindowStageCreate(windowStage: window.WindowStage): void {// other code...this.setFullSize(windowStage);// other code...
}/*** 将窗口设置为全屏* @param windowStage*/
setFullSize(windowStage: window.WindowStage) {let windowClass: window.Window = windowStage.getMainWindowSync(); // 获取应用主窗口// other code...// 设置窗口全屏let isLayoutFullScreen = true;windowClass.setWindowLayoutFullScreen(isLayoutFullScreen).then(() => {console.info('Succeeded in setting the window layout to full-screen mode.');}).catch((err: BusinessError) => {console.error('Failed to set the window layout to full-screen mode. Cause:' + JSON.stringify(err));});
}
  1. 使用 getWindowAvoidArea() 接口获取布局遮挡区域(例如状态栏、导航条)。
setFullSize(windowStage: window.WindowStage) {let windowClass: window.Window = windowStage.getMainWindowSync(); // 获取应用主窗口// other code...this.getAvoidAreaHeight(windowClass);
}/*** 获取布局避让遮挡的区域高度* @param windowClass*/
private getAvoidAreaHeight(windowClass: window.Window) {// 状态栏避让let statusBarType = window.AvoidAreaType.TYPE_SYSTEM;let statusBarAvoidArea = windowClass.getWindowAvoidArea(statusBarType);let statusBarHeight = statusBarAvoidArea.topRect.height; // 获取状态栏区域的高度AppStorage.setOrCreate('statusBarHeight', px2vp(statusBarHeight));// 导航条避让let indicatorType = window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR;let indicatorAvoidArea = windowClass.getWindowAvoidArea(indicatorType);let indicatorHeight = indicatorAvoidArea.bottomRect.height; // 获取导航条区域的高度AppStorage.setOrCreate('indicatorHeight', px2vp(indicatorHeight));
}
  1. 在布局中对具体控件布局避让遮挡的区域。例如增加 margin 属性,或者增加一个 Block() 空节点。
@Entry
@Component
struct Immersion {@State message: string = 'Immersion';statusBarHeight: number = AppStorage.get<number>('statusBarHeight') as number || 0;indicatorHeight: number = AppStorage.get<number>('indicatorHeight') as number || 0;build() {RelativeContainer() {Text(this.message).id('TextImmersion').fontSize(50).fontWeight(FontWeight.Bold).alignRules({center: { anchor: '__container__', align: VerticalAlign.Center },middle: { anchor: '__container__', align: HorizontalAlign.Center }})}.width('100%').backgroundColor('#67C23A').margin({top: this.statusBarHeight,bottom: this.indicatorHeight})}
}

在这里插入图片描述

应用扩展布局,隐藏避让区

此场景下导航条会自动隐藏,适用于游戏、电影等应用场景。可以通过从底部上滑唤出导航条。

在这里插入图片描述

  1. 调用 setWindowLayoutFullScreen() 接口设置窗口全屏。
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';export default class EntryAbility extends UIAbility {// ...onWindowStageCreate(windowStage: window.WindowStage): void {windowStage.loadContent('pages/Index', (err, data) => {if (err.code) {return;}let windowClass: window.Window = windowStage.getMainWindowSync(); // 获取应用主窗口// 1. 设置窗口全屏let isLayoutFullScreen = true;windowClass.setWindowLayoutFullScreen(isLayoutFullScreen).then(() => {console.info('Succeeded in setting the window layout to full-screen mode.');}).catch((err: BusinessError) => {console.error(`Failed to set the window layout to full-screen mode. Code is ${err.code}, message is ${err.message}`);});});}
}
  1. 调用 setSpecificSystemBarEnabled() 接口设置状态栏和导航条的具体显示/隐藏状态,此场景下将其设置为隐藏。
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';export default class EntryAbility extends UIAbility {// ...onWindowStageCreate(windowStage: window.WindowStage): void {windowStage.loadContent('pages/Index', (err, data) => {if (err.code) {return;}let windowClass: window.Window = windowStage.getMainWindowSync(); // 获取应用主窗口// 1. 设置窗口全屏// ...// 2. 设置状态栏和导航条隐藏windowClass.setSpecificSystemBarEnabled('status', false).then(() => {console.info('Succeeded in setting the status bar to be invisible.');}).catch((err: BusinessError) => {console.error(`Failed to set the status bar to be invisible. Code is ${err.code}, message is ${err.message}`);});});}
}
  1. 在界面中无需进行导航条避让操作。
@Entry()
@Component
struct Index {build() {Row() {Column() {Row() {Text('ROW1').fontSize(40)}.backgroundColor(Color.Orange).padding(20)Row() {Text('ROW2').fontSize(40)}.backgroundColor(Color.Orange).padding(20)Row() {Text('ROW3').fontSize(40)}.backgroundColor(Color.Orange).padding(20)Row() {Text('ROW4').fontSize(40)}.backgroundColor(Color.Orange).padding(20)Row() {Text('ROW5').fontSize(40)}.backgroundColor(Color.Orange).padding(20)Row() {Text('ROW6').fontSize(40)}.backgroundColor(Color.Orange).padding(20)}.width('100%').height('100%').alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.SpaceBetween).backgroundColor('#008000')}}
}

组件安全区方案

应用未使用 setWindowLayoutFullScreen() 接口设置窗口全屏布局时,默认使能组件安全区布局。

应用在默认情况下窗口背景绘制范围是全屏,但 UI 元素被限制在安全区内(自动排除状态栏和导航条)进行布局,来避免界面元素被状态栏和导航条遮盖。

界面元素自动避让状态栏和导航条示意图:

在这里插入图片描述

针对状态栏和导航条颜色与界面元素颜色不匹配问题,可以通过如下两种方式实现沉浸式效果:

  • 状态栏和导航条颜色相同场景,可以通过设置窗口的背景色来实现沉浸式效果。窗口背景色可通过 setWindowBackgroundColor() 进行设置。
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';export default class EntryAbility extends UIAbility {// ...onWindowStageCreate(windowStage: window.WindowStage): void {windowStage.loadContent('pages/Index', (err, data) => {if (err.code) {return;}// 设置全窗颜色和应用元素颜色一致windowStage.getMainWindowSync().setWindowBackgroundColor('#008000');});}
}

界面状态栏和导航条颜色相同场景。

// xxx.ets
@Entry
@Component
struct Example {build() {Column() {Row() {Text('ROW1').fontSize(40)}.backgroundColor(Color.Orange).padding(20)Row() {Text('ROW2').fontSize(40)}.backgroundColor(Color.Orange).padding(20)Row() {Text('ROW3').fontSize(40)}.backgroundColor(Color.Orange).padding(20)Row() {Text('ROW4').fontSize(40)}.backgroundColor(Color.Orange).padding(20)Row() {Text('ROW5').fontSize(40)}.backgroundColor(Color.Orange).padding(20)Row() {Text('ROW6').fontSize(40)}.backgroundColor(Color.Orange).padding(20)}.width('100%').height('100%').alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.SpaceBetween).backgroundColor('#008000')}
}

在这里插入图片描述

  • 状态栏和导航条颜色不同时,可以使用 expandSafeArea 属性扩展安全区域属性进行调整。
// xxx.ets
@Entry
@Component
struct Example {build() {Column() {Row() {Text('Top Row').fontSize(40).textAlign(TextAlign.Center).width('100%')}.backgroundColor('#F08080')// 设置顶部绘制延伸到状态栏.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP])Row() {Text('ROW2').fontSize(40)}.backgroundColor(Color.Orange).padding(20)Row() {Text('ROW3').fontSize(40)}.backgroundColor(Color.Orange).padding(20)Row() {Text('ROW4').fontSize(40)}.backgroundColor(Color.Orange).padding(20)Row() {Text('ROW5').fontSize(40)}.backgroundColor(Color.Orange).padding(20)Row() {Text('Bottom Row').fontSize(40).textAlign(TextAlign.Center).width('100%')}.backgroundColor(Color.Orange)// 设置底部绘制延伸到导航条.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])}.width('100%').height('100%').alignItems(HorizontalAlign.Center).backgroundColor('#008000').justifyContent(FlexAlign.SpaceBetween)}
}

在这里插入图片描述

扩展安全区域属性原理

  • 布局阶段按照安全区范围大小进行 UI 元素布局。
  • 布局完成后查看设置了 expandSafeArea 的组件边界(不包括 margin)是否和安全区边界相交。
  • 如果设置了 expandSafeArea 的组件和安全区边界相交,根据 expandSafeArea 传递的属性则进一步扩大组件绘制区域大小覆盖状态栏、导航条这些非安全区域。
  • 上述过程仅改变组件自身绘制大小,不进行二次布局,不影响子节点和兄弟节点的大小和位置。
  • 子节点可以单独设置该属性,只需要自身边界和安全区域重合就可以延伸自身大小至非安全区域内,需要确保父组件未设置 clip 等裁切属性。
  • 配置 expandSafeArea 属性组件进行绘制扩展时,需要关注组件不能配置固定宽高尺寸,百分比除外。

背景图和视频场景

设置背景图、视频控件大小为安全区域大小并配置 expandSafeArea 属性。

// xxx.ets
@Entry
@Component
struct SafeAreaExample1 {build() {Stack() {Image($r('app.media.bg')).height('100%').width('100%').expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM]) // 图片组件的绘制区域扩展至状态栏和导航条。}.height('100%').width('100%')}
}

在这里插入图片描述

滚动类场景

要求需要 List 滚动类组件滚动过程中元素可以和导航条重合,滚动至底部时,元素在导航条上面需要避让。

在这里插入图片描述

由于 expandSafeArea 不改变子节点布局,因此,List 等滚动类组件可以调用 expandSafeArea,延伸 List 组件视图窗口大小而不改变 ListItem 内在布局。实现 ListItem 在滑动过程中显示在导航条下,但滚动至最后一个时显示在导航条上。

未适配时列表下方被导航条遮盖:

在这里插入图片描述

List配置 expandSafeArea 属性后的效果:

在这里插入图片描述

在这里插入图片描述

仅扩展底部导航条。

  1. 配置窗口整体底色。
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';export default class EntryAbility extends UIAbility {// ...onWindowStageCreate(windowStage: window.WindowStage): void {windowStage.loadContent('pages/Index', (err, data) => {if (err.code) {return;}windowStage.getMainWindowSync().setWindowBackgroundColor('#DCDCDC'); // 配置窗口整体底色});}
}
  1. 界面代码展示。
// xxx.ets
@Entry
@Component
struct ListExample {private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]build() {Column() {List({ space: 20, initialIndex: 0 }) {ForEach(this.arr, (item: number) => {ListItem() {Text('' + item).width('100%').height(100).fontSize(16).textAlign(TextAlign.Center).borderRadius(10).backgroundColor(0xFFFFFF)}}, (item: string) => item)}.listDirection(Axis.Vertical) // 排列方向.scrollBar(BarState.Off).friction(0.6).divider({ strokeWidth: 2, color: 0xFFFFFF, startMargin: 20, endMargin: 20 }) // 每行之间的分界线.edgeEffect(EdgeEffect.Spring) // 边缘效果设置为Spring.width('90%')// List组件的视窗范围扩展至导航条。.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])}.width('100%').height('100%').padding({ top: 15 })}
}

底部页签场景

要求页签背景色能够延伸到导航条区域,但页签内部可操作元素需要在导航条之上。

在这里插入图片描述

针对底部的页签部分,Navigation 组件和 Tabs 组件默认实现了页签的延伸处理,开发者只需要保证 Navigation 和 Tabs 组件的底部边界和底部导航条重合即可。若开发者显式调用 expandSafeArea 接口,则安全区效果由 expandSafeArea 参数指定。

如果未使用上述组件而是采用自定义方式实现页签的场景,可以针对底部元素设置 expandSafeArea 属性实现底部元素的背景扩展。

顶部和底部 UI 元素未设置和设置 expandSafeArea 属性效果对比:

在这里插入图片描述

// xxx.ets
@Entry
@Component
struct VideoCreateComponent {build() {Column() {Row() {Text('Top Row').fontSize(40).textAlign(TextAlign.Center).width('100%')}.backgroundColor('#F08080')// 设置顶部绘制延伸到状态栏.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP])Row() {Text('ROW2').fontSize(40)}.backgroundColor(Color.Orange).padding(20)Row() {Text('ROW3').fontSize(40)}.backgroundColor(Color.Orange).padding(20)Row() {Text('ROW4').fontSize(40)}.backgroundColor(Color.Orange).padding(20)Row() {Text('ROW5').fontSize(40)}.backgroundColor(Color.Orange).padding(20)Row() {Text('Bottom Row').fontSize(40).textAlign(TextAlign.Center).width('100%')}.backgroundColor(Color.Orange)// 设置底部绘制延伸到导航条.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])}.width('100%').height('100%').alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.SpaceBetween).backgroundColor(Color.Green)}
}

图文场景

当状态栏元素和底部导航条元素不同时,无法单纯通过窗口背景色或者背景图组件延伸实现,此时需要对顶部元素和底部元素分别配置 expandSafeArea 属性,顶部元素配置 expandSafeArea([SafeAreaType.SYSTEM],[SafeAreaEdge.TOP]),底部元素配置 expandSafeArea([SafeAreaType.SYSTEM],[SafeAreaEdge.BOTTOM])

在这里插入图片描述

@Entry
@Component
struct Index {build() {Swiper() {Column() {Image($r('app.media.start')).height('50%').width('100%')// 设置图片延伸到状态栏.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP])Column() {Text('HarmonyOS 第一课').fontSize(32).margin(30)Text('通过循序渐进的学习路径,无经验和有经验的开发者都可以掌握ArkTS语言声明式开发范式,体验更简洁、更友好的HarmonyOS应用开发旅程。').fontSize(20).margin(20)}.height('50%').width('100%').backgroundColor(Color.White)// 设置文本内容区背景延伸到导航栏.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])}}.width('100%').height('100%')// 关闭Swiper组件默认的裁切效果以便子节点可以绘制在Swiper外。.clip(false)}
}

http://www.ppmy.cn/news/1527207.html

相关文章

牛客周赛 Round 60(A,B,C,D,E,F)

比赛链接 官方题解 这场基本都是数学题&#xff0c;官方题解讲的还不错&#xff0c;F能听懂的话其实不难。E是一个球盒模型的组合问题&#xff0c;F是化简递推式&#xff0c;成环时的解决方法很不错。 A 困难数学题 思路&#xff1a; 一个数异或两次结果为 0 0 0&#xff…

CleanMyMac 5 for Mac 最新中文破解版下载 系统优化垃圾清理工具

今天给大家带来的是CleanMyMac最新款CleanMyMac 5&#xff0c;它是一个全面的Mac清理和维护工具&#xff0c;通过提供多项强大的功能&#xff0c;帮助用户简化日常维护任务&#xff0c;提升系统性能&#xff0c;同时保护个人隐私和安全。无论是新手还是经验丰富的Mac用户&#…

Axure设计之表格列冻结(动态面板+中继器)

在Web端产品设计中&#xff0c;复杂的表格展示是常见需求&#xff0c;尤其当表格包含大量列时&#xff0c;如何在有限的屏幕空间内优雅地展示所有信息成为了一个挑战。用户通常需要滚动查看隐藏列&#xff0c;但关键信息列&#xff08;如ID、操作按钮等&#xff09;在滚动时保持…

【数据分析】标准误差与标准差的区别

标准误差&#xff08;Standard Error, SE&#xff09;和标准差&#xff08;Standard Deviation, SD&#xff09;是两个在统计学中非常重要的概念&#xff0c;但它们的含义和用途有所不同。以下是它们之间的主要区别&#xff1a; 定义&#xff1a; 标准差&#xff1a;衡量单个数…

网络设备登录——《路由与交换技术》实验报告

目录 一、实验目的 二、实验设备和环境 三、实验记录 1.通过 Console 登录 步骤1:连接配置电缆。 步骤2:启动PC,运行超级终端。 步骤3:进入Console 配置界面 2.通过 Telnet 登录 步骤1:通过 Console 接口配置 Telnet 用户。 步骤2:配置 super 口令 步骤3:配置登录欢迎…

golang学习笔记25——golang 实现 MD5加密、RSA加密 和 Base64编码

推荐学习文档 golang应用级os框架&#xff0c;欢迎stargolang应用级os框架使用案例&#xff0c;欢迎star案例&#xff1a;基于golang开发的一款超有个性的旅游计划app经历golang实战大纲golang优秀开发常用开源库汇总想学习更多golang知识&#xff0c;这里有免费的golang学习笔…

在Unity UI中实现UILineRenderer组件绘制线条

背景介绍 在Unity的UI系统中&#xff0c;绘制线条并不像在3D世界中那样直观(使用Unity自带的LineRender组件在UI中连线并不方便,它在三维中更合适)。没有内置的工具来处理这种需求。如果你希望在UI元素之间绘制连接线&#xff08;例如在UI上连接不同的图标或控件&#xff09;&a…

用 Python 实现将长 Markdown 文档从二级标题开始拆分

以下是一个简单的Python脚本&#xff0c;它可以将Markdown文档按照二级标题&#xff08;##&#xff09;进行拆分&#xff0c;并保存到指定的输出路径。 import osdef split_markdown_by_headers(input_path, output_folder):# 确保输出文件夹存在if not os.path.exists(output…