HarmonyOS鸿蒙Next中求教:全屏模式下的自适应问题
HarmonyOS鸿蒙Next中求教:全屏模式下的自适应问题 在开发视频播放应用时,通过使用 Stack 布局包裹 Video组件和自定义控制栏实现了自定义控制栏(包含全屏按钮),当点击全屏按钮后出现以下两个问题:
- 调用 requestFullScreen()方法成功进入全屏,但自定义控制栏消失
- 手动切换设备横竖屏时,播放器状态(如进度、播放状态)被重置。
求教:
如何在全屏模式下保留自定义控制栏?横竖屏切换时如何保持播放状态?如何实现横屏/竖屏自适应全屏(根据视频宽高比动态切换)?
不要使用requestFullScreen() 有更好的解决方案!
- 通过setPreferredOrientation方法设置窗口方向旋转模式为跟随传感器自动旋转。
- 监听横竖屏变化,获取设备当前定向。
aboutToAppear() {
setOrientation(this.context);
// 监听横竖屏变化
this.windowClass = AppStorage.get('windowClass');
this.windowClass?.on('windowSizeChange', () => {
const DEFAULT_DISPLAY = display.getDefaultDisplaySync();
this.orientation = DEFAULT_DISPLAY.orientation;
});
}
export function setOrientation(context: UIContext) {
try {
window.getLastWindow(context.getHostContext(), (err, data) => { // 获取window实例
if (err.code) {
return;
}
let windowClass = data;
let orientation = window.Orientation.AUTO_ROTATION_UNSPECIFIED; // 设置窗口方向旋转模式
try {
windowClass.setPreferredOrientation(orientation, (err) => {
if (err.code) {
return;
}
});
} catch (exception) {
// ...
}
});
} catch (exception) {
// ...
}
}

还有一个官方项目:https://gitcode.com/HarmonyOS_Samples/avplayer-long-video

