HarmonyOS 6.1.0 创新特性技术精讲之沉浸光感

【开发者实践】HarmonyOS 6.1.0 创新特性"悬浮页签+沉浸光感"

开发相册、音乐播放器等沉浸式应用时,底部Tab栏要么是一块生硬的深色矩形截断内容区域,要么使用全透明背景导致图标在多变内容背景下"隐形"。内容本该延伸到屏幕底部形成视觉延伸,却被传统导航栏无情"砍断"。当你希望Tab栏具备类似毛玻璃光晕效果、点击时有细腻光影反馈时,手写实现需处理实时色彩抽取、多层模糊,性能难以保证。

HarmonyOS 6.1.0 的 "悬浮页签(Floating Tab Bar)+ 沉浸光感(Immersive Material)" 将动态模糊、实时色彩抽取与多层光影叠加封装为开箱即用组件,支持自适应降级策略。你只需配置几个属性,就能让底部导航栏悬浮于内容之上,随手指触摸产生灵动光效反馈。本文基于多位社区开发者的实战经验,带你从零打通完整实现链路。

核心概念解析

悬浮页签:让内容"流淌"到屏幕边缘

传统底部导航栏(如系统Tabs组件的默认样式)锚定在屏幕底部,固定不透明色块使内容区域只能从导航栏顶部开始布局,视觉上形成一块"补丁"。悬浮页签(Floating Tab Bar)通过半透明材质或模糊效果"悬浮"在内容之上,内容可延伸到屏幕最底部,导航栏以覆盖层形式叠加,实现真正全面屏沉浸体验。

在HarmonyOS 6.1.0中,悬浮页签载体为 HdsTabs 组件(来自 @kit.UIDesignKit 的 HDS 设计体系)。关键属性是 barFloatingStyle,配合 barOverlap(true) 让内容重叠到导航栏下方,配合 systemMaterialEffect 设置沉浸光感材质。

沉浸光感:把物理光照模型搬进UI

传统毛玻璃(Blur)只对背景做模糊 + 半透明叠加,是"静态"的。沉浸光感(IMMERSIVE) 在模糊基础上增加实时动态光照模拟:点击或划过Tab栏时,模拟物理光照产生的"光晕"和"反射"效果------被点击图标周围出现柔和白色光晕,Tab栏像在玻璃表面下被点光源照亮。这种细腻光影反馈极大增强控件的立体感和触控真实感。

HdsTabsHdsNavigation(标题栏)都通过 systemMaterialEffect 属性启用此效果。系统提供多种材质级别(如 ADAPTIVEIMMERSIVE)和精细度级别(如 EXQUISITE),开发者可根据设备性能与设计需求选择。

组件关系图

复制代码
应用内容(图片/列表/视频)
    ↑ 延伸至全屏
    ↓ 叠加覆盖
底部HdsTabs (barOverlap=true)
    ├─ 背景:系统材质效果(systemMaterialEffect)
    │      └─ IMMERSIVE / ADAPTIVE + 级别 (EXQUISITE / BALANCED)
    ├─ 图标 + 文字(可自定义选中/未选中颜色)
    └─ 交互反馈:点击时产生动态光晕

环境准备

项目 要求
DevEco Studio 6.1.0 及以上版本
HarmonyOS SDK API 23 (HarmonyOS 6.1.0) 及以上
设备/模拟器 支持 HDS 材质效果(API 23+)
工程类型 Stage模型,使用 ArkTS 语言

依赖配置 :在 oh-package.json5 中添加 @kit.UIDesignKit 依赖(通常新建工程已包含,若没有需手动添加):

json 复制代码
{
  "dependencies": {
    "@kit.UIDesignKit": "file:../../../../ohos/api/23/ets/kit/UIDesignKit"
  }
}

注意:路径需根据实际 SDK 安装位置调整,建议使用 DevEco Studio 自动管理的版本,避免手动路径出错。

实战步骤

1. 创建基础页面结构与数据源

pages/Index.ets 中,先定义 Tab 页签的数据源(图标和文字)。这里使用 @ohos.want 提供的资源引用方式,也可以直接传字符串和图片 resource 对象。

typescript 复制代码
import { HdsTabs, TabContent } from '@kit.UIDesignKit';

@Entry
@Component
struct Index {
  @State currentIndex: number = 0;
  private tabs: { icon: Resource; text: string }[] = [
    { icon: $r('app.media.ic_photo'), text: '照片' },
    { icon: $r('app.media.ic_album'), text: '相册' },
    { icon: $r('app.media.ic_discover'), text: '发现' },
    { icon: $r('app.media.ic_my'), text: '我的' }
  ];

  build() {
    Column() {
      // 略:后续步骤填充
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#FF000000')
  }
}

注意 :图标资源需提前放置到 resources/base/media/ 下,并在 media 目录的 string.jsonmedia.json 中注册,否则 $r 无法引用。

2. 使用 HdsTabs 实现悬浮页签

HdsTabs 组件通过 barFloatingStyle 属性启用悬浮模式。同时设置 barOverlap(true) 允许内容与导航栏重叠。底部内容(如 GridList)需要将 expandSafeArea 设置为 true 以延伸到安全区。

typescript 复制代码
build() {
  Stack() {
    // 背景内容:全屏图片列表
    Grid() {
      ForEach(this.imageList, (item: Resource) => {
        Image(item)
          .objectFit(ImageFit.Cover)
          .width('100%')
          .aspectRatio(1)
      })
    }
    .columnsTemplate('1fr 1fr 1fr')
    .columnsGap(4)
    .rowsGap(4)
    .padding({ left: 4, right: 4 })
    .width('100%')
    .height('100%')
    .expandSafeArea([SafeAreaType.SYSTEM, SafeAreaType.CUTOUT]) // 内容延伸到底部

    // HdsTabs 覆盖在上面
    HdsTabs({
      barFloatingStyle: BarFloatingStyle.DEFAULT, // 启用悬浮样式
      barOverlap: true,                            // 内容重叠到导航栏下方
      systemMaterialEffect: {
        target: MaterialTarget.IMMERSIVE,          // 沉浸光感
        level: EffectLevel.EXQUISITE               // 精细度最高
      }
    }) {
      ForEach(this.tabs, (tab: { icon: Resource; text: string }, index: number) => {
        TabContent() {
          // 每个 Tab 的内容可以为空(由外层 Grid 统一展示),或独立内容
          Column().width('100%').height('100%')
        }
        .tabBar({
          icon: tab.icon,
          text: tab.text,
          selectedIcon: tab.icon, // 可换为选中态图标
        })
      })
    }
    .width('100%')
    .height('100%') // 必须给高度,否则可能显示异常
    .onChange((index: number) => {
      this.currentIndex = index;
    })
  }
  .width('100%')
  .height('100%')
}

注意HdsTabs 的高度必须明确指定(如 '100%'),否则可能因父容器 Stack 无固定高度导致组件不显示。expandSafeArea 属性可以让内容延伸到系统状态栏和安全区域,实现真全面屏。

3. 配置沉浸光感效果

systemMaterialEffect 对象接受 targetlevel 两个子属性:

