HarmonyOS 6(API 23)实战:基于 HDS 沉浸光感与悬浮导航打造“光影工作台“多窗口协作系统

文章目录

    • 每日一句正能量
    • 前言
    • [一、沉浸光感与悬浮导航:HarmonyOS 6 的视觉革命](#一、沉浸光感与悬浮导航:HarmonyOS 6 的视觉革命)
      • [1.1 核心特性解析](#1.1 核心特性解析)
      • [1.2 技术架构对比](#1.2 技术架构对比)
    • 二、项目实战:"光影工作台"架构设计
      • [2.1 应用场景与功能规划](#2.1 应用场景与功能规划)
      • [2.2 技术架构图](#2.2 技术架构图)
    • 三、环境配置与模块依赖
      • [3.1 模块依赖配置](#3.1 模块依赖配置)
      • [3.2 窗口沉浸配置(EntryAbility.ets)](#3.2 窗口沉浸配置(EntryAbility.ets))
    • 四、核心组件实战
      • [4.1 沉浸光感标题栏(HdsNavigation + systemMaterialEffect)](#4.1 沉浸光感标题栏(HdsNavigation + systemMaterialEffect))
      • [4.2 悬浮页签导航(HdsTabs + 沉浸光感 + 自适应材质)](#4.2 悬浮页签导航(HdsTabs + 沉浸光感 + 自适应材质))
      • [4.3 多窗口光效同步管理器](#4.3 多窗口光效同步管理器)
      • [4.4 主页面:光效联动与自适应渲染](#4.4 主页面:光效联动与自适应渲染)
    • 五、关键技术总结
      • [5.1 沉浸光感实现清单](#5.1 沉浸光感实现清单)
      • [5.2 悬浮导航适配要点](#5.2 悬浮导航适配要点)
      • [5.3 PC 端多窗口光效协同](#5.3 PC 端多窗口光效协同)
    • 六、调试与性能优化
      • [6.1 真机调试建议](#6.1 真机调试建议)
      • [6.2 无障碍与降级适配](#6.2 无障碍与降级适配)
    • 七、总结与展望

每日一句正能量

人跟人之间的感情就像织毛衣,建立的时候一针一线,小心而漫长,拆除的时候只要轻轻一拉,卸载永远比安装快,失去永远比得到快。


前言

摘要 :HarmonyOS 6(API 23)在 @kit.UIDesignKit 中引入了 systemMaterialEffect(系统材质效果)能力,其中**沉浸光感(IMMERSIVE)**通过模拟真实物理光照模型,为标题栏和底部导航带来细腻的"光晕"与"反射"反馈。本文将基于 HDS 设计系统,实战开发一款面向 HarmonyOS PC 的"光影工作台"多窗口协作应用,展示悬浮页签、自适应材质与空间光效的深度融合。


一、沉浸光感与悬浮导航:HarmonyOS 6 的视觉革命

1.1 核心特性解析

HarmonyOS 6(API 23)带来了两大 UI 革新特性 :

沉浸光感(Immersive Light Effects)

  • 基于物理光照模型,组件内部模拟真实的光晕与反射效果
  • 支持 HdsNavigation (标题栏)和 HdsTabs(底部悬浮页签)两大核心组件
  • 交互时产生细腻的触控反馈,增强控件立体感

悬浮导航(Float Navigation)

  • 导航栏脱离底部边界,以"四周留白、圆角卡片"形态悬浮于内容之上
  • 支持 强(85%)、平衡(70%)、弱(55%) 三档透明度自定义
  • 与系统全面屏手势无缝衔接,内容区域智能避让

1.2 技术架构对比

特性 HarmonyOS 5.x HarmonyOS 6(API 23)
标题栏材质 纯色/简单模糊 systemMaterialEffect 物理光照模型
底部导航 固定 TabBar 悬浮卡片 + 沉浸光感
窗口管理 单窗口全屏 多自由窗口 + 光效同步
交互反馈 基础点击效果 光晕反射 + 微震动

二、项目实战:"光影工作台"架构设计

2.1 应用场景与功能规划

面向 HarmonyOS PC 的多窗口协作场景,核心功能包括:

  1. 悬浮导航工作台:底部悬浮页签支持"文档/表格/演示/白板"四大工作区切换
  2. 沉浸光感标题栏:根据当前工作区主题色动态变化光效,激活窗口边缘发光
  3. 多窗口协同:支持主窗口 + 浮动工具窗口(如计算器、便签)的光效联动
  4. 自适应材质:根据窗口焦点状态、鼠标位置、时间(日间/夜间)自动调整光感强度

2.2 技术架构图

复制代码
┌─────────────────────────────────────────────────────────┐
│              HDS Design System (UIDesignKit)              │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────┐   │
│  │ HdsNavigation│  │  HdsTabs    │  │ systemMaterial  │   │
│  │ (沉浸光感标题栏)│  │ (悬浮页签)   │  │   Effect        │   │
│  └──────┬──────┘  └──────┬──────┘  └─────────────────┘   │
└─────────┼────────────────┼─────────────────────────────────┘
          │                │
┌─────────▼────────────────▼─────────────────────────────┐
│              ArkUI 渲染层                                  │
│  ┌─────────────────┐      ┌─────────────────────────┐   │
│  │  主窗口 (Main)   │      │    浮动工具窗口          │   │
│  │  · 沉浸光效背景   │      │  · 跟随主窗口光效        │   │
│  │  · 自适应材质     │      │  · 置顶/阴影/圆角        │   │
│  │  · 鼠标追踪光效   │      │  · 边缘发光指示          │   │
│  └─────────────────┘      └─────────────────────────┘   │
└─────────────────────────────────────────────────────────┘

三、环境配置与模块依赖

3.1 模块依赖配置

oh-package.json5 中添加 HDS 设计系统与窗口管理依赖:

json 复制代码
{
  "dependencies": {
    "@kit.UIDesignKit": "^6.1.0",
    "@kit.ArkUI": "^6.1.0",
    "@kit.AbilityKit": "^6.1.0",
    "@kit.BasicServicesKit": "^6.1.0"
  }
}

3.2 窗口沉浸配置(EntryAbility.ets)

代码亮点:HarmonyOS PC 应用需配置自由窗口模式,启用窗口阴影与圆角,并设置透明背景以允许光效穿透。

typescript 复制代码
// entry/src/main/ets/entryability/EntryAbility.ets
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';

export default class EntryAbility extends UIAbility {
  private windowStage: window.WindowStage | null = null;

  onWindowStageCreate(windowStage: window.WindowStage): void {
    this.windowStage = windowStage;

    windowStage.loadContent('pages/Index', (err) => {
      if (err.code) {
        console.error('Failed to load content:', JSON.stringify(err));
        return;
      }
      console.info('Succeeded in loading content.');
      this.setupPCWindow(windowStage);
    });
  }

  /**
   * 配置PC端自由窗口模式
   * 关键设置:
   * 1. FREE窗口模式:支持自由缩放、移动
   * 2. 自定义标题栏:使用HdsNavigation替代系统标题栏
   * 3. 窗口阴影与圆角:增强视觉层次
   * 4. 透明背景:允许沉浸光效穿透至窗口边缘
   */
  private async setupPCWindow(windowStage: window.WindowStage): Promise<void> {
    try {
      const mainWindow = windowStage.getMainWindowSync();

      // 1. 设置为自由窗口模式(PC端核心特性)
      await mainWindow.setWindowSizeType(window.WindowSizeType.FREE);
      await mainWindow.setWindowMode(window.WindowMode.FULLSCREEN);

      // 2. 隐藏系统标题栏,使用HdsNavigation自定义
      await mainWindow.setWindowTitleBarEnable(false);

      // 3. 启用窗口阴影和圆角
      await mainWindow.setWindowShadowEnabled(true);
      await mainWindow.setWindowCornerRadius(12);

      // 4. 设置透明背景,允许光效穿透
      await mainWindow.setWindowBackgroundColor('#00000000');

      // 5. 配置系统栏属性
      await mainWindow.setWindowSystemBarProperties({
        statusBarColor: '#00000000',
        navigationBarColor: '#00000000',
        statusBarContentColor: '#FFFFFF',
        navigationBarContentColor: '#FFFFFF'
      });

      // 6. 启用安全区避让
      await mainWindow.setWindowAvoidAreaOption({
        type: window.AvoidAreaType.TYPE_SYSTEM,
        enabled: true
      });

      // 7. 保存窗口实例到全局,供子窗口管理使用
      AppStorage.setOrCreate('main_window', mainWindow);

      console.info('PC window setup completed');
    } catch (error) {
      console.error('Failed to setup PC window:', (error as BusinessError).message);
    }
  }

  onWindowStageDestroy(): void {
    this.windowStage = null;
  }
}

四、核心组件实战

4.1 沉浸光感标题栏(HdsNavigation + systemMaterialEffect)

代码亮点 :使用 @kit.UIDesignKitHdsNavigation 组件,通过 systemMaterialEffect 设置 IMMERSIVE 材质,实现物理光照模型的光晕与反射效果。标题栏根据当前工作区主题色动态变化,窗口激活时边缘发光增强。

typescript 复制代码
// components/ImmersiveTitleBar.ets
import { HdsNavigation, SystemMaterialEffect } from '@kit.UIDesignKit';
import { window } from '@kit.ArkUI';

/**
 * 工作区主题配置
 */
interface WorkspaceTheme {
  name: string;
  primaryColor: string;
  accentColor: string;
  icon: Resource;
}

@Component
export struct ImmersiveTitleBar {
  @Prop currentWorkspace: number;
  @State isWindowFocused: boolean = true;
  @State titleBarColor: string = '#4A90E2';
  
  // 四大工作区主题配置
  private workspaces: WorkspaceTheme[] = [
    { name: '文档', primaryColor: '#4A90E2', accentColor: '#5BA0F2', icon: $r('app.media.ic_doc') },
    { name: '表格', primaryColor: '#00C853', accentColor: '#00E676', icon: $r('app.media.ic_sheet') },
    { name: '演示', primaryColor: '#FF6D00', accentColor: '#FF9100', icon: $r('app.media.ic_slide') },
    { name: '白板', primaryColor: '#AA00FF', accentColor: '#E040FB', icon: $r('app.media.ic_board') }
  ];

  aboutToAppear(): void {
    this.monitorWindowFocus();
    this.updateThemeColor();
  }

  /**
   * 监听窗口焦点变化,调整光效强度
   * 激活窗口:光效增强(边缘发光)
   * 非激活窗口:光效减弱(降低干扰)
   */
  private async monitorWindowFocus(): Promise<void> {
    try {
      const mainWindow = await window.getLastWindow();
      mainWindow.on('windowFocusChange', (isFocused: boolean) => {
        this.isWindowFocused = isFocused;
        // 焦点变化时触发光效脉冲
        if (isFocused) {
          this.triggerLightPulse();
        }
      });
    } catch (error) {
      console.error('Failed to monitor window focus:', error);
    }
  }

  /**
   * 根据当前工作区更新主题色
   */
  private updateThemeColor(): void {
    this.titleBarColor = this.workspaces[this.currentWorkspace].primaryColor;
    // 同步到全局,供其他组件使用
    AppStorage.setOrCreate('global_theme_color', this.titleBarColor);
  }

  /**
   * 触发光效脉冲(窗口获得焦点时的视觉反馈)
   */
  private triggerLightPulse(): void {
    // 通过状态变化触发ArkUI动画
    const originalColor = this.titleBarColor;
    this.titleBarColor = this.workspaces[this.currentWorkspace].accentColor;
    setTimeout(() => {
      this.titleBarColor = originalColor;
    }, 300);
  }

  build() {
    Column() {
      // 顶部边缘光晕(窗口激活状态指示)
      if (this.isWindowFocused) {
        Column()
          .width('100%')
          .height(2)
          .backgroundColor(this.titleBarColor)
          .opacity(0.8)
          .shadow({
            radius: 10,
            color: this.titleBarColor,
            offsetX: 0,
            offsetY: 2
          })
      }

      // HdsNavigation 沉浸光感标题栏
      HdsNavigation({
        title: this.workspaces[this.currentWorkspace].name,
        titleColor: '#FFFFFF',
        // 核心:设置系统材质效果为 IMMERSIVE(沉浸光感)
        systemMaterialEffect: SystemMaterialEffect.IMMERSIVE,
        // 背景色使用主题色+透明度,配合材质效果产生光晕
        backgroundColor: `${this.titleBarColor}${this.isWindowFocused ? 'E6' : '99'}`, // E6=90%, 99=60%
        // 左侧操作区
        leading: this.buildLeading(),
        // 右侧操作区
        actions: this.buildActions()
      })
        .width('100%')
        .height(56)
        // 关键:扩展至安全区顶部(状态栏区域)
        .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP])
        // 背景模糊增强光感层次
        .backgroundBlurStyle(BlurStyle.REGULAR)
        // 底部微光分割线
        .border({
          width: { bottom: 1 },
          color: 'rgba(255,255,255,0.1)',
          style: BorderStyle.Solid
        })

      // 光效反射层(模拟物理光照的底部反射)
      Column()
        .width('100%')
        .height(20)
        .backgroundColor(this.titleBarColor)
        .opacity(this.isWindowFocused ? 0.05 : 0.02)
        .blur(20)
        .linearGradient({
          direction: GradientDirection.Top,
          colors: [
            [this.titleBarColor, 0.0],
            ['transparent', 1.0]
          ]
        })
    }
    .width('100%')
  }

  @Builder
  buildLeading(): void {
    Row({ space: 12 }) {
      // 工作区图标
      Image(this.workspaces[this.currentWorkspace].icon)
        .width(24)
        .height(24)
        .fillColor('#FFFFFF')
      
      // 窗口控制按钮(PC端特色)
      Row({ space: 8 }) {
        Button() { Text('−').fontColor('#FFFFFF').fontSize(14) }
          .type(ButtonType.Circle)
          .backgroundColor('rgba(255,255,255,0.1)')
          .width(24)
          .height(24)
          .onClick(() => this.minimizeWindow())

        Button() { Text('□').fontColor('#FFFFFF').fontSize(10) }
          .type(ButtonType.Circle)
          .backgroundColor('rgba(255,255,255,0.1)')
          .width(24)
          .height(24)
          .onClick(() => this.maximizeWindow())

        Button() { Text('×').fontColor('#FFFFFF').fontSize(14) }
          .type(ButtonType.Circle)
          .backgroundColor('rgba(255,255,255,0.1)')
          .width(24)
          .height(24)
          .onClick(() => this.closeWindow())
      }
    }
  }

  @Builder
  buildActions(): void {
    Row({ space: 16 }) {
      // 搜索按钮(带光效反馈)
      Button() {
        Image($r('app.media.ic_search'))
          .width(20)
          .height(20)
          .fillColor('#FFFFFF')
      }
      .type(ButtonType.Circle)
      .backgroundColor('transparent')
      .width(36)
      .height(36)
      // 点击时的光效反馈
      .stateStyles({
        pressed: {
          .backgroundColor('rgba(255,255,255,0.15)')
          .shadow({ radius: 8, color: 'rgba(255,255,255,0.3)' })
        }
      })

      // 协作状态指示
      Stack() {
        Image($r('app.media.ic_collab'))
          .width(20)
          .height(20)
          .fillColor('#FFFFFF')
        
        // 在线状态光点
        Column()
          .width(8)
          .height(8)
          .backgroundColor('#00E676')
          .borderRadius(4)
          .position({ x: 14, y: -2 })
          .shadow({ radius: 4, color: '#00E676' })
      }

      // 用户头像(带沉浸光感边框)
      Stack() {
        Image($r('app.media.avatar_user'))
          .width(32)
          .height(32)
          .borderRadius(16)
        
        // 头像光晕
        Column()
          .width(36)
          .height(36)
          .backgroundColor(this.titleBarColor)
          .borderRadius(18)
          .opacity(0.3)
          .blur(4)
          .position({ x: -2, y: -2 })
      }
    }
  }

  // 窗口控制方法
  private async minimizeWindow(): Promise<void> {
    const win = await window.getLastWindow();
    await win.minimize();
  }

  private async maximizeWindow(): Promise<void> {
    const win = await window.getLastWindow();
    const isMax = win.getWindowProperties().isMaximize;
    if (isMax) {
      await win.recover();
    } else {
      await win.maximize();
    }
  }

  private async closeWindow(): Promise<void> {
    const win = await window.getLastWindow();
    await win.close();
  }
}

4.2 悬浮页签导航(HdsTabs + 沉浸光感 + 自适应材质)

代码亮点 :使用 HdsTabs 组件构建底部悬浮页签,通过 systemMaterialEffect 实现沉浸光感。页签支持"强/平衡/弱"三档透明度调节,根据窗口焦点状态和鼠标位置自适应调整光效强度。

typescript 复制代码
// components/FloatTabNavigation.ets
import { HdsTabs, HdsTabBar, SystemMaterialEffect } from '@kit.UIDesignKit';
import { window } from '@kit.ArkUI';

/**
 * 透明度档位枚举
 */
export enum TransparencyLevel {
  STRONG = 0.85,    // 强效果:高透明度,玻璃感明显
  BALANCED = 0.70,  // 平衡效果:适中透明度
  WEAK = 0.55       // 弱效果:低透明度,更清晰
}

/**
 * 页签配置
 */
interface TabConfig {
  label: string;
  icon: Resource;
  iconActive: Resource;
  badge?: number;
}

@Component
export struct FloatTabNavigation {
  @Prop currentIndex: number;
  @Prop onTabChange: (index: number) => void;
  
  @State navTransparency: number = TransparencyLevel.BALANCED;
  @State isExpanded: boolean = false;  // 是否展开透明度调节面板
  @State isWindowFocused: boolean = true;
  @State mouseY: number = 0;  // 鼠标Y坐标,用于光效追踪
  @State bottomAvoidHeight: number = 0;

  // 四大工作区页签配置
  private tabs: TabConfig[] = [
    { label: '文档', icon: $r('app.media.ic_doc'), iconActive: $r('app.media.ic_doc_filled'), badge: 3 },
    { label: '表格', icon: $r('app.media.ic_sheet'), iconActive: $r('app.media.ic_sheet_filled') },
    { label: '演示', icon: $r('app.media.ic_slide'), iconActive: $r('app.media.ic_slide_filled'), badge: 1 },
    { label: '白板', icon: $r('app.media.ic_board'), iconActive: $r('app.media.ic_board_filled') }
  ];

  aboutToAppear(): void {
    this.getBottomAvoidArea();
    this.monitorWindowFocus();
  }

  /**
   * 获取底部安全区高度(导航栏避让)
   * HarmonyOS 6支持多种避让类型:TYPE_SYSTEM, TYPE_NAVIGATION_INDICATOR等
   */
  private async getBottomAvoidArea(): Promise<void> {
    try {
      const mainWindow = await window.getLastWindow();
      const avoidArea = mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR);
      this.bottomAvoidHeight = avoidArea.bottomRect.height;
      // 保存到全局供其他组件使用
      AppStorage.setOrCreate('bottom_avoid_height', this.bottomAvoidHeight);
    } catch (error) {
      console.error('Failed to get avoid area:', error);
    }
  }

  private async monitorWindowFocus(): Promise<void> {
    const mainWindow = await window.getLastWindow();
    mainWindow.on('windowFocusChange', (isFocused: boolean) => {
      this.isWindowFocused = isFocused;
    });
  }

  build() {
    Stack({ alignContent: Alignment.Bottom }) {
      // 内容区域(由父组件传入)
      Column() {
        this.contentBuilder()
      }
      .width('100%')
      .height('100%')
      // 关键:底部留出安全区+导航栏高度,避免内容被遮挡
      .padding({ bottom: this.bottomAvoidHeight + 100 })

      // 悬浮页签容器
      Column() {
        // 玻璃拟态背景层(多层叠加实现沉浸光感)
        Stack() {
          // 底层:系统级毛玻璃效果
          Column()
            .width('100%')
            .height('100%')
            .backgroundBlurStyle(BlurStyle.REGULAR)
            .opacity(this.navTransparency)
            .backdropBlur($r('sys.blur.20'))

          // 中层:主题色微光晕染
          Column()
            .width('100%')
            .height('100%')
            .backgroundColor(this.getThemeColor())
            .opacity(this.isWindowFocused ? 0.08 : 0.03)
            .blur(40)

          // 顶层:渐变高光(模拟物理光照的顶部反射)
          Column()
            .width('100%')
            .height('100%')
            .linearGradient({
              direction: GradientDirection.Top,
              colors: [
                ['rgba(255,255,255,0.15)', 0.0],
                ['rgba(255,255,255,0.05)', 0.5],
                ['rgba(255,255,255,0.0)', 1.0]
              ]
            })
        }
        .width('100%')
        .height('100%')
        .borderRadius(24)  // 圆角悬浮卡片
        .shadow({
          radius: 20,
          color: 'rgba(0,0,0,0.15)',
          offsetX: 0,
          offsetY: -4
        })
        // 鼠标悬停时增强光效(PC端特色)
        .onHover((isHover: boolean) => {
          this.navTransparency = isHover 
            ? Math.min(this.navTransparency + 0.1, TransparencyLevel.STRONG)
            : TransparencyLevel.BALANCED;
        })

        // HdsTabs 沉浸光感页签
        HdsTabs({
          index: this.currentIndex,
          barPosition: BarPosition.Bottom,
          // 核心:启用沉浸光感材质
          tabBarStyle: {
            systemMaterialEffect: SystemMaterialEffect.IMMERSIVE,
            // 选中指示器光效
            indicatorColor: this.getThemeColor(),
            indicatorHeight: 3,
            indicatorWidth: 24,
            // 文字样式
            labelColor: '#999999',
            activeLabelColor: '#FFFFFF',
            labelFontSize: 12,
            activeLabelFontSize: 13,
            // 图标样式
            iconSize: 24,
            activeIconColor: '#FFFFFF',
            inactiveIconColor: '#999999'
          }
        }) {
          // 页签内容通过ForEach动态生成
          ForEach(this.tabs, (tab: TabConfig, index: number) => {
            TabContent() {
              // 内容由外部传入
            }
            .tabBar(this.buildTabBarItem(tab, index))
          })
        }
        .width('100%')
        .height(80)
        .onChange((index: number) => {
          this.onTabChange(index);
          this.triggerTabSwitchFeedback(index);
        })

        // 透明度调节面板(长按展开)
        if (this.isExpanded) {
          this.buildTransparencyPanel()
        }
      }
      .width('92%')  // 左右留白,形成悬浮效果
      .height(this.isExpanded ? 130 : 80)
      .margin({ 
        bottom: this.bottomAvoidHeight + 16, 
        left: '4%', 
        right: '4%' 
      })
      .animation({
        duration: 300,
        curve: Curve.Spring,
        iterations: 1
      })
      // 长按手势:展开/收起透明度调节
      .gesture(
        LongPressGesture({ duration: 500 })
          .onAction(() => {
            this.isExpanded = !this.isExpanded;
          })
      )
    }
    .width('100%')
    .height('100%')
  }

  /**
   * 构建单个页签项(带徽章和光效)
   */
  @Builder
  buildTabBarItem(tab: TabConfig, index: number): void {
    Column() {
      Stack() {
        // 图标
        Image(this.currentIndex === index ? tab.iconActive : tab.icon)
          .width(24)
          .height(24)
          .fillColor(this.currentIndex === index ? '#FFFFFF' : '#999999')
        
        // 徽章红点
        if (tab.badge && tab.badge > 0) {
          Column() {
            Text(`${tab.badge}`)
              .fontSize(10)
              .fontColor('#FFFFFF')
          }
          .width(16)
          .height(16)
          .backgroundColor('#FF4444')
          .borderRadius(8)
          .position({ x: 14, y: -4 })
          .shadow({ radius: 4, color: '#FF4444' })
        }

        // 选中状态光晕
        if (this.currentIndex === index) {
          Column()
            .width(48)
            .height(48)
            .backgroundColor(this.getThemeColor())
            .borderRadius(24)
            .opacity(0.2)
            .blur(12)
            .position({ x: -12, y: -12 })
        }
      }
      .width(48)
      .height(48)

      Text(tab.label)
        .fontSize(11)
        .fontColor(this.currentIndex === index ? '#FFFFFF' : '#999999')
        .margin({ top: 2 })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }

  /**
   * 透明度调节面板
   */
  @Builder
  buildTransparencyPanel(): void {
    Row() {
      Text('光感强度')
        .fontSize(12)
        .fontColor('rgba(255,255,255,0.7)')
        .margin({ right: 12 })

      // 三档预设按钮
      Row({ space: 8 }) {
        Button('强')
          .type(ButtonType.Capsule)
          .fontSize(11)
          .backgroundColor(this.navTransparency === TransparencyLevel.STRONG ? this.getThemeColor() : 'rgba(255,255,255,0.1)')
          .onClick(() => this.navTransparency = TransparencyLevel.STRONG)

        Button('平衡')
          .type(ButtonType.Capsule)
          .fontSize(11)
          .backgroundColor(this.navTransparency === TransparencyLevel.BALANCED ? this.getThemeColor() : 'rgba(255,255,255,0.1)')
          .onClick(() => this.navTransparency = TransparencyLevel.BALANCED)

        Button('弱')
          .type(ButtonType.Capsule)
          .fontSize(11)
          .backgroundColor(this.navTransparency === TransparencyLevel.WEAK ? this.getThemeColor() : 'rgba(255,255,255,0.1)')
          .onClick(() => this.navTransparency = TransparencyLevel.WEAK)
      }

      // 滑块微调
      Slider({
        value: this.navTransparency * 100,
        min: 30,
        max: 95,
        step: 5,
        style: SliderStyle.InSet
      })
        .width(100)
        .selectedColor(this.getThemeColor())
        .onChange((value: number) => {
          this.navTransparency = value / 100;
        })
    }
    .width('100%')
    .height(50)
    .justifyContent(FlexAlign.Center)
    .backgroundColor('rgba(255,255,255,0.05)')
    .borderRadius({ topLeft: 16, topRight: 16 })
  }

  /**
   * 获取当前主题色(从全局状态读取)
   */
  private getThemeColor(): string {
    return AppStorage.get<string>('global_theme_color') || '#4A90E2';
  }

  /**
   * 页签切换反馈(光效脉冲 + 微震动)
   */
  private triggerTabSwitchFeedback(index: number): void {
    // 光效脉冲
    const themeColor = this.workspaces?.[index]?.primaryColor || '#4A90E2';
    AppStorage.setOrCreate('global_theme_color', themeColor);

    // 微震动反馈
    try {
      import('@kit.SensorServiceKit').then(sensor => {
        sensor.vibrator.startVibration({
          type: 'time',
          duration: 30
        }, { id: 0 });
      });
    } catch (error) {
      console.error('Haptic feedback failed:', error);
    }
  }

  // 内容构建器(由外部传入)
  @BuilderParam contentBuilder: () => void = this.defaultContentBuilder;

  @Builder
  defaultContentBuilder(): void {
    Column() {
      Text('工作区内容')
        .fontSize(16)
        .fontColor('#999999')
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

4.3 多窗口光效同步管理器

代码亮点 :HarmonyOS PC 支持多自由窗口,通过 WindowManager 实现主窗口与浮动工具窗口间的光效同步。浮动窗口跟随主窗口移动,激活时边缘发光,失活时自动降低光效强度 。

typescript 复制代码
// utils/WindowManager.ets
import { window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';

export interface ToolWindowConfig {
  name: string;
  title: string;
  width: number;
  height: number;
  x?: number;
  y?: number;
  followMainWindow?: boolean;  // 是否跟随主窗口移动
}

export class WindowManager {
  private static instance: WindowManager;
  private mainWindow: window.Window | null = null;
  private subWindows: Map<string, window.Window> = new Map();

  static getInstance(): WindowManager {
    if (!WindowManager.instance) {
      WindowManager.instance = new WindowManager();
    }
    return WindowManager.instance;
  }

  /**
   * 初始化主窗口(PC自由窗口模式)
   */
  async initializeMainWindow(windowStage: window.WindowStage): Promise<void> {
    this.mainWindow = windowStage.getMainWindowSync();

    // 配置为PC自由窗口
    await this.mainWindow.setWindowSizeType(window.WindowSizeType.FREE);
    await this.mainWindow.setWindowMode(window.WindowMode.FULLSCREEN);
    await this.mainWindow.setWindowTitleBarEnable(false);
    await this.mainWindow.setWindowShadowEnabled(true);
    await this.mainWindow.setWindowCornerRadius(12);
    await this.mainWindow.setWindowBackgroundColor('#00000000');

    console.info('Main window initialized for PC immersive mode');
  }

  /**
   * 创建浮动工具窗口(如计算器、便签)
   * 特性:置顶、圆角、阴影、跟随主窗口、光效同步
   */
  async createToolWindow(config: ToolWindowConfig): Promise<window.Window | null> {
    try {
      if (!this.mainWindow) {
        throw new Error('Main window not initialized');
      }

      const subWindow = await this.mainWindow.createSubWindow(config.name);

      // 配置浮动窗口属性
      await subWindow.setWindowSizeType(window.WindowSizeType.FREE);
      await subWindow.moveWindowTo({ x: config.x ?? 100, y: config.y ?? 100 });
      await subWindow.resize(config.width, config.height);
      await subWindow.setWindowBackgroundColor('#00000000');
      await subWindow.setWindowShadowEnabled(true);
      await subWindow.setWindowCornerRadius(16);
      await subWindow.setWindowTopmost(true);  // 置顶

      // 存储并加载内容
      this.subWindows.set(config.name, subWindow);
      await subWindow.setUIContent(`pages/${config.name}`);
      await subWindow.showWindow();

      // 如果配置跟随,设置窗口联动
      if (config.followMainWindow) {
        this.setupWindowFollow(subWindow, config);
      }

      // 监听子窗口焦点,同步光效
      this.syncSubWindowLightEffect(subWindow, config.name);

      console.info(`Tool window created: ${config.name}`);
      return subWindow;
    } catch (error) {
      console.error(`Failed to create tool window:`, (error as BusinessError).message);
      return null;
    }
  }

  /**
   * 设置窗口跟随(主窗口移动时子窗口跟随)
   */
  private setupWindowFollow(subWindow: window.Window, config: ToolWindowConfig): void {
    this.mainWindow?.on('windowRectChange', (data: window.RectChangeOptions) => {
      if (data.rectChangeReason === window.RectChangeReason.MOVE) {
        const mainRect = this.mainWindow?.getWindowProperties().windowRect;
        if (mainRect) {
          subWindow.moveWindowTo({
            x: mainRect.left + (config.x ?? 100),
            y: mainRect.top + (config.y ?? 100)
          });
        }
      }
    });
  }

  /**
   * 同步子窗口光效(与主窗口主题色联动)
   */
  private syncSubWindowLightEffect(subWindow: window.Window, name: string): void {
    subWindow.on('windowFocusChange', (isFocused: boolean) => {
      // 通过AppStorage同步光效状态
      AppStorage.setOrCreate(`window_${name}_focused`, isFocused);
      
      // 子窗口激活时触发脉冲光效
      if (isFocused) {
        AppStorage.setOrCreate('global_light_pulse', Date.now());
      }
    });
  }

  /**
   * 全局光效同步(主题色变化时通知所有窗口)
   */
  async syncGlobalLightEffect(color: string): Promise<void> {
    AppStorage.setOrCreate('global_theme_color', color);
    
    // 可选:通过窗口间通信直接发送
    for (const [name, win] of this.subWindows) {
      console.info(`Syncing light effect to window: ${name}`);
    }
  }

  async closeToolWindow(name: string): Promise<void> {
    const subWindow = this.subWindows.get(name);
    if (subWindow) {
      await subWindow.destroyWindow();
      this.subWindows.delete(name);
    }
  }
}

4.4 主页面:光效联动与自适应渲染

代码亮点 :主页面整合沉浸光感标题栏与悬浮页签,实现工作区切换时的全屏光效过渡。通过 expandSafeArea 将背景延伸至非安全区,配合动态光晕营造沉浸式体验 。

typescript 复制代码
// pages/Index.ets
import { ImmersiveTitleBar } from '../components/ImmersiveTitleBar';
import { FloatTabNavigation, TransparencyLevel } from '../components/FloatTabNavigation';
import { WindowManager } from '../utils/WindowManager';

@Entry
@Component
struct WorkbenchPage {
  @State currentWorkspace: number = 0;
  @State themeColor: string = '#4A90E2';
  @State lightIntensity: number = 0.6;
  @State isNightMode: boolean = false;

  // 工作区页面映射
  private workspacePages: string[] = [
    'pages/DocumentWorkspace',
    'pages/SheetWorkspace',
    'pages/SlideWorkspace',
    'pages/BoardWorkspace'
  ];

  aboutToAppear(): void {
    // 监听全局主题色变化
    AppStorage.watch('global_theme_color', (newColor: string) => {
      this.themeColor = newColor;
      this.triggerAmbientLightTransition();
    });

    // 监听夜间模式
    AppStorage.watch('night_mode', (isNight: boolean) => {
      this.isNightMode = isNight;
      this.lightIntensity = isNight ? 0.3 : 0.6;
    });
  }

  /**
   * 触发环境光过渡动画
   */
  private triggerAmbientLightTransition(): void {
    // 通过状态变化触发ArkUI自动动画
    this.lightIntensity = 1.0;
    setTimeout(() => {
      this.lightIntensity = this.isNightMode ? 0.3 : 0.6;
    }, 300);
  }

  build() {
    Stack() {
      // 第一层:动态环境光背景(全屏沉浸)
      this.buildAmbientLightLayer()

      // 第二层:内容层
      Column() {
        // 沉浸光感标题栏
        ImmersiveTitleBar({ currentWorkspace: this.currentWorkspace })

        // 工作区内容(通过Tabs切换)
        Tabs({ index: this.currentWorkspace, barPosition: BarPosition.Start }) {
          TabContent() { DocumentWorkspace() }
          TabContent() { SheetWorkspace() }
          TabContent() { SlideWorkspace() }
          TabContent() { BoardWorkspace() }
        }
        .width('100%')
        .layoutWeight(1)
        .backgroundColor('transparent')
        .onChange((index: number) => {
          this.currentWorkspace = index;
        })
      }
      .width('100%')
      .height('100%')

      // 第三层:悬浮页签导航(叠加在最上层)
      FloatTabNavigation({
        currentIndex: this.currentWorkspace,
        onTabChange: (index: number) => {
          this.currentWorkspace = index;
        },
        contentBuilder: () => {}  // 内容已在Tabs中渲染
      })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#0a0a0f')  // 深色基底增强光效对比
    // 关键:扩展至所有安全区边缘,实现全屏沉浸
    .expandSafeArea(
      [SafeAreaType.SYSTEM], 
      [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM, SafeAreaEdge.START, SafeAreaEdge.END]
    )
  }

  /**
   * 构建环境光背景层
   * 三层光效叠加:主光晕 + 辅助光点 + 呼吸动画
   */
  @Builder
  buildAmbientLightLayer(): void {
    Column() {
      // 主光晕(随主题色变化)
      Column()
        .width(400)
        .height(400)
        .backgroundColor(this.themeColor)
        .blur(150)
        .opacity(this.lightIntensity * 0.4)
        .position({ x: '50%', y: '20%' })
        .anchor('50%')
        .animation({
          duration: 6000,
          curve: Curve.EaseInOut,
          iterations: -1,
          playMode: PlayMode.Alternate
        })
        .scale({ x: 1.3, y: 1.3 })

      // 辅助光点(对角线分布)
      ForEach([1, 2, 3], (item: number) => {
        Column()
          .width(100 + item * 60)
          .height(100 + item * 60)
          .backgroundColor(this.adjustHue(this.themeColor, item * 30))
          .blur(80)
          .opacity(this.lightIntensity * (0.2 - item * 0.05))
          .position({
            x: `${20 + item * 25}%`,
            y: `${50 + item * 15}%`
          })
          .animation({
            duration: 4000 + item * 1000,
            curve: Curve.EaseInOut,
            iterations: -1,
            playMode: PlayMode.AlternateReverse
          })
      })

      // 底部反射光(增强空间感)
      Column()
        .width('100%')
        .height(200)
        .backgroundColor(this.themeColor)
        .opacity(this.lightIntensity * 0.1)
        .blur(100)
        .position({ x: 0, y: '80%' })
        .linearGradient({
          direction: GradientDirection.Top,
          colors: [
            [this.themeColor, 0.0],
            ['transparent', 1.0]
          ]
        })
    }
    .width('100%')
    .height('100%')
    .backgroundColor(this.isNightMode ? '#050508' : '#0a0a0f')
  }

  /**
   * 简单的色相调整(实际项目使用颜色工具库)
   */
  private adjustHue(color: string, degree: number): string {
    // 简化处理,实际应使用HSL转换
    return color;
  }
}

五、关键技术总结

5.1 沉浸光感实现清单

技术点 API/方法 应用场景
系统材质效果 systemMaterialEffect: SystemMaterialEffect.IMMERSIVE HdsNavigation/HdsTabs 标题栏与页签
背景模糊 backgroundBlurStyle(BlurStyle.REGULAR) 玻璃拟态容器背景
背景滤镜 backdropBlur($r('sys.blur.20')) 更精细的模糊控制
安全区扩展 expandSafeArea([SafeAreaType.SYSTEM], [...]) 内容延伸至状态栏/导航栏
窗口沉浸 setWindowLayoutFullScreen(true) 全屏布局模式
光效动画 animation({ duration, iterations: -1 }) 呼吸灯/脉冲效果

5.2 悬浮导航适配要点

  1. 安全区避让 :通过 getWindowAvoidArea 获取底部导航指示器高度,内容区域动态预留空间
  2. 悬浮层级设计 :导航栏脱离底边,使用 margin({ left: '4%', right: '4%', bottom: avoidHeight + 16 }) 实现四周留白
  3. 透明度动态调节 :支持强/平衡/弱三档,通过 Slider 或预设按钮实时调整
  4. 手势融合:长按展开透明度面板,与系统全面屏手势无缝衔接

5.3 PC 端多窗口光效协同

  • 主窗口:全屏沉浸,环境光背景延伸至所有安全区边缘
  • 浮动工具窗口:置顶、圆角、阴影,跟随主窗口移动
  • 光效同步 :通过 AppStorage 全局状态实现跨窗口主题色联动
  • 焦点感知:窗口激活时边缘发光增强,失活时自动降低光效强度,减少视觉干扰

六、调试与性能优化

6.1 真机调试建议

  1. 玻璃拟态效果:在模拟器上可能显示异常,建议在支持 HarmonyOS 6 的真机或 PC 模拟器上测试
  2. 光效性能 :大范围 blur(150) 消耗 GPU 资源,建议:
    • 夜间模式降低光效刷新率
    • 窗口失活时暂停呼吸动画
    • 使用 visibility 控制不可见区域的光效渲染

6.2 无障碍与降级适配

typescript 复制代码
// 高对比度模式降级:玻璃拟态效果自动转为纯色背景
if (AppStorage.get<boolean>('high_contrast_mode')) {
  this.navTransparency = 1.0;  // 不透明
  // 移除模糊效果,使用纯色替代
}

七、总结与展望

本文基于 HarmonyOS 6(API 23)的 沉浸光感悬浮导航 特性,完整实战了一款面向 PC 端的"光影工作台"多窗口协作应用。核心要点总结:

  1. HDS 系统材质 :通过 systemMaterialEffect.IMMERSIVE 为标题栏和页签带来物理光照级的光晕与反射效果,告别死板的纯色背景
  2. 悬浮卡片导航:底部页签脱离边界,以圆角玻璃卡片形态悬浮,支持三档透明度自适应调节,内容区域智能避让
  3. PC 多窗口光效协同:主窗口与浮动工具窗口通过全局状态同步主题色,激活/失活状态自动调整光效强度,打造专业级多任务协作体验
  4. 全屏沉浸布局 :利用 expandSafeArea 将背景光效延伸至状态栏和导航栏,配合动态环境光层营造"无界"视觉体验

未来扩展方向

  • 结合 HarmonyOS PC 的自由窗口管理,实现工作区的拖拽分屏与光效联动
  • 接入 分布式软总线,跨设备同步光效主题(手机、平板、PC 统一视觉风格)
  • 探索 Face AR 能力,通过摄像头捕捉用户视线,动态调整界面光效焦点区域

转载自:https://blog.csdn.net/u014727709/article/details/157390973

欢迎 👍点赞✍评论⭐收藏,欢迎指正

相关推荐
Ww.xh2 小时前
OpenHarmony API 9 升级到 API 10 权限与接口变更实战指南
服务器·华为·harmonyos
数智前线2 小时前
百灵大模型认领“Elephant”:Ling-2.6-flash定价每百万token 0.1美元
前端·javascript·microsoft
枫叶丹42 小时前
【HarmonyOS 6.0】ArkWeb新特性:PDF加载成功/失败回调及滚动到底部监听
华为·pdf·harmonyos
南村群童欺我老无力.2 小时前
鸿蒙 - Progress进度条从手工拼装到原生组件的重构
华为·重构·harmonyos
Lanren的编程日记2 小时前
Flutter 鸿蒙应用语音识别功能集成实战:多平台框架+模拟模式,实现便捷语音输入
flutter·语音识别·harmonyos
枫叶丹43 小时前
【HarmonyOS 6.0】AVCodec Kit 同步模式视频编解码深度解析:从API演进到高性能实战
开发语言·华为·harmonyos·视频编解码
想你依然心痛3 小时前
HarmonyOS 6(API 23)实战:基于 Face AR & Body AR 打造沉浸式“虚实融合健身镜“应用
ar·restful·harmonyos·悬浮导航·沉浸光感
南村群童欺我老无力.3 小时前
鸿蒙开发中的@Builder装饰器函数中的UI语法限制
ui·华为·harmonyos
南村群童欺我老无力.3 小时前
鸿蒙开发中紧凑写法的括号灾难——深度追踪法定位问题
华为·harmonyos