《导航栏背景变色》二、沉浸光感导航栏变色案例指南

HarmonyOS 沉浸导航栏变色案例:从取色到全屏动态主题

效果

一、案例概述

本案例实现了一个全屏动态变色导航栏——当用户滑动轮播 Banner 时,系统通过 @ohos.effectKit 自动提取当前图片的主色调,并将该颜色同步应用到:

  • 顶部状态栏背景(实时变色)
  • 底部导航栏背景(毛玻璃质感 + 变色)
  • Banner 下方渐变过渡条(主色→白色渐变)
  • 切换指示器颜色(随主色动态变化)

实现图片内容与界面色调的深度融合,打造沉浸式视觉体验。

效果链路

Banner 切换 → effectKit 取色 → ThemeModel 更新 →
├─ 顶部状态栏:backgroundColor(dominantColor)
├─ 渐变过渡条:dominantColor → white
├─ 底部导航栏:navBarColor + 毛玻璃
└─ 指示器/图标:dominantColor

技术栈

技术说明
ArkTS + ArkUI声明式 UI 框架
@ohos.effectKitColorPicker 图像主色调提取
State Management V2@ObservedV2 + @Trace + @Local
Scroll + Grid滚动 + 网格布局组合
backgroundBlurStyle毛玻璃效果

二、项目结构

entry/src/main/ets/
├── model/
│   └── ThemeModel.ets          # V2 可观察颜色主题模型
├── constants/
│   └── StyleConstants.ets      # 全局样式常量
├── pages/
│   └── Index.ets               # 主页面(@ComponentV2 + @Entry)
└── entryability/
    └── EntryAbility.ets        # 应用入口,全屏 + 避让区域

三、核心实现详解

3.1 颜色主题模型(ThemeModel)

@ObservedV2
export class ColorThemeModel {
  @Trace dominantColor: string = '#7C4DFF';       // 提取的主色调(全色)
  @Trace isDark: boolean = true;                   // 颜色明暗判断
  @Trace navBarColor: string = '#D07C4DFF';        // 导航栏背景(90%透明度)
  @Trace ambientGlowColor: string = '#267C4DFF';   // 环境光晕(35%透明度)

  updateFromColor(color: string): void {
    this.dominantColor = color;
    this.isDark = this.calcBrightness(color) < 160;
    this.navBarColor = ColorThemeModel.hexWithAlpha(color, 0.90);
    this.ambientGlowColor = ColorThemeModel.hexWithAlpha(color, 0.35);
  }

  /** 亮度计算:ITU-R BT.601 加权公式 */
  private calcBrightness(hex: string): number {
    const r = parseInt(hex.substring(1, 3), 16);
    const g = parseInt(hex.substring(3, 5), 16);
    const b = parseInt(hex.substring(5, 7), 16);
    return (r * 299 + g * 587 + b * 114) / 1000;
  }

  /** 为十六进制颜色添加 Alpha 透明度 */
  static hexWithAlpha(hex: string, alpha: number): string {
    const a = Math.round(alpha * 255).toString(16).padStart(2, '0');
    return '#' + a + hex.substring(1);
  }
}

关键参数navBarColor 使用 90% 透明度,在白色背景下既能清晰显色又保留毛玻璃透感;ambientGlowColor 使用 35% 透明度,用于环境光晕。

3.2 effectKit 取色流水线

private async extractColor(resId: number): Promise<void> {
  // 1. 获取 ResourceManager
  const ctx = this.getUIContext().getHostContext() as Context;
  const resMgr: resourceManager.ResourceManager = ctx.resourceManager;

  // 2. 图片二进制 → ImageSource → PixelMap
  const fileData: Uint8Array = resMgr.getMediaContentSync(resId);
  const imgSrc: image.ImageSource = image.createImageSource(fileData.buffer);
  const pixelMap: image.PixelMap = await imgSrc.createPixelMap();

  // 3. 创建 ColorPicker → 提取主色
  effectKit.createColorPicker(pixelMap, (_err: BusinessError, colorPicker) => {
    const color = colorPicker.getMainColorSync();

    // 4. Color → #AARRGGBB
    const hexColor = '#' +
      color.alpha.toString(16).padStart(2, '0') +
      color.red.toString(16).padStart(2, '0') +
      color.green.toString(16).padStart(2, '0') +
      color.blue.toString(16).padStart(2, '0');

    // 5. 更新模型 → 自动触发 UI 刷新
    this.themeModel.updateFromColor(hexColor);
  });
}

⚠️ 重要陷阱toString(16) 后必须使用 padStart(2, '0'),否则小于 16 的分量值(如 alpha=10)会输出单字符 "a" 而非 "0a",导致颜色格式错误。

3.3 白色背景布局架构

与常见的暗色沉浸方案不同,本案例采用白色背景,让变色效果更加清晰直观:

build() {
  Column() {
    // 顶部状态栏 - 随主色变色
    Row()
      .height(this.topRectHeight + 4)
      .width('100%')
      .backgroundColor(this.themeModel.dominantColor)  // ← 核心变色点

    Scroll() {
      Column() {
        // Banner 轮播
        Swiper() { ... }

        // 渐变过渡条:主色 → 白色(取色效果直观展示)
        Row()
          .height(40).width('100%')
          .linearGradient({
            colors: [[this.themeModel.dominantColor, 0.0], ['#FFFFFF', 1.0]]
          })

        // 标题区 + 内容网格
        Row() { Text('灵感精选')... }
        Grid() { ... }
      }
    }
    .layoutWeight(1)

    // 底部导航栏 - 毛玻璃变色
    Row() { this.buildTabItem(...)... }
      .backgroundColor(this.themeModel.navBarColor)
      .backgroundBlurStyle(BlurStyle.Thin)
  }
  .backgroundColor('#FFFFFF')  // 白色基底
}