  • target: MaterialTarget.IMMERSIVE 启用沉浸光感;MaterialTarget.ADAPTIVE 为自适应(根据背景颜色变化,效果略弱)
  • level: EffectLevel.EXQUISITE 精细模式(适合高端设备);EffectLevel.BALANCED 平衡模式(兼顾性能)

如果设备不支持 IMMERSIVE,系统会自动降级为 ADAPTIVE 或普通模糊(无光晕)。你可以通过 HdsTabs 的回调 onMaterialEffectLoad 监听材质加载状态,打印日志确认是否生效:

typescript 复制代码
HdsTabs({ ... }) {
  // ...
}
.onMaterialEffectLoad((isLoaded: boolean) => {
  console.info(`沉浸光感加载状态:${isLoaded}`);
})

4. 自定义 Tab 图标与文字样式

tabBar 属性支持 iconselectedIcontextfontColorselectedFontColoriconColorselectedIconColor 等。以下示例设置图标默认白色、选中时高亮颜色:

typescript 复制代码
.tabBar({
  icon: tab.icon,
  selectedIcon: tab.selectedIcon ?? tab.icon,
  text: tab.text,
  fontColor: '#99FFFFFF',            // 未选中文字颜色(半透明白)
  selectedFontColor: '#FFFFFFFF',     // 选中文字颜色(纯白)
  iconColor: '#99FFFFFF',             // 未选中图标颜色
  selectedIconColor: '#FF007AFF',      // 选中图标颜色(蓝色)
})

注意 :颜色值需带透明度前缀(如 #99 对应 60% 透明度),否则会不透明。建议统一使用 8 位十六进制 ARGB 格式,避免不同设备解析差异。

5. 处理内容区域与 Tab 重叠布局

由于 barOverlap: true,内容(如 Grid)会延伸到 Tab 下方。如果不希望内容被 Tab 遮挡,可以在内容底部添加与 Tab 高度相同的 padding 或使用 margin。推荐通过 HdsTabstabBarHeight 属性获取实际高度后动态设置:

typescript 复制代码
@State tabHeight: number = 0;

// 在 HdsTabs 的 onAreaChange 回调中获取高度
HdsTabs({ ... }) {
  // ...
}
.onAreaChange((oldArea: Area, newArea: Area) => {
  this.tabHeight = newArea.height as number;
})

// 在 Grid 底部添加 padding
Grid() { ... }
.padding({ bottom: this.tabHeight })

注意onAreaChange 在组件渲染完成后才触发,首次可能为 0。如果应用启动时需要立即生效,可以在 aboutToAppear 中预设一个默认高度(如 56vp),待回调更新。

6. 完整代码整合

将以上代码片段合并到 Index.ets,添加必要的状态定义和图片列表数据。以下为关键部分完整版:

typescript 复制代码
import { HdsTabs, TabContent, BarFloatingStyle, MaterialTarget, EffectLevel, SafeAreaType } from '@kit.UIDesignKit';

@Entry
@Component
struct Index {
  @State currentIndex: number = 0;
  @State tabHeight: number = 56; // 默认高度,后续回调更新
  private tabs: { icon: Resource; selectedIcon?: Resource; text: string }[] = [
    { icon: $r('app.media.ic_photo'), text: '照片' },
    { icon: $r('app.media.ic_album'), text: '相册' },
    { icon: $r('app.media.ic_discover'), text: '发现' },
    { icon: $r('app.media.ic_my'), text: '我的' }
  ];
  private imageList: Resource[] = [ /* 省略众多图片资源 */ ];

  build() {
    Stack() {
      Grid() {
        ForEach(this.imageList, (item: Resource) => {
          Image(item)
            .objectFit(ImageFit.Cover)
            .width('100%')
            .aspectRatio(1)
        })
      }
      .columnsTemplate('1fr 1fr 1fr')
      .columnsGap(4)
      .rowsGap(4)
      .padding({ left: 4, right: 4, bottom: this.tabHeight })
      .width('100%')
      .height('100%')
      .expandSafeArea([SafeAreaType.SYSTEM, SafeAreaType.CUTOUT])

      HdsTabs({
        barFloatingStyle: BarFloatingStyle.DEFAULT,
        barOverlap: true,
        systemMaterialEffect: {
          target: MaterialTarget.IMMERSIVE,
          level: EffectLevel.EXQUISITE
        }
      }) {
        ForEach(this.tabs, (tab: { icon: Resource; selectedIcon?: Resource; text: string }, index: number) => {
          TabContent() {
            Column().width('100%').height('100%')
          }
          .tabBar({
            icon: tab.icon,
            selectedIcon: tab.selectedIcon ?? tab.icon,
            text: tab.text,
            fontColor: '#99FFFFFF',
            selectedFontColor: '#FFFFFFFF',
            iconColor: '#99FFFFFF',
            selectedIconColor: '#FF007AFF',
          })
        })
      }
      .width('100%')
      .height('100%')
      .onChange((index: number) => {
        this.currentIndex = index;
      })
      .onAreaChange((oldArea: Area, newArea: Area) => {
        const height = newArea.height as number;
        if (height > 0) {
          this.tabHeight = height;
        }
      })
    }
    .width('100%')
    .height('100%')
  }
}

常见问题与注意事项

1. 沉浸光感效果未生效

检查设备是否支持 IMMERSIVE 级别。API 23 模拟器上可能只支持 ADAPTIVE,真机(如 Mate 60 Pro 系列)正常。可通过 onMaterialEffectLoad 回调判断。

2. 内容被 Tab 遮挡

确保 barOverlap: true,同时在内容底部添加与 Tab 等高的内边距。Tab 高度动态变化时需通过 onAreaChange 更新。

3. 图标显示异常

图标资源需为 SVG 或 PNG,且尺寸建议 24vp~32vp。如果使用 $r 引用后图标变黑,检查资源文件是否被误设为 @color 类型,或者图标路径包含空格。

4. 性能问题

IMMERSIVE + EXQUISITE 在低端设备上可能导致帧率下降。建议在 onMaterialEffectLoad 中根据设备等级动态切换级别,或使用 BALANCED

扩展:结合 HdsNavigation 实现顶部标题栏沉浸光感

HdsNavigation 同样支持 systemMaterialEffect,可与悬浮页签组成全屏沉浸体验。示例:

typescript 复制代码
HdsNavigation({
  title: '相册',
  systemMaterialEffect: {
    target: MaterialTarget.IMMERSIVE,
    level: EffectLevel.BALANCED
  }
})

结语

悬浮页签与沉浸光感的组合,能显著提升应用底部导航的视觉融入度和交互反馈细腻度。以上代码可直接复用到你的项目中,根据实际设计调整颜色、图标和材质级别。你在实现中是否遇到了其他降级策略或自定义材质的问题?欢迎在评论区讨论。

版本要求与依赖配置

使用 HDS 悬浮页签组件最低要求如下:

项目 版本
HarmonyOS API 23 (HarmonyOS 6.1.0) 及以上
HarmonyOS SDK API 23 (HarmonyOS 6.1.0) 及以上
设备/模拟器 支持 HDS 材质效果(API 23+)
工程类型 Stage模型,使用 ArkTS 语言

依赖配置 :在 oh-package.json5 中添加 @kit.UIDesignKit 依赖(通常新建工程时已包含,若没有需手动添加):

json 复制代码
// oh-package.json5
{
  "dependencies": {
    "@kit.UIDesignKit": "file:../../../../../SystemLibs/Resource/ArkTS/ets/kit/UIDesignKit"
  }
}

注意:实际路径可能因SDK安装位置不同而变化,建议通过DevEco Studio的"添加依赖"可视化操作完成。手动路径容易写错,可视化操作还能自动解决依赖冲突。

核心实现:手把手教学

代码块一:基础配置------导入模块与数据类型定义

导入 HdsTabs 及其相关类型,并定义底部Tab的菜单项(包括图标、文字,以及选中/未选中颜色的配置策略)。

typescript 复制代码
// Index.ets
import { HdsTabs, HdsTabsController, BottomTabBarStyle } from '@kit.UIDesignKit';
import { image } from '@kit.ImageKit'; // 用于获取系统图标资源

// 定义Tab栏配置项
interface TabConfig {
  title: ResourceStr;
  icon: Resource;      // 系统SymbolIcon或自定义图片资源
  selectedIcon?: Resource; // 可选:选中态图标
  badgeCount?: number;  // 可选:角标数
}

// 三个Tab示例:首页、发现、我的
const tabList: TabConfig[] = [
  { title: '首页', icon: $r('sys.symbol.photo'), selectedIcon: $r('sys.symbol.photo.fill') },
  { title: '发现', icon: $r('sys.symbol.heart') },
  { title: '我的', icon: $r('sys.symbol.person.circle') }
];

@Entry
@Component
struct Index {
  // HdsTabs控制器,用于切换Tab
  private tabsController: HdsTabsController = new HdsTabsController();

  build() {
    // ... 后续代码
  }
}

关键点说明

  • sys.symbol. 前缀引用系统内置Symbol图标,支持多色自适应渲染,是HDS推荐的做法。如果使用自定义图片,需要额外处理深色/浅色模式。
  • HdsTabsController 用于程序化控制Tab切换,是可选的。如果不提供,则完全由用户交互驱动。

代码块二:核心逻辑------悬浮页签 + 沉浸光感配置

实现悬浮页签的精髓:通过 barFloatingStyle 属性开启悬浮样式,并通过 systemMaterialEffect 配置沉浸光感材质。官方推荐的 ADAPTIVE 自适应模式让系统根据设备性能自动选择最佳效果。

typescript 复制代码
build() {
  Column() {
    HdsTabs({
      controller: this.tabsController,
      barFloatingStyle: { // 开启悬浮样式
        borderRadius: 24,               // 圆角弧度,单位vp
        margins: { left: 16, right: 16, bottom: 12 },
        backgroundColor: '#80FFFFFF',  // 半透明白色背景
        shadow: {
          radius: 10,
          color: '#26000000',          // 阴影颜色和透明度
          offsetX: 0,
          offsetY: -2
        }
      },
      bottomTabBarStyle: BottomTabBarStyle.FIXED, // 固定底部模式(必须有)
      systemMaterialEffect: {          // 沉浸光感材质
        style: HdsBlurStyle.ADAPTIVE,
        light: {
          fallbackColor: '#E8FFFFFF', // 亮色模式降级色(当不支持模糊时使用)
          fallbackOpacity: 0.9
        },
        dark: {
          fallbackColor: '#E81A1A2E', // 暗色模式降级色
          fallbackOpacity: 0.85
        }
      }
    }) {
      // Tab内容区域
      ForEach(tabList, (tab: TabConfig, index: number) => {
        TabContent() {
          // 这里放各个Tab对应的页面内容
          Text(tab.title)
            .fontSize(20)
            .width('100%')
            .height('100%')
            .textAlign(TextAlign.Center)
        }
        .tabBar(this.buildTabItem(tab))
      })
    }
    .width('100%')
    .height('100%')
  }
  .width('100%')
  .height('100%')
}

// 自定义TabBar外观(支持选中态icon和角标)
@Builder
buildTabItem(tab: TabConfig) {
  Column() {
    Image(tab.selectedIcon ? (this.currentIndex === tabList.indexOf(tab) ? tab.selectedIcon : tab.icon) : tab.icon)
      .width(24)
      .height(24)
      .fillColor(this.currentIndex === tabList.indexOf(tab) ? '#007AFF' : '#8A8A8F')
    Text(tab.title)
      .fontSize(12)
      .fontColor(this.currentIndex === tabList.indexOf(tab) ? '#007AFF' : '#8A8A8F')
    if (tab.badgeCount && tab.badgeCount > 0) {
      Badge({ count: tab.badgeCount, position: BadgePosition.RightTop }) {
        // 占位符,实际Badge会覆盖在右上角
      }
    }
  }
}

注意事项:

  • barFloatingStylebackgroundColor 建议使用半透明色,否则会遮盖光感材质的效果。如果设为纯色,则 systemMaterialEffect 将无效。
  • systemMaterialEffect.style 可选 HdsBlurStyle.EXTRA_LIGHTLIGHTDARKADAPTIVE 等。ADAPTIVE 会根据当前系统主题自动切换,但如果应用内主题与系统不一致可改用固定值。
  • 降色配置 fallbackColor 在低端设备或不支持实时模糊的场景下起效,应尽量与预期材质效果接近,避免突兀。

代码块三:完整示例------包含状态管理和页面切换

为了让Tab切换生效,需要维护当前选中索引,并绑定 onChange 事件。

typescript 复制代码
@State currentIndex: number = 0;

build() {
  Column() {
    HdsTabs({
      controller: this.tabsController,
      barFloatingStyle: { /* 同上 */ },
      bottomTabBarStyle: BottomTabBarStyle.FIXED,
      systemMaterialEffect: { /* 同上 */ },
      index: this.currentIndex,
      onChange: (index: number) => {
        this.currentIndex = index;
      }
    }) {
      ForEach(tabList, (tab: TabConfig, index: number) => {
        TabContent() {
          // 此处可根据不同index展示不同页面
          if (index === 0) {
            HomePage() // 自定义组件
          } else if (index === 1) {
            DiscoverPage()
          } else {
            ProfilePage()
          }
        }
        .tabBar(this.buildTabItem(tab))
      })
    }
    .width('100%')
    .height('100%')
  }
}

注意事项与踩坑记录

  1. barFloatingStylesystemMaterialEffect 必须同时使用才有沉浸光感效果。如果只配悬浮样式,背景是纯色半透明但不带毛玻璃;只配材质效果而不配悬浮样式,则Tab栏不悬浮,材质效果会应用于普通底部栏。
  2. Symbol图标缩放问题 :使用 sys.symbol 图标时,若 Image 组件设置的宽高与图标原始尺寸不一致,可能导致图标显示不全或变形。建议保持宽高一致,或者使用 objectFit: ImageFit.Contain 防止裁剪。
  3. 深色模式适配 :自定义 tabBar 中的图标和文字颜色需要根据主题切换。推荐使用 @ohos.arkui.themeTheme 能力或 @Styles 配合 isDarkMode() 判断。
  4. Badge 角标 :HdsTabs 本身不支持Badge属性,需要通过 Badge 组件嵌套在 tabBar 内实现,如上文代码所示。注意 Badge 位置依赖于父容器,建议使用 StackOverlay 精确定位。

以上即完成 HDS 悬浮页签+沉浸光感材质的核心集成。实际项目中还需根据业务调整 tabList 和页面内容。如果在实现中遇到性能问题(特别是低端机上的实时模糊),可以尝试将 systemMaterialEffect.style 改为 HdsBlurStyle.LIGHTHdsBlurStyle.DARK 并关闭 ADAPTIVE,或降低模糊半径(当前HDS暂未开放半径参数,可反馈给官方)。

欢迎在评论区分享你遇到的适配问题或更好的实践方案。

HdsTabsController 用于程序化控制 Tab 切换,属于可选能力。如果需要在外部(如按钮点击、手势事件)触发 Tab 跳转,可以声明一个控制器实例并绑定到 Tabs 组件;否则无需引入,避免额外的实例化开销。

接下来是实现悬浮页签的核心配置:通过 barFloatingStyle 属性启用悬浮样式,并配合 systemMaterialEffect 参数配置沉浸光感材质。官方推荐的 ADAPTIVE 自适应模式能够根据设备 GPU 和内存状况动态切换渲染等级,开发者无需手动干预,即可在低端机上获得流畅的降级效果,在旗舰机上呈现完整的毛玻璃光感。

下面的代码展示了集成上述配置的典型页面结构,内容区域使用 Scroll 承载可滚动列表,确保悬浮页签在滚动时正常显现:

typescript 复制代码
// Index.ets 完整页面结构
build() {
  Column() {
    // 内容区域:使用Scroll实现可滚动内容,验证悬浮效果
    Scroll() {
      Column() {
        // 模拟大量图片/卡片内容,确保内容延伸到屏幕底部
        ForEach(new Array(20), (_, index) => {
          Text(`内容卡片 ${index + 1}`)
            .width('100%')
            .height(100)
            .backgroundColor(index % 2 === 0 ? '#FFE0E0E0' : '#FFF5F5F5')
            .borderRadius(12)
            .textAlign(TextAlign.Center)
            .margin({ top: 12 })
        })
      }
      .width('100%')
      .padding({ bottom: 80 }) // 预留底部空间,避免被悬浮导航栏遮挡
    }
    .width('100%')
    .layoutWeight(1) // 占据剩余空间
    .edgeEffect(EdgeEffect.Spring) // 弹性边缘效果增加质感
---

注意 Scroll 的 .padding({ bottom: 80 }) 不可省略,否则当内容不足一屏时,悬浮导航栏会直接覆盖最后一张卡片;实际应用中应根据导航栏高度动态计算。.edgeEffect(EdgeEffect.Spring) 可提供类似 iOS 的弹性回弹手感,与沉浸材质搭配后视觉反馈更一致。

实际项目中的材质选择(.systemMaterialEffect(MaterialEffect.ADAPTIVE) vs MaterialEffect.LIGHT)建议根据 UI 定稿的模糊程度和透明度来微调,不同设备上的效果存在差异。开发时可以在模拟器中开启"显示边界"来确认悬浮层是否超出预期区域。

HarmonyOS 实现悬浮底部导航栏的配置要点

开发中实现悬浮底部导航栏时,常遇到的问题包括:内容页面如何正确延伸到Tab栏下方、如何在不同设备上呈现统一的毛玻璃效果、Tab栏圆角阴影如何适配。通过Tabs组件的barOverlapbarFloatingStyle属性可以准确控制这些行为,避免内容被Tab栏遮挡或悬浮效果失效。

以下是完整的悬浮底部导航栏实现代码,包括页面布局和Tab栏配置:

typescript 复制代码
// 页面根布局,使用Column包裹向下兼容的滚动区域和悬浮Tab栏
Column() {
  // 可滚动内容区域
  Scroll() {
    // 实际业务内容,此处略
    Column() {
      // ...
    }
    .width('100%')
    .padding({ bottom: 80 }) // 预留底部空间,避免被悬浮导航栏遮挡
  }
  .width('100%')
  .layoutWeight(1) // 占据剩余空间
  .edgeEffect(EdgeEffect.Spring) // 弹性边缘效果增加质感

  // HdsTabs悬浮页签
  HdsTabs({
    controller: this.tabsController
  }) {
    // 使用ForEach动态生成TabContent
    ForEach(tabList, (tab: TabConfig, index: number) => {
      TabContent() {
        // Tab对应的内容区域,这里简单示意
        Column() {
          Text(`这是「${tab.title}」页面`)
            .fontSize(20)
            .fontWeight(FontWeight.Bold)
        }
        .width('100%')
        .height('100%')
        .justifyContent(FlexAlign.Center)
      }
      .tabBar(
        new BottomTabBarStyle(tab.icon, tab.title)
      )
    })
  }
  .barOverlap(true)                               // 关键:内容区域与Tab栏重叠
  .barFloatingStyle({                             // 悬浮样式配置
    systemMaterialEffect: {
      type: MaterialType.ADAPTIVE,                // 自适应材质类型
      level: MaterialLevel.ADAPTIVE               // 自适应精细度级别
    },
    blurStyle: BlurStyle.COMPONENT_THICK,         // 额外模糊程度(用于降级兜底)
    borderRadius: 28,                             // 悬浮Tab栏的圆角半径
    shadow: {
      radius: 12,
      color: '#1A000000'                          // 轻微阴影增加立体感
    }
  })
  .width('100%')
  .height(64)                                     // 固定高度
  .position({ bottom: 0 })                        // 吸附在底部
}
.width('100%')
.height('100%')
.backgroundColor('#FFFFFF')                       // 内容背景色,实际会被内容覆盖

关键配置说明

  • barOverlap(true) 必须放在HdsTabs组件的方法链中,且在.barFloatingStyle之前调用。如果漏掉该设置,内容区域会停留在Tab栏上方,悬浮效果将无法实现。
  • systemMaterialEffect 是核心参数:type: MaterialType.ADAPTIVE 让系统根据设备性能自动选择最优材质(高端设备使用沉浸光感,低端设备使用普通模糊),level: MaterialLevel.ADAPTIVE 控制精细度。实测在HarmonyOS 3.1及以上版本,ADAPTIVE类型能覆盖绝大多数机型。
  • blurStyle: BlurStyle.COMPONENT_THICK 作为降级兜底:当systemMaterialEffect无法使用时(如旧版本或低端设备),系统退化为纯模糊效果,保证视觉效果不断裂。建议同时保留这两个配置。
  • 圆角borderRadius: 28和阴影shadow能增强玻璃面板的物理质感,模拟悬浮在内容上方的视觉效果。如果应用背景色较深,阴影颜色建议调整为半透明黑(如#1A000000),避免出现生硬的黑色块。

如果有其他实现方式或优化建议,欢迎在评论区讨论。

blurStyle: BlurStyle.COMPONENT_THICK 作为降级兜底:当设备不支持沉浸光感时,系统退化为纯模糊效果,保证视觉连续性。给Tab栏添加圆角和阴影能进一步增强悬浮感,模拟玻璃面板的物理外观。

代码块三:进阶用法------强制沉浸光感 + 设备能力检测与降级

如果你希望在支持设备上强制开启最顶级的沉浸光效(比如点击Tab时出现明显的白色光晕反馈),可以使用 IMMERSIVE + EXQUISITE 组合。但必须先查询设备能力,否则在低端设备上可能导致严重卡顿。以下代码演示了如何通过检测设备能力来安全启用:

typescript 复制代码
// 使用 @ohos.arkui.modifier 或系统能力API检测设备支持情况
// 这里演示基于系统API返回的材质能力进行条件判断

import { BusinessError } from '@kit.BasicServicesKit';
import { systemCapability } from '@kit.SystemCapabilityKit'; // 假设存在,实际可用@ohos.arkui.systemCapability

// 假设有一个工具函数检测沉浸光感是否可用(实际API需根据文档调整)
function isImmersiveSupported(): boolean {
  // 返回true表示支持,false表示不支持
  return systemCapability.hasSystemCapability('system.ability.immersiveMaterial');
}

@State canUseImmersive: boolean = isImmersiveSupported();

// 在barFloatingStyle中动态配置
build() {
  // ... 其他结构

  HdsTabs({ /* ... */ })
  .barOverlap(true)
  .barFloatingStyle({
    systemMaterialEffect: this.canUseImmersive
      ? {
          type: MaterialType.IMMERSIVE,        // 强制沉浸光感
          level: MaterialLevel.EXQUISITE       // 最高精细度
        }
      : {
          type: MaterialType.ADAPTIVE,         // 降级为自适应
          level: MaterialLevel.ADAPTIVE
        },
    blurStyle: BlurStyle.COMPONENT_THICK,
    borderRadius: 28,
    shadow: { radius: 12, color: '#1A000000' }
  })
  // ...
}

⚠️ 避坑提示systemCapability 的具体调用方式需以HarmonyOS 6.1.0官方文档为准。素材中开发者提到"必须先通过API查询设备能力",但未给出确切API名。实际可改用 @ohos.arkui.modifier 中的 SystemCapabilityModifier,或直接捕获异常------在不支持的设备上设置 IMMERSIVE 不会崩溃,但可能无效果且性能差。更稳妥的做法是先用 MaterialType.ADAPTIVE,让系统自行决定。

关于材质级别的选择(来自开发者云杰的实战总结):

  • EXQUISITE(精致):光影反馈最强烈,点击时有明显的点光源照亮效果,适合旗舰机型。
  • BALANCED(平衡):光影反馈适中,性能与视觉效果折中。
  • ADAPTIVE(自适应):由系统根据当前负载动态切换级别,推荐作为默认项。

完整示例:融合悬浮页签 + 沉浸光感的相册类页面

将上述代码块整合成一个可运行的 Index.ets 页面,并补充选中/未选中图标颜色自定义逻辑(参考开发者Maybe在《左右相册》中的实践)。实际开发中,你可以根据业务需求调整图标颜色,不同材质级别的光晕反馈差异明显,码友们更倾向用哪种?

IVE(自适应)级别

IVE(自适应)由系统根据当前负载动态切换级别,推荐作为默认项。无需手动指定具体光感强度,系统会根据页面复杂度和设备散热状况自动在 EXQUISITE 与 BALANCED 之间调整,兼顾视觉效果与性能。

!截图: 沉浸光感不同级别的对比效果------EXQUISITE vs BALANCED

完整示例:融合悬浮页签 + 沉浸光感的相册类页面

下面将上述代码块整合成一个可运行的 Index.ets 页面,并补充选中/未选中图标颜色自定义逻辑(参考开发者 Maybe 在《左右相册》中的实践)。注意:沉浸光感必须搭配 BarMode.Float(悬浮模式)才能生效,否则即使设置了层级参数也无法看到模糊效果。

typescript 复制代码
// Index.ets 完整代码
import { HdsTabs, HdsTabsController, BottomTabBarStyle } from '@kit.UIDesignKit';

@Entry
@Component
struct AlbumPage {
  private tabsController: HdsTabsController = new HdsTabsController();
  @State currentIndex: number = 0;

  // Tab配置------包含默认图标与选中图标,通过 selectedIcon 区分
  private tabData: { title: Resource; icon: Resource; selectedIcon: Resource }[] = [
    { title: $r('app.string.photos'), icon: $r('sys.symbol.photo'), selectedIcon: $r('sys.symbol.photo.fill') },
    { title: $r('app.string.videos'), icon: $r('sys.symbol.video'), selectedIcon: $r('sys.symbol.video.fill') },
    { title: $r('app.string.albums'), icon: $r('sys.symbol.rectangle.stack'), selectedIcon: $r('sys.symbol.rectangle.stack.fill') }
  ];

  build() {
    Column() {
      // 主要内容区域------展示随机照片网格,此处省略具体图片加载逻辑
      Grid() {
        ForEach(new Array(30), (_, index) => {
          GridItem() {
            Column()
              .aspectRatio(1)
              .backgroundColor('#FFD3D3D3')
              .borderRadius(8)
          }
        })
      }
      .columnsTemplate('1fr 1fr 1fr')
      .rowsTemplate('1fr 1fr 1fr')
      .columnsGap(4)
      .rowsGap(4)
      .padding(8)
      .layoutWeight(1)   // 占据全部剩余空间

      // 悬浮底部导航栏------使用 HdsTabs + BottomTabBarStyle
      HdsTabs({ barStyle: BottomTabBarStyle.Float, controller: this.tabsController }) {
        ForEach(this.tabData, (tabItem, index) => {
          TabContent() {
            // 每个 tab 对应的页面内容,此处留空
          }
          .tabBar(this.buildTabBar(tabItem, index))
        })
      }
      .immersiveMode(ImmersiveMode.IVE)   // 关键:开启沉浸光感自适应模式
      .selectedColor('#FF007AFF')
      .unselectedColor('#FF8E8E93')
    }
    .height('100%')
    .width('100%')
  }

  @Builder
  buildTabBar(tabItem: typeof this.tabData[0], index: number) {
    Column() {
      Image(this.currentIndex === index ? tabItem.selectedIcon : tabItem.icon)
        .size({ width: 24, height: 24 })
        .fillColor(this.currentIndex === index ? '#FF007AFF' : '#FF8E8E93')
      Text(tabItem.title)
        .fontSize(12)
        .fontColor(this.currentIndex === index ? '#FF007AFF' : '#FF8E8E93')
    }
    .onClick(() => {
      this.currentIndex = index;
      this.tabsController.changeIndex(index);
    })
  }
}

代码中的 immersiveMode(ImmersiveMode.IVE) 启用了自适应沉浸光感,图标颜色通过 selectedColorunselectedColor 统一设置;如果你需要更精细的图标颜色控制(例如不同 Tab 使用不同配色),可以在自定义 @Builder 中直接覆盖,注意不要与全局设置冲突。

如果对沉浸光感在不同设备上的表现差异有疑问,可以在评论区聊聊你的具体机型与版本。

在实现沉浸式相册或媒体播放器时,底部导航栏的悬浮效果和动态光感能显著提升用户体验。但如何让Tab栏半透明模糊地浮在内容之上,同时点击时产生灵动的光照反馈?关键在于barOverlapbarFloatingStyle属性的组合,以及通过BottomTabBarStyle自定义图标颜色。下面展示具体配置步骤。

网格布局与悬浮页签完整配置

以下代码在九宫格图片网格之上叠加了一个悬浮导航栏。网格占满剩余空间,Tab栏使用barOverlap(true)实现悬浮,并设置圆角模糊背景和阴影。注意BottomTabBarStyle的第二个参数用于自定义未选中图标颜色(白色),第三个参数用于选中图标颜色(亮蓝),确保在深色背景下清晰可见。

typescript 复制代码
      .columnsTemplate('1fr 1fr 1fr')
      .rowsTemplate('1fr 1fr 1fr')
      .columnsGap(4)
      .rowsGap(4)
      .padding(8)
      .layoutWeight(1)   // 占据全部剩余空间

      // 悬浮页签
      HdsTabs({ controller: this.tabsController, index: this.currentIndex }) {
        ForEach(this.tabData, (item, index) => {
          TabContent() {
            Stack() {
              // 每个Tab的内容区,实际业务中在此加载对应媒体流
            }
          }
          .tabBar(
            new BottomTabBarStyle(
              item.icon,
              item.title,
              // 自定义未选中图标颜色:白色(因为悬浮背景透明,白色在任何内容上都有较好对比)
              { color: '#FFFFFF' },
              // 自定义选中图标颜色:使用系统强调色,此处设为亮蓝色
              { color: '#FF007AFF' }
            )
          )
        })
      }
      .barOverlap(true)
      .barFloatingStyle({
        systemMaterialEffect: {
          type: MaterialType.ADAPTIVE,
          level: MaterialLevel.ADAPTIVE
        },
        blurStyle: BlurStyle.COMPONENT_ULTRA_THICK, // 更厚实的模糊效果,提升对比度
        borderRadius: 32,
        shadow: { radius: 16, color: '#1A000000' }
      })
      .width('100%')
      .height(72)        // 比默认稍高,视觉更舒适
      .position({ bottom: 0 })
      .onChange((index: number) => {
        this.currentIndex = index;
      })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#000000')  // 深色背景衬托悬浮效果

注意事项

  1. barOverlap(true)必须配合position({ bottom: 0 })使用,否则Tab栏可能被内容遮挡。
  2. BlurStyle.COMPONENT_ULTRA_THICK在低端设备可能降级为普通模糊,无需额外处理。
  3. 如果自定义图标颜色不生效,检查BottomTabBarStyle构造参数顺序:前两个为图标和标题,后两个分别为未选中和选中样式对象。

运行效果

  • 底部Tab栏呈半透明"玻璃面板"悬浮在图片网格之上,网格可以滑动到屏幕最底部。
  • 点击任意Tab,图标周围出现柔和的白色光晕,同时Tab栏背景中对应位置有动态光照扩散。
  • 未选中的图标为白色(利用 BottomTabBarStyle 构造参数自定义颜色),选中图标变为蓝色,视觉层次清晰。

运行效果与验证

  1. 编译运行:使用真机(API 23+)或模拟器运行项目。注意模拟器可能不支持完整沉浸光感,建议使用真机测试光影效果。
  2. 验证悬浮性:快速滚动内容区域,确认网格可以滑动到屏幕最底部,Tab栏始终浮在最上层。
  3. 验证沉浸光感:点击不同的Tab,观察被点击的图标附近是否有光晕扩散(类似轻按玻璃时的光斑)。如果设备支持,光效会非常明显;不支持则呈现普通模糊效果。
  4. 验证自适应降级:尝试在低端设备(如中低端麒麟芯片)上运行,观察是否有明显卡顿。正常情况系统应自动降级为普通模糊,保持流畅。

预期输出:应用启动后,底部呈现一个圆角矩形、半透明模糊的悬浮导航栏,点击Tab时产生动态光效。内容区域无白色截断线,视觉上与前几年流行的"流体导航栏"类似,但质感更佳。

常见问题与避坑指南

  • 光效不出现 :检查设备是否支持MaterialType.ADAPTIVE,若不支持则systemMaterialEffect降级为普通模糊,光效不会触发。无需修改代码,系统自动处理。
  • 悬浮栏被内容遮挡 :确保barOverlap(true)已设置,且父容器的高度布局正确。如果网格容器使用了layoutWeight(1),需确认父容器为ColumnRow且高度明确。
  • 图标颜色未生效BottomTabBarStyle构造器的第三、四个参数必须是对象类型({ color: string }),不能直接传入字符串。同时注意选中/未选中顺序不要颠倒。

你在实现悬浮导航时遇到过哪些兼容性问题?欢迎在评论区分享。

用 HarmonyOS 6.1.0 实现悬浮导航栏与沉浸光效:从代码到降级兜底

在实际开发中,很多应用需要底部导航栏悬浮在内容之上,同时配合点击光效提升交互质感。但不同的设备对光影特性的支持差异明显:高端机型可以展现流畅的沉浸光晕,低端机型则可能卡顿或干脆不支持。HarmonyOS 6.1.0 的 systemMaterialEffect 提供了 ADAPTIVEIMMERSIVE 两种模式,配合 barFloatingStyle 可以实现自适应降级的悬浮导航栏。下文通过一个完整示例,演示如何配置并验证降级行为。

验证自适应降级

在实现悬浮导航栏后,需要验证不同设备上的表现。可以借助以下两个场景来确认降级逻辑是否生效:

  1. 检查光效类型 :在支持沉浸光感的设备(如最新麒麟旗舰)上,systemMaterialEffect 配置为 IMMERSIVE 时,点击 Tab 应有明显的光晕扩散动画。
  2. 观察降级状态 :在不支持沉浸光感的设备(比如中低端麒麟芯片)上,光效应自动回退为普通模糊(blurStyle),且界面保持流畅,无掉帧。

验证步骤

  • 安装应用后,在高端设备上点击 Tab,查看光晕效果是否明显。

    截图: 高端设备上点击Tab时光晕扩散的瞬间

  • 在中低端设备上运行,观察底部导航栏是否有卡顿。正常情况系统会自动降级为普通毛玻璃。

预期输出

启动应用后,底部呈现一个圆角矩形、半透明模糊的悬浮导航栏。点击 Tab 时产生动态光效(支持设备)或静态模糊(降级设备)。内容区域完全延伸到导航栏下方,无白色截断线,视觉上类似前几年流行的"流体导航栏"但质感更佳。

常见问题与避坑指南

问题1:设置了 barOverlap(true) 但内容仍然被 Tab 栏遮挡

现象:内容区域并未延伸到 Tab 栏下方,Tab 栏依然占据固定的背景位置。

原因 :要么内容容器(如 Scroll、Grid)未设置 padding({ bottom: 高度 }),要么 layoutWeight(1) 未正确计算剩余空间。Tab 栏虽然悬浮,但内容若未预留底部空间,背景色会被覆盖。

解决方案 :给包含内容的父组件设置 layoutWeight(1),并在底部留出足够 padding,通常比 Tab 栏高度多 8--16px 以避免阴影被裁切。

typescript 复制代码
Scroll() {
  // ... 内容
}
.width('100%')
.layoutWeight(1)
.padding({ bottom: 96 })  // 预留空间:Tab栏高度72 + 额外阴影空间24

注意:padding 的值需要根据 Tab 栏的实际高度和阴影扩散范围调整。如果阴影不明显,可以适当增加上方的 margin 或改用 position: absolute 控制 Tab 栏的位置。

问题2:沉浸光感无效,仍然是普通毛玻璃

现象systemMaterialEffect 已配置为 IMMERSIVE,但点击时无光晕反馈。

原因 :设备不支持(API 级别低于 23,或者机型硬件不支持实时光照渲染);或者 MaterialLevel 设置过低(如 BALANCED 在某些设备上可能弱化效果)。

解决方案 :使用 ADAPTIVE 类型让系统自动决策;检查设备 API 版本;尝试升级 MaterialLevelEXQUISITE

typescript 复制代码
// 升级为EXQUISITE级别
systemMaterialEffect: {
  type: MaterialType.IMMERSIVE,
  level: MaterialLevel.EXQUISITE
}

注意:强制使用 IMMERSIVE 前应先通过 systemCapability 检测设备是否支持实时光照渲染,或在调用后捕获异常,避免在低端机型上造成性能问题。

问题3:Tab 图标颜色在浅色内容下看不清(未选中状态)

现象:当 Scroll 中背景为浅色(如白色)时,白色未选中图标几乎不可见。

原因:未选中颜色固定为白色,但内容背景有时较亮。

解决方案 :利用 BottomTabBarStyle 的构造参数动态设置未选中颜色,或者统一使用深色图标并搭配浅色模态背景。更优雅的做法是根据内容亮度动态切换:

typescript 复制代码
// 在亮度检测后动态切换颜色(伪代码)
const unselectedColor = isLightBackground ? '#333333' : '#FFFFFF';

new BottomTabBarStyle(icon, title, { color: unselectedColor }, { color: '#FF007AFF' })

一个侧面参考:开发者 Maybe 在《左右相册》中始终用白色图标,因为其内容以深色照片为主,且悬浮 Tab 栏自带模糊材质,白色对比度足够。建议根据自身业务场景微调。

总结与展望

悬浮页签通过 HdsTabs + barOverlap(true) + barFloatingStyle 实现,让内容不受导航栏切割,真正实现全面屏沉浸。沉浸光感通过 systemMaterialEffect 属性配置,可选用 ADAPTIVE(自适应)或 IMMERSIVE(强制),配合 EXQUISITE 级别获得顶级光影反馈。降级兜底:始终保留 blurStyle 作为后备方案,保证在不支持沉浸光感的设备上视觉仍可接受。设备能力检测:强制使用 IMMERSIVE 前应先检测设备支持性,避免性能问题。自定义图标颜色:利用 BottomTabBarStyle 的构造参数分别设置选中/未选中颜色,增强可读性。

你在实现悬浮导航栏时遇到过哪些兼容性问题?欢迎在评论区分享你的降级策略。

降级兜底与设备能力检测

始终保留 blurStyle 作为后备方案,保证在不支持沉浸光感的设备上视觉仍可接受。强制使用 IMMERSIVE 前应先检测设备支持性(可通过 systemCapability 或直接捕获异常),避免性能问题。自定义图标颜色时,利用 BottomTabBarStyle 的构造参数分别设置选中/未选中颜色,增强可读性。

适用场景

相册、视频播放器、音乐应用、社交信息流等需要强视觉沉浸感的场景。这两个特性可以单独使用,也可以组合------例如在导航栏上叠加沉浸光感(如开发者Jack的"悬浮导航+沉浸光感"实践),或者在按钮上局部应用(如开发者SummerKaze的 HdsButton 封装)。

相关推荐
JOJO数据科学2 小时前
JupyterLab Electron 鸿蒙 PC 适配全记录:从 Python 原生崩溃到 node-static 本地工作台
python·electron·harmonyos
CHB3 小时前
HDC2026 演讲实录|AI 驱动的跨端进化:利用 uni-agent 快速构建高性能鸿蒙应用
uni-app·harmonyos
祭曦念4 小时前
【共创季稿事节】鸿蒙ArkTS布局实战_Column交叉轴对齐
华为·harmonyos
古德new5 小时前
鸿蒙PC迁移:Anki Qt 记忆卡片工具鸿蒙PC适配全记录
qt·华为·harmonyos
TMT星球7 小时前
创梦天地《地铁跑酷》携手鸿蒙 深化全场景生态共建
华为·harmonyos
枫叶丹47 小时前
【HarmonyOS 6.0】MDM Kit 新特性:PC/2in1设备无锁屏密码重启自动解锁能力详解
开发语言·华为·harmonyos
Davina_yu7 小时前
数据持久化(2):RelationalStore关系型数据库(SQLite)操作(14)
harmonyos·鸿蒙·鸿蒙系统
不良使7 小时前
鸿蒙PC迁移:使用Electron`logseq-master-ohos` 鸿蒙适配全记录
jvm·electron·harmonyos
枫叶丹48 小时前
【HarmonyOS 6.0】MDM Kit:PC/2in1设备用户行为限制策略详解
开发语言·华为·harmonyos