HarmonyOS鸿蒙Next中如何在全屏沉浸式且在横屏的状态下获取状态栏,刘海屏,导航栏他们的间距
HarmonyOS鸿蒙Next中如何在全屏沉浸式且在横屏的状态下获取状态栏,刘海屏,导航栏他们的间距 我的app是横屏沉浸式的,但是里面的按钮图标等UI设计我不想被摄像头或者切图的挖空区遮挡,所以我需要在这种状态下获取合适的间距进行适当调节,或者说你有什么更好的办法能解决这个问题。因为在横屏状态下,背景图是全屏的但是里面的UI按钮并不想要被摄像头遮挡所以有没有更好的办法。我还是用flutter做的如果需要原生那边进行修改还得通信什么的
/*
- Copyright © 2023 Hunan OpenValley Digital Industry Development Co., Ltd.
- Licensed under the Apache License, Version 2.0 (the “License”);
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an “AS IS” BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License. */
import { FlutterAbility, FlutterEngine } from ‘@ohos/flutter_ohos’; import { GeneratedPluginRegistrant } from ‘…/plugins/GeneratedPluginRegistrant’; import { CustomPlugin } from ‘./CustomPlugin’; import { NBWindow } from ‘./NBWindow’; import { window } from ‘@kit.ArkUI’; import HuaweiPaymentPlugin from ‘…/plugins/HuaweiPaymentPlugin’; import WindowManagerPlugin from ‘…/plugins/WindowManagerPlugin’;
export default class EntryAbility extends FlutterAbility { configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) GeneratedPluginRegistrant.registerWith(flutterEngine) this.addPlugin(new CustomPlugin()) this.addPlugin(new HuaweiPaymentPlugin()) // 支付插件 this.addPlugin(new WindowManagerPlugin()); //安全区域 }
onWindowStageCreate(windowStage: window.WindowStage): void {
// super.onWindowStageCreate(windowStage)
// NBWindow.setWindow(windowStage)
windowStage.loadContent('pages/AreaZone');
} }
import { window } from ‘@kit.ArkUI’; import { BusinessError } from ‘@kit.BasicServicesKit’;
export class NBWindow { private static windowStage: window.WindowStage; public static PORTRAIT: window.Orientation = window.Orientation.PORTRAIT; public static LANDSCAPE: window.Orientation = window.Orientation.LANDSCAPE;
// 其他方法 public static setWindow(_windowStage: window.WindowStage) { NBWindow.windowStage = _windowStage }
public static getWindow(): window.WindowStage { return NBWindow.windowStage }
// 新增:获取顶部状态栏高度(安全区顶部)
public static async getStatusBarHeight(): Promise<number> {
if (!NBWindow.windowStage) {
throw new Error(“windowStage is not initialized”);
}
try {
// 获取主窗口
const mainWindow = await NBWindow.windowStage.getMainWindowSync();
// 获取系统安全区域(包含状态栏)
const systemArea = mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM);
// 转换为VP单位并返回顶部高度
return px2vp(systemArea.topRect.height);
} catch (err) {
console.error(获取状态栏高度失败: ${(err as BusinessError).message}
);
return 0;
}
}
// 新增:获取底部导航栏高度(安全区底部)
public static async getNavigationBarHeight(): Promise<number> {
if (!NBWindow.windowStage) {
throw new Error(“windowStage is not initialized”);
}
try {
const mainWindow = await NBWindow.windowStage.getMainWindowSync();
// 获取导航栏安全区域
const navigationArea = mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR);
// 转换为VP单位并返回底部高度
return px2vp(navigationArea.bottomRect.height);
} catch (err) {
console.error(获取导航栏高度失败: ${(err as BusinessError).message}
);
return 0;
}
}
public static setOrientation(ori: window.Orientation): void {
let windowClass: window.Window | undefined = undefined;
NBWindow.windowStage.getMainWindow((err: BusinessError, data) => {
const errCode: number = err.code;
if (errCode) {
console.error(Failed to obtain the main window. Cause code: ${err.code}, message: ${err.message}
);
return;
}
windowClass = data;
let orientation = ori;
try {
let promise = windowClass.setPreferredOrientation(orientation);
promise.then(() => {
console.info(‘Succeeded in setting the window orientation.’);
}).catch((err: BusinessError) => {
console.error(Failed to set the window orientation. Cause code: ${err.code}, message: ${err.message}
);
});
} catch (exception) {
console.error(Failed to set window orientation. Cause code: ${exception.code}, message: ${exception.message}
);
}
});
}
}
@Entry @Component struct AreaZone { // 声明安全区高度变量(不依赖AppStorage) @State topHeight: number = 0; @State bottomHeight: number = 0;
// 组件初始化时获取安全区高度
async aboutToAppear() {
try {
// 从NBWindow获取顶部状态栏高度
this.topHeight = await NBWindow.getStatusBarHeight();
// 从NBWindow获取底部导航栏高度
this.bottomHeight = await NBWindow.getNavigationBarHeight();
} catch (err) {
console.error(获取安全区高度失败: ${err}
);
}
}
// 定义顶部安全区域样式 @Styles topSafeAreaStyle() { .padding({ top: this.topHeight }) }
// 定义底部安全区域样式 @Styles bottomSafeAreaStyle() { .padding({ bottom: this.bottomHeight }) }
build() { Column() { // 顶部内容(适配状态栏高度) Text(‘顶部内容’) .fontSize(18) .width(‘100%’) .height(this.topHeight) .backgroundColor(’#ff0000’) .topSafeAreaStyle()
// 中间内容区域
Column() {
Text(`刘海屏/状态栏高度: ${this.topHeight}`)
.fontSize(16)
.padding(5)
.backgroundColor('#e0e0e0')
.borderRadius(4)
.margin(5)
Text(`底部导航栏高度: ${this.bottomHeight}`)
.fontSize(16)
.padding(5)
.backgroundColor('#e0e0e0')
.borderRadius(4)
.margin(5)
}
.flexGrow(1)
.width('100%')
.justifyContent(FlexAlign.Center)
.padding(10)
// 底部内容(适配导航栏高度)
Text('底部内容')
.fontSize(18)
.width('100%')
.height(this.bottomHeight)
.backgroundColor('#00ff00')
.bottomSafeAreaStyle()
}
.width('100%')
.height('100%')
} }
import { window } from ‘@kit.ArkUI’; import { BusinessError } from ‘@kit.BasicServicesKit’;
@Entry @Component struct AreaZone { // 声明安全区高度变量 @State topHeight: number = 0; @State bottomHeight: number = 0;
// 组件初始化时获取安全区高度 async aboutToAppear() { try { // 直接通过window模块获取当前窗口实例 const windowClass = await window.getLastWindow(getContext(this));
// 获取状态栏/刘海屏高度(安全区顶部)
const avoidArea = windowClass.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM);
// 获取导航栏安全区域
const navigationArea = windowClass.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR);
// 转换为VP单位(px转vp)
this.topHeight = px2vp(avoidArea.topRect.height);
this.bottomHeight = px2vp(navigationArea.bottomRect.height);
} catch (err) {
console.error(`获取安全区高度失败: ${(err as BusinessError).message}`);
}
}
// 定义顶部安全区域样式 @Styles topSafeAreaStyle() { .padding({ top: this.topHeight }) }
// 定义底部安全区域样式 @Styles bottomSafeAreaStyle() { .padding({ bottom: this.bottomHeight }) }
build() { Column() { // 顶部内容(适配状态栏高度) Text(‘顶部内容’) .fontSize(18) .width(‘100%’) .height(this.topHeight) .backgroundColor(’#ff0000’) .topSafeAreaStyle()
// 中间内容区域
Column() {
Text(`刘海屏/状态栏高度: ${this.topHeight}`)
.fontSize(16)
.padding(5)
.backgroundColor('#e0e0e0')
.borderRadius(4)
.margin(5)
Text(`底部导航栏高度: ${this.bottomHeight}`)
.fontSize(16)
.padding(5)
.backgroundColor('#e0e0e0')
.borderRadius(4)
.margin(5)
}
.flexGrow(1)
.width('100%')
.justifyContent(FlexAlign.Center)
.padding(10)
// 底部内容(适配导航栏高度)
Text('底部内容')
.fontSize(18)
.width('100%')
.height(this.bottomHeight)
.backgroundColor('#00ff00')
.bottomSafeAreaStyle()
}
.width('100%')
.height('100%')
} }
import { BusinessError } from ‘@kit.BasicServicesKit’; import { common } from ‘@kit.AbilityKit’; import { FlutterPlugin, FlutterPluginBinding, MethodCall, MethodChannel, MethodResult, } from ‘@ohos/flutter_ohos’; import { window } from ‘@kit.ArkUI’;
const TAG = “WindowManagerPlugin”;
export default class WindowManagerPlugin implements FlutterPlugin { private channel?: MethodChannel; // 与Flutter通信的通道 private context: common.UIAbilityContext | null = null;
onAttachedToEngine(binding: FlutterPluginBinding): void { this.channel = new MethodChannel(binding.getBinaryMessenger(), “samples.flutter.dev/window_manager”); // 从绑定中获取应用上下文(关键修正) // this.context = binding.getApplicationContext() as common.UIAbilityContext; this.context = getContext() as common.UIAbilityContext; this.channel.setMethodCallHandler({ onMethodCall: (call: MethodCall, result: MethodResult) => { if (!this.context) { result.error(TAG, ‘上下文未初始化’, null); return; } switch (call.method) { case “getSafeAreaInsets”: this.getSafeAreaInsets(result); break; default: result.notImplemented(); break; } } });
}
// 使用windowClass获取安全区域信息 private async getSafeAreaInsets(result: MethodResult) { try { // 直接通过window模块获取当前窗口实例 const windowClass = await window.getLastWindow(getContext(this)); // const windowClass: window.Window = windowStage.getMainWindowSync(); // 获取应用主窗口
// 获取状态栏/刘海屏高度(安全区顶部)
const avoidArea = windowClass.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM);
// 获取导航栏安全区域
const navigationArea = windowClass.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR);
// 转换为VP单位(px转vp)
const topHeight = px2vp(avoidArea.topRect.height);
const bottomHeight = px2vp(navigationArea.bottomRect.height);
// 返回安全区域信息
result.success({
top: topHeight,
bottom: bottomHeight,
left: 20,
right: 20
});
} catch (error) {
console.warn(TAG, `无法获得安全区域插图: ${(error as BusinessError).message}`);
result.error(
"获取插图失败",
`无法获得安全区域插图: ${(error as BusinessError).message}`,
error
);
}
}
onDetachedFromEngine(binding: FlutterPluginBinding): void { console.warn(TAG, “onDetachedFromEngine”); this.channel?.setMethodCallHandler(null);
}
getUniqueClassName(): string { return “WindowManagerPlugin”; } }
在HarmonyOS鸿蒙Next中,获取全屏沉浸式横屏状态下的安全区域间距,需使用window
模块。通过getWindowAvoidArea
接口可获取避开区域数据:
import window from '@ohos.window';
let windowClass;
window.getLastWindow(this.context).then((win) => {
windowClass = win;
let avoidArea = windowClass.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM);
// avoidArea包含top/right/bottom/left四个边的避开像素值
});
该接口返回的AvoidArea
对象包含状态栏、刘海屏和导航栏占据的像素值。横屏时需注意left
/right
属性可能对应物理顶部/底部区域。
在HarmonyOS Next中处理横屏沉浸式界面的安全区域问题,可以通过以下方式实现:
- 获取系统安全区域:
使用
WindowInsets
类获取系统栏位信息:
WindowInsets windowInsets = getWindow().getDecorView().getRootWindowInsets();
Insets statusBarInsets = windowInsets.getInsets(Type.statusBars());
Insets navigationBarInsets = windowInsets.getInsets(Type.navigationBars());
Insets displayCutoutInsets = windowInsets.getInsets(Type.displayCutout());
- Flutter端处理:
在Flutter中可以通过
MediaQuery
获取安全区域:
final padding = MediaQuery.of(context).padding;
- 横屏适配建议:
- 将关键UI元素布局在安全区域内
- 使用
SafeArea
组件自动避开系统栏位 - 对于挖孔屏区域,可以通过
DisplayCutout
获取具体位置信息
- 原生与Flutter通信: 如果需要原生能力,可以通过MethodChannel实现通信:
// Flutter端调用
final result = await platform.invokeMethod('getWindowInsets');
注意:沉浸式模式下获取的insets值可能会变化,建议在onApplyWindowInsets回调中实时更新布局。