布局演进说明

  • 最初采用 Stack 双层叠加 + 暗色背景 + 环境光晕,但深色基底压制了变色效果
  • 最终改为纯 Column 布局 + 白色背景,利用「白色画布」让颜色变化一目了然
  • 状态栏和导航栏使用 dominantColor / navBarColor 直接显色,而非半透明叠加

3.4 顶部状态栏变色

// 状态栏高度从 AppStorage 读取,避免全屏模式下被系统栏遮挡
aboutToAppear(): void {
  this.topRectHeight = AppStorage.get<number>('topRectHeight') ?? 0;
  this.bottomRectHeight = AppStorage.get<number>('bottomRectHeight') ?? 0;
  this.extractColor(this.banners[0].id);
}

EntryAbility 中通过 setWindowLayoutFullScreen(true) 启用全屏,并获取避让区域:

windowClass.setWindowLayoutFullScreen(true);

const uiContext = windowClass.getUIContext();
const topRectHeight = uiContext.px2vp(avoidArea.topRect.height);
AppStorage.setOrCreate('topRectHeight', topRectHeight);

// 注册动态监听
windowClass.on('avoidAreaChange', (data) => {
  if (data.type === window.AvoidAreaType.TYPE_SYSTEM) {
    AppStorage.setOrCreate('topRectHeight', uiContext.px2vp(data.area.topRect.height));
  }
});

3.5 底部导航栏毛玻璃变色

// 底部导航栏
Row() {
  this.buildTabItem(0, $r('app.media.home'), '首页')
  this.buildTabItem(1, $r('app.media.comments_selected'), '发现')
  this.buildTabItem(2, $r('app.media.mine_selected'), '我的')
}
.width('100%')
.height(64)
.backgroundColor(this.themeModel.navBarColor)   // 90%透明度主色
.backgroundBlurStyle(BlurStyle.Thin)            // 毛玻璃
.padding({ bottom: this.bottomRectHeight + 4 }) // 避开系统导航条

Tab 项图标和文字自适应:

@Builder
buildTabItem(index: number, icon: Resource, label: string) {
  Column() {
    Image(icon)
      .width(24).height(24)
      .fillColor(this.currentTab === index
        ? this.themeModel.dominantColor    // 选中色 = 主色
        : '#99000000')                     // 未选中 = 半透明黑
    Text(label)
      .fontSize(11)
      .fontColor(this.currentTab === index
        ? Color.Black
        : '#66000000')
  }
}

3.6 内容卡片风格

白色卡片 + 阴影,适配白色背景:

GridItem() {
  Column() {
    Image(item.img).width('100%').height(90).objectFit(ImageFit.Contain)
    Column() {
      Text(item.title).fontColor('#1A1A2E')
      Text(item.desc).fontColor('#999999')
    }.padding(10)
  }
  .height(150).borderRadius(16)
  .backgroundColor(Color.White)           // 纯白背景
  .shadow({                               // 投影增加层次感
    radius: 8, color: '#1A000000',
    offsetX: 0, offsetY: 2
  })
}

四、V2 状态管理要点

V1V2本案例用途
@State@Local主题模型引用、当前索引
@Observed@ObservedV2ColorThemeModel 类
@Track@Trace4 个颜色属性(dominantColor/isDark/navBarColor/ambientGlowColor)
@Builder@BuilderbuildTabItem 导航项构建
@Component@ComponentV2主页面结构体

五、编译常见问题

问题原因解决
Namespace 'window' has no exported member 'AvoidAreaEvent'类型不存在移除类型注解,让编译器自动推断 (data) =>
Identifier 'DOMAIN' has already been declared文件写入工具追加导致重复代码检查并删除重复的 importconst DOMAIN
"import" statements after other statements are not allowedimport 位置错误确保所有 import 在文件最顶部
状态栏避空失效,Banner 紧贴顶部未从 AppStorage 读取高度aboutToAppear 中调用 AppStorage.get()
导航栏变色不明显深色背景压制颜色改用白色背景 #FFFFFF + 显色参数

六、运行与调试

6.1 前提条件

  • DevEco Studio 6.1+ / HarmonyOS NEXT SDK API 23+
  • 真机或模拟器运行(建议真机以获得完整毛玻璃效果)

6.2 调试建议

  1. extractColor 中添加 hilog 输出 hexColor 值,验证取色是否正确
  2. 使用 DevEco Studio 的 Inspector 工具检查状态栏和导航栏的背景色
  3. 切换不同色系的 Banner 图片,观察颜色变化范围

七、扩展思路

扩展方向实现思路
颜色渐变动画使用 animateTo 包裹 updateFromColor() 调用
暗黑模式根据 isDark 自动切换页面背景色
多图片源扩展为网络图片 + 缓存 PixelMap
音乐可视化配合 AudioKit 提取专辑封面色调
动态壁纸将主色扩展到整个桌面级主题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值