应用的窄屏底部是一条浮动导航栏:336 × 64vp 的圆角胶囊,浮在手势指示条上方,不占布局高度。它原来带一层玻璃感,在后来一次「去掉底部渐变遮罩」的改造里,遮罩确实去掉了,材质也跟着不见了。这次要把材质找回来,同时不能把遮罩带回来。

难点在于材质挂在哪个组件上。把接口挂到现成的手绘栏上,接口全部返回成功,画面却没有任何变化。下面记录从「接口全对但不出图」到落地的完整过程。

底栏的材质在哪一层生效

沉浸式系统材质由 ArkUI 的 uiMaterial 命名空间提供。接入前先确认三件事:系统版本、运行时是否支持、全局材质开关是否打开。

private immersiveMaterialAvailable(): boolean {
  if (deviceInfo.sdkApiVersion < 26) {
    return false;
  }
  if (!uiMaterial.isImmersiveMaterialSupported()) {
    return false;
  }
  return uiMaterial.getMaterialInfo().state === uiMaterial.MaterialState.ENABLE;
}

三项都为真时,材质请求才会下发;任一为假就走手绘栏分支,页面的导航功能不受影响。

内容层挂材质不出图

按官方示例,给一个容器挂上材质:

Column() { /* 导航项 */ }
  .systemMaterial(new uiMaterial.ImmersiveMaterial({ style: uiMaterial.ImmersiveStyle.THIN }))

接口没有报错,画面也没有变化。为了确认材质层到底出不出图,把材质颜色临时设成高不透明红,看这一层是否参与合成。

在底栏所在的内容层上做同机对照,两次采样的红色占比都是零,红色只出现在页头已有的金色装饰里。把同样的探针放到导航栏与底部栏作用域之外的其他容器上,结果一致。

设备日志给出了直接答案:

W C03900/com.shanhai.spirits/Ace: Material inactive: out of scope.
    Use component in navigation title bar or Tabbar.

沉浸式系统材质只在导航标题栏与底部栏作用域内生效。页面内容层里叠的 Row 与 Column 覆盖层,挂上去也一律不出图,这正是「浮动栏是 Stack 里的覆盖层」这一结构本身的限制。

判据实测值结论
系统版本26通过
运行时支持查询真通过
全局材质开关已开启通过
内容层材质探针红色占比0.00%作用域不符
日志提示出现两次作用域不符

把栏交还给 Tabs 自己的浮动栏

既然只有底部栏作用域认材质,就回到 Tabs 自己的栏上要,不必退回早前那套封装。原生 Tabs 自 API 26 起新增 barFloatingStyle,其样式对象里有 systemMaterial 字段,直接接收沉浸式材质;maskColor 与 maskHeight 是可选项。

private floatingBarStyle(): FloatingTabBarStyle | undefined {
  const material = this.floatingBarMaterial();
  if (material === undefined) {
    return undefined;
  }
  return {
    barWidth: {
      smallBarWidth: SHANHAI_NATIVE_BAR_WIDTH,
      mediumBarWidth: SHANHAI_NATIVE_BAR_WIDTH,
      largeBarWidth: SHANHAI_NATIVE_BAR_WIDTH
    },
    barBottomMargin: SHANHAI_NATIVE_BAR_BOTTOM_MARGIN,
    systemMaterial: material
  };
}

材质对象本身承载底色,浮栏在接管时让出手绘底色与描边:

private floatingBarMaterial(): uiMaterial.ImmersiveMaterial | undefined {
  if (!this.immersiveMaterialAvailable()) {
    return undefined;
  }
  return new uiMaterial.ImmersiveMaterial({
    style: uiMaterial.ImmersiveStyle.THIN,
    materialColor: this.isDarkMode ? '#33202E2A' : '#33FFF9EE',
    colorInvert: false,
    applyShadow: true,
    interactive: true,
    lightEffect: { color: this.accentColor }
  });
}

同一个红色探针再跑一次,底栏区域的红色占比为 38.51%,材质这一层确实参与了合成,选中项图标与文字也照常渲染。

不引入遮罩

早前退出封装组件的原因,是它带一层运行时关不掉的底部渐变遮罩,会压暗内容。新的样式对象里,maskColor 与 maskHeight 都是可选项。

两条渲染路径按互斥条件切换:材质可用时把栏交还给 Tabs,此时不配置遮罩相关字段;材质不可用时把原生栏高度归零,由手绘覆盖层接管。

