在开发沉浸式的元服务过程中,开发者若选择自行设计开发导航栏(包括头部导航栏和底部页签栏),而非使用官方提供的标准控件(AtomicServiceNavigation以及AtomicServiceTabs)时,容易出现界面布局不符合UX体验标准的情况。常见问题包括:
为实现使用自定义导航栏和Tab栏并符合UX标准的元服务沉浸式体验,对于这种需要动态调整的场景推荐使用getWindowAvoidArea以及使用getBarRect方法对两个安全区进行针对性的布局适配:
顶部适配需同时兼顾系统状态栏的透明沉浸效果,以及元服务胶囊的视觉平衡与交互避让。
完整示例参考如下:
import { AtomicServiceBar } from '@kit.ArkUI';
import window from '@ohos.window';
import common from '@ohos.app.ability.common';
import { hilog } from '@kit.PerformanceAnalysisKit';
// ----------------配置常量----------------
const BLUE = '#0A59F7';
const BG_BOTTOM_COLOR = '#E6F0FF'; // 背景底部的浅蓝色
@Entry
@Component
struct ImmersiveMetaService {
// ----------------状态变量----------------
@State SYSTEM_BAR_HEIGHT: number = 0; // 顶部导航栏高度
@State bottomSafeHeight: number = 0; // 底部导航条避让高度
@State currentIndex: number = 0; // 当前选中的 Tab 索引
@State barOffsetY: number = 0; // 对应 rect.y
private topSafeHeight: number = 0; // 顶部状态栏避让高度
private barHeight: number = 0; // 对应 rect.height
private tabsController: TabsController = new TabsController();
private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
// ----------------生命周期----------------
aboutToAppear() {
window.getLastWindow(this.context).then((windowClass) => {
// 1. 设置全屏布局
windowClass.setWindowLayoutFullScreen(true);
// 2. 获取避让区域
let typeSystem = window.AvoidAreaType.TYPE_SYSTEM; // 状态栏区域
let typeNav = window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR; // 底部导航区域
this.topSafeHeight = this.getUIContext().px2vp(windowClass.getWindowAvoidArea(typeSystem).topRect.height);
this.bottomSafeHeight = this.getUIContext().px2vp(windowClass.getWindowAvoidArea(typeNav).bottomRect.height);
hilog.info(0x0000, 'testTag', 'avoidAreaSize: topSafeHeight = ' + this.topSafeHeight + ', avoidAreaSize: bottomSafeHeight = ' + this.bottomSafeHeight);
// 3. 监听屏幕变化
windowClass.on('avoidAreaChange', (data) => {
if (data.type === window.AvoidAreaType.TYPE_SYSTEM) {
this.topSafeHeight = this.getUIContext().px2vp(data.area.topRect.height);
} else if (data.type === window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR) {
this.bottomSafeHeight = this.getUIContext().px2vp(data.area.bottomRect.height);
}
});
});
}
onPageShow() {
setTimeout(() => {
try {
let uiContext = this.getUIContext();
let currentBar: Nullable<AtomicServiceBar> = uiContext.getAtomicServiceBar();
if (currentBar !== undefined) {
let rect = currentBar.getBarRect(); // 获取元服务menuBar相对窗口的布局信息
hilog.info(0x0000, 'testTag', 'Get AtomicServiceBar Successfully. x:' + rect.x + ' y:' + rect.y + ' width:' + rect.width + ' height:' + rect.height);
// 保存胶囊位置信息
this.barOffsetY = rect.y; // 胶囊距离顶部的高度
this.barHeight = rect.height; // 胶囊自身高度
hilog.info(0x0000, 'testTag', 'barOffsetY: ' + this.barOffsetY + 'barHeight: ' + this.barHeight);
this.SYSTEM_BAR_HEIGHT = rect.y + rect.height; // 计算顶部导航栏高度
}
} catch (error) {
console.error(`[Immersive] Failed to get AtomicServiceBar`);
}
}, 50);
}
// ----------------UI 构建器----------------
@Builder TabItem(index: number, title: string, icon: ResourceStr) {
Column() {
Image(icon)
.width(24)
.height(24)
.objectFit(ImageFit.Contain)
.fillColor(this.currentIndex === index ? BLUE : Color.Gray)
.margin({ bottom: 0 })
Text(title)
.fontSize(10)
.fontColor(this.currentIndex === index ? BLUE : Color.Gray)
.fontWeight(this.currentIndex === index ? FontWeight.Medium : FontWeight.Normal)
.margin({ top: 2 })
}
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
.onClick(() => {
this.currentIndex = index;
this.tabsController.changeIndex(index);
})
}
// ----------------主布局----------------
build() {
RelativeContainer() {
// Layer 1: 背景层 (ID: bg_layer)
Column()
.width('100%')
.height('100%')
.linearGradient({
angle: 180,
colors: [[0xFFFFFF, 0.0], [BG_BOTTOM_COLOR, 1.0]]
})
.alignRules({
top: { anchor: '__container__', align: VerticalAlign.Top },
bottom: { anchor: '__container__', align: VerticalAlign.Bottom },
left: { anchor: '__container__', align: HorizontalAlign.Start },
right: { anchor: '__container__', align: HorizontalAlign.End }
})
.id('bg_layer')
// Layer 2: 主体内容区 Tabs (ID: content_layer)
Tabs({ barPosition: BarPosition.End, controller: this.tabsController }) {
// --- Tab 1 ---
TabContent() {
List() {
ListItem().height(this.SYSTEM_BAR_HEIGHT)
ForEach([1, 2, 3, 4, 5, 6], (item: number) => {
ListItem() {
Column() {
Row().width('100%').height(150).backgroundColor('#DCEAFE').borderRadius(12)
Text(`元服务卡片 - ${item}`).fontSize(16).fontWeight(FontWeight.Bold).margin({ top: 12 })
Text('即开即用、纯净轻量、高效触达').fontSize(14).fontColor(Color.Gray).margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start).padding(16).backgroundColor(Color.White)
.borderRadius(16).shadow({ radius: 20, color: 'rgba(0,0,0,0.05)', offsetY: 8 })
}.margin({ left: 16, right: 16, bottom: 16 })
})
// 底部占位 (重要:高度 = 底部安全区 + 导航栏高度,防止最后一条显示不全)
ListItem().height(this.bottomSafeHeight + 50)
}
.width('100%').height('100%').scrollBar(BarState.Off).edgeEffect(EdgeEffect.Spring)
}
// --- Tab 2 ---
TabContent() {
Column() {
Button('立即创作').type(ButtonType.Capsule).backgroundColor(BLUE)
.width(200).height(56).shadow({ radius: 20, color: 'rgba(10, 89, 247, 0.3)', offsetY: 8 })
}.width('100%').height('100%').justifyContent(FlexAlign.Center)
}
// --- Tab 3 ---
TabContent() {
Column() {
Blank().height(this.SYSTEM_BAR_HEIGHT)
Row() {
Circle({ width: 64, height: 64 }).fill('#E6F0FF')
Column({ space: 6 }) {
Text('HarmonyOS').fontSize(20).fontWeight(FontWeight.Bold)
}.alignItems(HorizontalAlign.Start).margin({ left: 16 })
}.width('90%').padding(24).backgroundColor('rgba(255,255,255,0.9)')
.borderRadius(20)
}.width('100%').height('100%')
}
}
.vertical(false)
.barHeight(0)
.scrollable(false)
.onChange((index: number) => { this.currentIndex = index; })
.alignRules({
top: { anchor: '__container__', align: VerticalAlign.Top },
bottom: { anchor: '__container__', align: VerticalAlign.Bottom },
left: { anchor: '__container__', align: HorizontalAlign.Start },
right: { anchor: '__container__', align: HorizontalAlign.End }
})
.id('content_layer')
// Layer 3: 顶部导航栏 (ID: top_nav)
Row() {
Text('沉浸式元服务')
.fontSize(20)
.fontWeight(FontWeight.Medium)
.fontColor(Color.Black)
.textAlign(TextAlign.Start)
.width('100%')
.padding({
left: 16
})
}
.alignItems(VerticalAlign.Center)
.width('100%')
.height(this.SYSTEM_BAR_HEIGHT) // 顶部导航栏高度
.padding({ top: this.barOffsetY }) // 保证Text与胶囊中心水平对齐
.backgroundColor('rgba(255, 255, 255, 0.7)')
.backdropBlur(20)
.alignRules({
top: { anchor: '__container__', align: VerticalAlign.Top },
left: { anchor: '__container__', align: HorizontalAlign.Start },
right: { anchor: '__container__', align: HorizontalAlign.End }
})
.id('top_nav')
// Layer 4: 底部自定义 Tab 栏 (ID: bottom_nav)
Row() {
this.TabItem(0, '探索', $r('app.media.more'));
this.TabItem(1, '创作', $r('app.media.paperplane'));
this.TabItem(2, '我的', $r('app.media.person'));
}
.width('100%')
.padding({
top: 6,
bottom: this.bottomSafeHeight > 0 ? this.bottomSafeHeight : 0
})
.backgroundColor('rgba(230, 240, 255, 0.95)')
.backdropBlur(40)
.alignRules({
bottom: { anchor: '__container__', align: VerticalAlign.Bottom },
left: { anchor: '__container__', align: HorizontalAlign.Start },
right: { anchor: '__container__', align: HorizontalAlign.End }
})
.id('bottom_nav')
}
.width('100%')
.height('100%')
}
}