更多关于HarmonyOS鸿蒙Next中求教:全屏模式下的自适应问题的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
你说的对,试了有效,
开发者您好,针对您点击全屏按钮后出现两个问题,请参考如下:
- 问题1:调用 requestFullScreen()方法成功进入全屏,但自定义控制栏消失。原因:Video组件自带的全屏功能仅将视频内容设为全屏,显示默认控制器,无法显示自定义标题或控制器。如需其他功能,用户需自行实现全屏功能。具体可参考官网文档:requestFullscreen说明。
- 问题2: 手动切换设备横竖屏时,播放器状态(如进度、播放状态)被重置。我这边使用Mate X5 API 21未能复现您的问题,您方便的话麻烦提供下您的复现demo还有复现设备。下面是我的复现代码,注意视频资源替换为您的资源文件:
import { window } from '@kit.ArkUI';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { common } from '@kit.AbilityKit';
const CONVERSION = 60;
@Entry
@Component
struct Index {
@State orientation: number = 0;
@State isPlaying: boolean = false;
context: common.UIAbilityContext = this.getUIContext()?.getHostContext() as common.UIAbilityContext;
windowClass: window.Window = (this.context as common.UIAbilityContext).windowStage.getMainWindowSync();
@State curTime: number = 0;
@State videoDuration: number = 0;
private videoController: VideoController = new VideoController();
@State isFullScreen: boolean = false;
onPageHide(): void {
this.isPlaying = false;
}
// 视频时间格式化
timeFormat(time: number) {
let hoursStr = Math.floor(time / (CONVERSION * CONVERSION)).toString();
let minutesStr = Math.floor(time / CONVERSION).toString();
let secondsStr = Math.floor(time % CONVERSION).toString();
if (minutesStr.length === 1) {
minutesStr = '0' + minutesStr;
}
if (secondsStr.length === 1) {
secondsStr = '0' + secondsStr;
}
return `${hoursStr}:${minutesStr}:${secondsStr}`;
}
build() {
Column() {
Stack({ alignContent: Alignment.Bottom }) {
Video({
src: $rawfile('test.mp4'), // 请替换为您的视频文件
controller: this.videoController
})
.controls(false)
.autoPlay(true)
.width('100%')
.height('100%')
.onPrepared((event) => {
this.videoDuration = event.duration;
})
.onStart(() => {
if (!this.isPlaying) {
this.videoController.pause();
}
})
.onUpdate((event) => {
this.curTime = event.time;
})
.onFinish(() => {
this.isPlaying = false;
});
// 控制栏
Row() {
Image(this.isPlaying ? $r('app.media.ic_pause') : $r('app.media.ic_play'))
.width(24)
.height(24)
.onClick(() => {
if (this.isPlaying) {
this.videoController.pause();
this.isPlaying = false;
} else {
this.videoController.start();
this.isPlaying = true;
}
});
Text(this.timeFormat(this.curTime))
.fontSize(10)
.fontColor($r('sys.color.white'))
.margin({
left: 12,
right: 8
});
Slider({
value: this.curTime,
step: 1,
min: 0,
max: this.videoDuration,
style: SliderStyle.OutSet
})
.layoutWeight(1)
.trackThickness(5)
.blockColor(Color.White)
.blockSize({
width: 12,
height: 12
})
.trackColor($r('app.color.slider_track'))
.selectedColor($r('app.color.slider_selected'))
.onChange((value, mode) => {
this.curTime = value;
if (mode === SliderChangeMode.End) {
this.videoController.setCurrentTime(value);
}
});
Text(this.timeFormat(this.videoDuration))
.fontSize(10)
.fontColor($r('sys.color.white'))
.margin({
right: 12,
left: 8
});
Image($r('app.media.ic_full'))
.width(24)
.height(24)
.onClick(() => {
if (!this.windowClass) {
hilog.error(0x0000, 'testTag', 'this.windowClass is und');
return;
}
if (!this.isFullScreen) {
this.windowClass.setPreferredOrientation(window.Orientation.LANDSCAPE);
} else {
this.windowClass.setPreferredOrientation(window.Orientation.PORTRAIT);
}
this.isFullScreen = !this.isFullScreen;
});
}
.padding({
left: 12,
right: 12,
top: 8,
bottom: 8
})
.backgroundColor($r('app.color.video_controller_background'));
}
.width('100%')
.height('100%');
}
.width('100%')
.height('100%')
.backgroundColor($r('sys.color.white'))
.padding({
top: 16,
left: 12,
right: 12,
bottom: 16
});
}
}
根据您描述中期望解决的问题,
- 如何在全屏模式下保留自定义控制栏:不使用requestFullScreen()方法成功进入全屏,原因请参考上面问题一解答。通过setPreferredOrientation方法设置窗口方向旋转模式为跟随传感器自动旋转,监听横竖屏变化,获取设备当前定向,具体可参考官网文档:视频窗口横竖屏切换。或者参考上面代码中切换横竖屏方法,实现全屏。
- 横竖屏切换时如何保持播放状态:请您方便的话麻烦提供下您的复现demo,或者参考上面代码修改您的播放逻辑。
- 如何实现横屏/竖屏自适应全屏(根据视频宽高比动态切换):通用属性onAreaChange、onSizeChange均可以监听组件的尺寸变化,并执行回调中的动作。Video的画面填充模式可以通过设置不同的objectFit属性值改变。通过onAreaChange获取外部容器的高度。Video组件设置objectFit属性为ImageFit.Contain,使画面内容保持宽高比,在边界内完全显示;同时设置组件的高度为随外部组件高度动态变化。
鸿蒙Next全屏模式下自适应可通过以下方式实现:
- 使用
preferredOrientation配置应用支持的屏幕方向 - 通过
displayCutout处理刘海屏适配 - 利用
windowSizeClass响应式布局方案 - 采用
GridRow/GridCol栅格系统适配不同屏幕尺寸 - 使用
MediaQuery获取设备屏幕信息进行动态布局
关键API包括Window模块的getTopWindow()获取窗口属性,配合display模块的屏幕信息查询。建议使用自适应布局能力替代固定尺寸值。
针对你提到的两个问题,解决方案如下:
1. 全屏模式下保留自定义控制栏
自定义控制栏消失,是因为requestFullScreen()默认作用于调用它的组件及其子节点。当Video组件单独进入全屏时,外层的Stack布局和控制栏会被排除在外。
解决方法:将需要全屏显示的整个容器(包含Video和控制栏的Stack布局)作为requestFullScreen()的调用者。确保全屏请求作用于整个播放器容器,而非单独的Video组件。
2. 横竖屏切换时保持播放状态 状态重置是因为屏幕方向变化时,系统默认会重建UI(如销毁并重新创建Ability)。这会导致播放器组件被重新初始化。 解决方法:
- 状态持久化:在方向变化前(例如监听
onConfigurationUpdate回调),将当前播放进度、播放状态等关键数据保存到AppStorage或本地临时变量中。 - 状态恢复:在UI重新构建后(例如在
aboutToAppear生命周期中),从存储中读取并恢复这些状态,重新设置给Video组件。 - 避免重建:在module.json5中对应Ability的
configChanges字段里添加"orientation"等配置,声明由应用自身处理屏幕方向变化,从而避免系统重建UI。这通常是首选的解决方案。
3. 实现横屏/竖屏自适应全屏 这通常不是通过简单的“全屏”API实现,而是需要根据视频宽高比和当前屏幕方向,动态调整播放器容器的尺寸和布局。 核心思路:
- 计算与判断:获取视频的原始宽高比(aspect ratio)。同时,通过媒体查询(
@ohos.mediaquery)或窗口管理器(@ohos.window)获取当前窗口的实际尺寸和方向。 - 动态布局:比较视频宽高比与当前屏幕区域的宽高比。
- 若视频更“宽”(如16:9的电影在9:18.5的竖屏手机上),则采用“竖屏适配模式”:播放器高度撑满屏幕,宽度按比例计算,左右留黑边(或显示其他内容)。
- 若视频更“高”或与屏幕比例匹配,则采用“横屏适配模式”:播放器宽度撑满屏幕,高度按比例计算,上下留黑边。
- 这种“自适应”通常是在一个固定的全窗口容器内,通过动态计算并设置Video组件的
width、height和objectFit属性来实现,而不是调用系统级的requestFullScreen()。真正的“全屏”体验可以通过隐藏系统状态栏和导航栏来增强。
总结建议: 对于视频播放器场景,实现“沉浸式自适应播放”的常见做法是:
- 使用窗口管理器设置全窗口布局(隐藏状态栏/导航栏)。
- 通过
configChanges接管屏幕方向变化,避免UI重建。 - 自行监听方向传感器或窗口尺寸变化,根据视频比例动态计算并应用播放器视图的尺寸和位置。
- 将播放状态、进度等数据与UI组件生命周期分离,进行持久化管理,确保在任何界面变化下都能正确恢复。
这样既能获得类似全屏的沉浸体验,又能完全掌控控制栏的显示和播放状态的连续性。