Tabs({ barPosition: BarPosition.End, controller: this.tabController }) {
  // 各 tab 内容
}
.barOverlap(true)
.barHeight(this.useNativeFloatingBar() ? SHANHAI_NATIVE_BAR_HEIGHT : 0)
.barFloatingStyle(this.useNativeFloatingBar() ? this.floatingBarStyle() : undefined)
.scrollable(false)
.animationDuration(180)

原生栏与手绘栏的互斥条件写成单一判定,避免两处逻辑各写一份而走偏:

private useNativeFloatingBar(): boolean {
  return !this.hideBottomNav && this.immersiveMaterialAvailable();
}

系统浮动栏会放大请求尺寸

材质生效后,栏位几何发生了变化。用布局树量取真实边界,第一版按设计值请求 336 × 64vp、底距 32vp,落屏得到的是 378 × 72vp、底距 36vp,宽出 42vp、高出 8vp。

内容净空是按「栏顶距底 96vp」定的,栏一长高,净空就从 16vp 掉到 4vp,最后一屏内容几乎贴着栏。第一次按固定差值补偿,高度和底距准了,宽度仍偏 5vp。把两组数据摆在一起,规律才出来:

请求值实测值比值
3363781.125
2943311.126
64721.125
32361.125

系统按 1.125 倍等比放大请求值,也就是九比八。第一次之所以看起来「高度对了」,是因为设计值恰好是 64 和 32,差值与比例值在这两个数上刚好重合;换一个设计值就会错。改成按比例除:

const SHANHAI_NATIVE_BAR_SCALE: number = 1.125;
export const SHANHAI_NATIVE_BAR_WIDTH = SHANHAI_FLOATING_NAV_BAR_WIDTH / SHANHAI_NATIVE_BAR_SCALE;
export const SHANHAI_NATIVE_BAR_HEIGHT = SHANHAI_FLOATING_NAV_HEIGHT / SHANHAI_NATIVE_BAR_SCALE;
export const SHANHAI_NATIVE_BAR_BOTTOM_MARGIN = SHANHAI_FLOATING_NAV_BOTTOM_MARGIN / SHANHAI_NATIVE_BAR_SCALE;

补偿后重新量取,栏位回到 336 × 64vp、底距 32vp,与手绘栏的设计几何一致,内容净空恢复到 16vp。

能力不可用时的回退分支

模拟器的运行时支持查询返回假,走的是手绘栏分支。两条路径的视觉与交互分别核对:

分支触发条件底栏表现
系统浮动栏版本、支持查询、全局开关三项为真胶囊外形一致,材质参与合成
手绘栏任一项为假米金底色加金色描边,选中项高亮

回退分支的胶囊尺寸、图标、选中态与动画与改造前逐项一致,底栏功能不因能力缺失而降级。

if (!this.hideBottomNav && !this.useNativeFloatingBar()) {
  Row({ space: 0 }) {
    // 五个导航项
  }
  .width(SHANHAI_FLOATING_NAV_BAR_WIDTH)
  .height(SHANHAI_FLOATING_NAV_HEIGHT)
  .justifyContent(FlexAlign.SpaceBetween)
  .backgroundColor(this.isDarkMode ? '#E60C2529' : '#E6FFF9EE')
  .border({ width: 1, color: this.isDarkMode ? '#805D8886' : '#99D9CDAE' })
  .borderRadius(32)
  .margin({ bottom: SHANHAI_FLOATING_NAV_BOTTOM_MARGIN })
}

手绘栏保留原来的米金底色与金色描边,只在材质不可用时渲染,两条路径不会同时出现在屏幕上。

设备上量到的栏位几何

改造后的三态在设备上分别采图:材质未生效的手绘栏、材质生效的系统浮动栏、以及模拟器上的回退分支。

浮动底栏三种状态:改造前手绘栏、改造后系统浮动栏、模拟器回退分支

底栏的收放与探索页地图覆盖层的视口绑定,改渲染路径后需要确认覆盖层不受影响。覆盖层挂载时底栏收起、原生栏高度归零,视口不重排。九个区域锚点逐个点选,信息页选中的区域与所点锚点全部一致,共 9 组命中。

两条渲染路径的验收点

检查项预期结果
材质可用底栏区域材质探针参与合成,选中项正常渲染
材质不可用手绘栏接管,尺寸与选中态与改造前一致
未配置遮罩字段内容不被底部渐变压暗
覆盖层挂载底栏收起,视口不重排
区域锚点点选与信息页选中区域一致

参考:开启沉浸光感、组件适配沉浸光感。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