概述
横竖屏切换是一种常见的功能,用户在一些场景下,需要在横屏状态浏览应用,例如在进行视频播放的场景,一些16:9的视频在横向播放时,全屏的体验更加。所以应用需要支持切换到横屏全屏的能力。在开发时,部分应用需要提供全屏按钮,让用户选择开启横竖屏播放,另外我们也需要考虑系统是否开启了方向锁定,在方向锁定的情况下,用户只能通过手动横屏来实现全屏,而在方向没有锁定下,用户将设备横向的情况下,应用需要自动切换到横屏进行播放。
本文中将介绍如何进行横竖屏功能的开发,以及横竖屏开发中常见的问题及其优化方案。
横竖屏开发流程
为了实现应用的横竖屏功能,我们需要从以下技术方面进行考虑:
1、设置界面的旋转策略
2、监听屏幕的窗口变化
3、根据方向进行布局适配
设置界面的旋转策略
1、配置应用默认选择方式
初次进入应用时的旋转策略可以通过module.json5文件中对应Ability的"orientation"字段进行配置,此字段控制的是应用打开后的旋转方式,
{
"module": {
...
,
"abilities": [
{
"name": "EntryAbility",
...,
"orientation":"auto_rotation" // 需要配置为相应的旋转策略
}
]
}
}
其支持的参数可以参考module.json5配置项其中orientation字段相关配置:
根据应用默认的旋转行为进行相应的配置:
- 如果应用是竖屏应用,建议配置portrait为默认旋转策略。
- 如果应用是横屏应用,建议配置landscape为默认旋转策略。
- 如果应用为可旋转应用,建议应用配置auto_rotation_restricted为默认旋转策略。
- 如果一个应用,在直板机和折叠机折叠态是竖屏应用,在平板和折叠机展开态默认是可旋转应用,推荐配置follow_desktop为默认旋转策略。
以如下备忘录为例,在系统关闭了旋转锁定后,应用的页面都会随着手机旋转而发生展示上的切换,此时就需要配置为auto_rotation_restricted。
图1 应用随系统旋转切换横竖屏
2、支持手动设置旋转
部分应用的旋转策略可能在不同页面是不同的,例如视频播放页面支持横屏,但是首页的内容是支持仅竖屏的,那么就需要针对单独的页面,采用window窗口提供的设置窗口方向的能力,可以将窗口显示的方向修改为横屏、竖屏、反向横屏、反向竖屏的状态。在HarmonyOS系统中,窗口的状态对应真机实际状态如下:
图2 窗口形态示意图
在使用时,需根据应用自身的旋转策略选择相应的参数,可以封装如下方法,进行旋转策略的设置,方法中通过window.getLastWindow拿到窗口实例,然后调用window实例的setPreferredOrientation设置旋转策略。
setOrientation(orientation: number) {
window.getLastWindow(getContext(this)).then((win) => {
win.setPreferredOrientation(orientation).then((data) => {
console.log('setWindowOrientation: ' + orientation + ' Succeeded. Data: ' + JSON.stringify(data));
}).catch((err: string) => {
console.log('setWindowOrientation: Failed. Cause: ' + JSON.stringify(err));
});
}).catch((err: string) => {
console.log('setWindowOrientation: Failed to obtain the top window. Cause: ' + JSON.stringify(err));
});
以视频播放为例,不仅需要可以通过系统控制横竖屏,也支持用户在系统锁定旋转的情况下,手动设置横屏状态,即需要满足以下条件:
1、应用跟随传感器旋转。
2、受到控制中心的旋转锁定按钮控制。
3、支持用户使用应用时,在页面中临时调用设置方向能力,如点击全屏按钮进行切换。
图3 视频播窗横竖屏切换
要实现以上效果,可以使用窗口的能力设置orentation的枚举类型进行相应的旋转,由于要提供临时设置方向的能力,用户在手动点击全屏按钮时,相当于需要手动触发横竖屏切换,如果此时关闭了旋转锁定,那么窗口需要能够跟随传感器一起发生旋转,所以可以使用以下枚举中的能力,临时调用旋转,并让其后续也能支持跟随传感器。
Orentation枚举值 | 枚举数值 | 效果描述 |
---|---|---|
USER_ROTATION_PORTRAIT | 13 | 调用时临时旋转到竖屏,之后跟随传感器自动旋转,受控制中心的旋转开关控制,且可旋转方向受系统判定。 |
USER_ROTATION_LANDSCAPE | 14 | 调用时临时旋转到横屏,之后跟随传感器自动旋转,受控制中心的旋转开关控制,且可旋转方向受系统判定。 |
然后来分析旋转方向:对于视频播放的应用,一般旋转方向不会旋转到反向竖屏(由UX需求决定)。那么并且用户在使用时,由竖屏点击切换到横屏时,根据用户习惯,一般默认是旋转到横屏状态。
图4 窗口旋转状态示意图
由于在之前我们Ability内进行了权限设置,此时初次进入视频播放页面仍然会延续设置的旋转策略,所以需要再初次进入播放页面的地方,先设置为临时竖屏状态,即在页面的aboutToAppear回调中执行设置窗口旋转的方法:
- aboutToAppear() {
- this.setOrientation(USER_ROTATION_PORTRAIT);
- }
另外,在需要用户点击的地方,需要根据进去全屏和退出全屏分别执行相应的逻辑,需要使用的方向状态如下:
设置为竖屏时,对应窗口方向为竖屏状态:this.setOrientation(USER_ROTATION_PORTRAIT);
设置为横屏时,对应窗口方向为横屏状态:this.setOrientation(USER_ROTATION_LANDSCAPE);
- Button('> <').onClick(() => {
- if (this.isLandscape) {
- // 全屏时,横变竖
- this.setOrientation(USER_ROTATION_PORTRAIT);
- this.isLandscape = false;
- } else {
- // 非全屏时,竖变横
- this.setOrientation(USER_ROTATION_LANDSCAPE);
- this.isLandscape = true;
- }
- })
监听窗口变化
由于传感器变化或者用户手动设置窗口方向时,窗口的显示会发生变化,对应窗口的size也会发生改变,此时可以通过拿到窗口的宽高,并对宽高进行对比,判断当前显示是竖屏还是横屏状态,并利用该数据对布局进行适配。
监听窗口尺寸的变化可以通过window.on('windowSizeChange')进行实现。具体的措施如下,在需要进行横竖屏切换的页面进行以下窗口的监听,一般建议是在aboutToAppear中执行,并在aboutDisAppear中取消监听:
- win.on('windowSizeChange', (size) => {
- let viewWidth = px2vp(size.width);
- let viewHeight = px2vp(size.height);
- // 如果宽大于高,说明此时设置的窗口方向为横屏,相反为竖屏
- if (viewWidth > viewHeight) {
- // 横屏时的逻辑
- } else {
- // 竖屏时的逻辑
- }
- })
需要注意的时,当用户手动触发setOrientation设置为横屏状态时,即使当前手机处于垂直方向,窗口的状态也是横屏方向,即如下所示:
图5 临时设置窗口方向时,窗口宽高示意图
此时,窗口的宽是竖屏状态下的高,高变为竖屏状态的宽。所以,在监听窗口变化时,可以通过窗口的宽高大小关系,来确定当前窗口的方向,并决定实际的横竖屏状态。
- window.on('windowSizeChange', (size) => {
- let viewWidth = px2vp(size.width);
- let viewHeight = px2vp(size.height);
- // 如果宽大于高,说明此时设置的窗口方向为横屏,相反为竖屏
- if (viewWidth > viewHeight) {
- // 横屏时的逻辑
- this.isLandscape = true;
- } else {
- // 竖屏时的逻辑
- this.isLandscape = false;
- }
- })
进行布局适配
对应视频播放这类应用,属于只有播放窗口需要进行横竖屏,所以只需要对视频播放的组件内容,进行横屏并进入全屏,所以可以利用UI状态更新的特点,来让播窗变为全屏,将播窗的尺寸定义为@State状态,并设置导Xcomponent组件上。
- const aspect = 9/16 ; // 视频的比例,根据实际情况动态设置
- @Component
- struct VedioPlayView {
- // XComponent宽度
- @State xComponentWidth: number | string = px2vp(display.getDefaultDisplaySync().width);
- // XComponent高度
- @State xComponentHeight: number | string = px2vp(display.getDefaultDisplaySync().width * aspect);
- // 标志当前的横竖屏状态
- @State isLandscape: boolean = false;
- ...
- // 省略avplayer,控制器等变量定义
- build() {
- Stack({ alignContent: Alignment.BottomEnd }) {
- XComponent({ id: 'video_player_id', type: XComponentType.SURFACE, controller: this.mXComponentController })
- .onLoad(() => {
- // 初始化逻辑
- })
- .width(this.xComponentWidth)
- .height(this.xComponentHeight)
- ... // 其他播放控制按钮
- Button('> <').onClick(() => {
- if (this.isLandscape) {
- this.setOrientation(USER_ROTATION_PORTRAIT);
- this.isLandscape = false;
- } else { // 非全屏时,竖变横
- this.setOrientation(USER_ROTATION_LANDSCAPE_INVERTED);
- this.isLandscape = true;
- }
- })
- }
- }
- }
并且在之前监听窗口变化的回调中,对XComponentWidth和XComponentHeight进行动态修改,完成窗口变化时横屏和竖屏的视频窗口布局。需要注意的是,在横屏时,视频播放的宽高应该和窗口的宽高一样,并且需要进入全屏状态。而竖屏时,视频播放的宽应该等于窗口的宽,但是高度应该是按照播窗比例乘以窗口的宽进行设置,并退出全屏状态。
图6 旋转过程宽高变化示意图
- window.on('windowSizeChange', (size) => {
- let viewWidth = px2vp(size.width);
- let viewHeight = px2vp(size.height);
- // 如果宽大于高,说明此时设置的窗口方向为横屏,相反为竖屏
- if (viewWidth > viewHeight) {
- // 横屏时的逻辑,横屏时,把Xcomponent的宽高设置为窗口的宽高即可,并设置窗口全屏状态
- this.isLandscape = true;
- this.xComponentWidth = viewWidth;
- this.xComponentHeight = viewHeight;
- win.setWindowLayoutFullScreen(true);
- } else {
- // 竖屏时的逻辑,竖屏时,此时XComponent的宽应该是窗口的宽,但是高度应该是按播窗比例乘以窗口的宽,并退出全屏状态
- this.isLandscape = false;
- this.xComponentHeight = viewWidth * aspect;
- this.xComponentWidth = viewWidth;
- win.setWindowLayoutFullScreen(false);
- }
- })
到这里就能够完成一个基础的横竖屏功能的开发。
性能优化
由于在窗口旋转时,屏幕的尺寸会发生变化,界面会发生重新布局,为了提高横竖屏切换时的流畅度,需要进行相应的性能优化。
使用自定义组件冻结
旋转时,由于整窗一起旋转,会导致页面重新布局,但是实际上需要展示的可能只有播放内容,对于其他的组件可以使用自定义组件冻结功能,避免由于旋转导致的UI更新操作。例如视频播放底下的详情内容,可能是单独的组件。
- @Component({ freezeWhenInactive: true }) // 添加自定义组件冻结功能
- struct VideoDetailView {
- build() {
- Scroll() {
- // ... 详情内容
- }
- }
- }
对图片使用auto_resize
如果当前旋转页面存在一些图片,未经合理的裁剪,图片过大,可以对图片设置AutoSize属性,使图片裁剪到合适的大小进行绘制。该属性的作用是将组件显示区域作为绘制的图源尺寸,可以减少内存占用,例如原图是1920*1080,但是显示区域是200*100,则在解码时会降低采样编码到200*100尺寸。
- @Builder
- function ImageItem(imageSrc:ResourceStr) {
- Stack({}) {
- Image(imageSrc)
- .width('100%')
- .height('100%')
- .autoResize(true) // 对图片使用auto_resize属性
- .borderRadius(8)
- .objectFit(ImageFit.Fill)
- .backgroundColor('#1AFFFFFF')
- }
- }
排查一些耗时操作
排查当前页面是否存在一些冗余的OnAreaChange事件、blur模糊或者一些线性变化linearGradient的属性,这些都比较耗时,可以根据是否必须使用来决定是否进行优化。
其他常见问题
其他常见问题
Tab栏中的视频横屏播放,无法隐藏Tabs栏
对于首页中有部分视频可以直接播放,并且不会跳转至详情页播放的,需要支持直接在首页发生旋转,当前可以通过设置XComponent的宽高实现,但是会发现即使全屏后,tabs栏并不会消失。而是会一起发生旋转并存在于页面上。解决方案如下:
通过进入全屏时,隐藏Tabs,退出全屏时,展示Tabs栏。
- Tabs() {
- ...省略布局内容
- }
- .barHeight(this.isLayoutFullScreen ? 0 : 50)
- // 通过用户是否需要点击进入全屏,隐藏Tabs标签栏的高度,可以实现隐藏
最后
有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)资料用来跟着学习是非常有必要的。
点击领取→【纯血版鸿蒙全套最新学习资料】(安全链接,放心点击)希望这一份鸿蒙学习资料能够给大家带来帮助,有需要的小伙伴自行领取,限时开源,先到先得~无套路领取!!
这份鸿蒙(HarmonyOS NEXT)资料包含了鸿蒙开发必掌握的核心知识要点,内容包含了(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、(南向驱动、嵌入式等)鸿蒙项目实战等等)鸿蒙(HarmonyOS NEXT)技术知识点。
鸿蒙(HarmonyOS NEXT)最新学习路线
有了路线图,怎么能没有学习资料呢,小编也准备了一份联合鸿蒙官方发布笔记整理收纳的一套系统性的鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开发入门教学视频,内容包含:ArkTS、ArkUI、Web开发、应用模型、资源分类…等知识点。
获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料
HarmonyOS Next 最新全套视频教程
《鸿蒙 (OpenHarmony)开发基础到实战手册》
OpenHarmony北向、南向开发环境搭建
《鸿蒙开发基础》
- ArkTS语言
- 安装DevEco Studio
- 运用你的第一个ArkTS应用
- ArkUI声明式UI开发
- .……
《鸿蒙开发进阶》
- Stage模型入门
- 网络管理
- 数据管理
- 电话服务
- 分布式应用开发
- 通知与窗口管理
- 多媒体技术
- 安全技能
- 任务管理
- WebGL
- 国际化开发
- 应用测试
- DFX面向未来设计
- 鸿蒙系统移植和裁剪定制
- ……
《鸿蒙进阶实战》
- ArkTS实践
- UIAbility应用
- 网络案例
- ……
大厂面试必问面试题
鸿蒙南向开发技术
鸿蒙APP开发必备
鸿蒙生态应用开发白皮书V2.0PDF
总结
总的来说,华为鸿蒙不再兼容安卓,对中年程序员来说是一个挑战,也是一个机会。只有积极应对变化,不断学习和提升自己,他们才能在这个变革的时代中立于不败之地。