HarmonyOS 6(API 23)实战:基于悬浮导航、沉浸光感与Face AR的“心流空间“——PC端沉浸式心理疗愈与正念冥想平台

文章目录

    • 每日一句正能量
    • 前言
    • 一、前言:心理疗愈工具的UI革新需求
    • 二、核心特性解析与技术选型
      • [2.1 沉浸光感在心理疗愈中的价值](#2.1 沉浸光感在心理疗愈中的价值)
      • [2.2 Face AR在心理疗愈中的创新应用](#2.2 Face AR在心理疗愈中的创新应用)
    • 三、项目实战:"心流空间"架构设计
      • [3.1 应用场景与功能规划](#3.1 应用场景与功能规划)
      • [3.2 技术架构图](#3.2 技术架构图)
    • 四、环境配置与模块依赖
      • [4.1 模块依赖配置](#4.1 模块依赖配置)
      • [4.2 权限声明(module.json5)](#4.2 权限声明(module.json5))
    • 五、核心组件实战
      • [5.1 窗口沉浸配置(WellnessAbility.ets)](#5.1 窗口沉浸配置(WellnessAbility.ets))
      • [5.2 沉浸光感标题栏(ImmersiveTitleBar.ets)](#5.2 沉浸光感标题栏(ImmersiveTitleBar.ets))
      • [5.3 Face AR情绪感知与环境响应组件(EmotionResponsiveEnvironment.ets)](#5.3 Face AR情绪感知与环境响应组件(EmotionResponsiveEnvironment.ets))
      • [5.4 动态冥想粒子场景(MeditationScene.ets)](#5.4 动态冥想粒子场景(MeditationScene.ets))
      • [5.5 悬浮场景导航页签(FloatTabNavigation.ets)](#5.5 悬浮场景导航页签(FloatTabNavigation.ets))
      • [5.6 多窗口光效同步管理器(WindowManager.ets)](#5.6 多窗口光效同步管理器(WindowManager.ets))
      • [5.7 浮动呼吸引导窗口(BreathWindow.ets)](#5.7 浮动呼吸引导窗口(BreathWindow.ets))
      • [5.8 主页面集成(WellnessPage.ets)](#5.8 主页面集成(WellnessPage.ets))
    • 六、关键技术总结
      • [6.1 沉浸光感实现清单](#6.1 沉浸光感实现清单)
      • [6.2 Face AR情绪感知实现要点](#6.2 Face AR情绪感知实现要点)
      • [6.3 PC端多窗口光效协同](#6.3 PC端多窗口光效协同)
    • 七、调试与性能优化
      • [7.1 真机调试建议](#7.1 真机调试建议)
      • [7.2 性能优化策略](#7.2 性能优化策略)
    • 八、总结与展望

每日一句正能量

人生如一场修行,得意时要看淡,失意时要看开。」

不因顺境而膨胀,避免乐极生悲。不因逆境而沉沦,相信低谷会过去。

每一个闪闪发光的人,都在背后熬过一个又一个不为人知的黑夜,那才是真正值得我们拥有和赞叹的地方。早安!

前言

摘要 :HarmonyOS 6(API 23)带来的悬浮导航、沉浸光感与Face AR特性,为心理健康和正念冥想领域提供了全新的交互范式。本文将实战开发一款面向HarmonyOS PC的"心流空间"应用,展示如何利用systemMaterialEffect打造沉浸式疗愈环境,通过悬浮导航实现多场景快速切换,基于Face AR实现实时情绪识别与响应式环境调节,以及基于多窗口架构构建浮动呼吸引导、情绪日记和白噪音控制窗口的疗愈协作体验。


一、前言:心理疗愈工具的UI革新需求

传统的心理健康应用往往采用静态的界面和固定的功能模块,缺乏与使用者情绪状态的实时互动。HarmonyOS 6(API 23)引入的悬浮导航(Float Navigation)沉浸光感(Immersive Light Effects)Face AR特性,为心理疗愈带来了"感知、响应、治愈"的设计可能 。

本文核心亮点

  • 情绪响应光效:根据Face AR识别的实时情绪状态(平静/焦虑/愉悦/疲惫)动态切换环境光色与呼吸节奏
  • 悬浮场景导航:底部悬浮页签替代传统菜单,支持拖拽排序与透明度调节
  • Face AR情绪感知:实时捕捉微表情变化,自动调节环境色温、音乐节奏和引导语速
  • 多窗口疗愈协作:主冥想窗口 + 浮动呼吸引导 + 情绪日记 + 白噪音控制窗口的光效联动

二、核心特性解析与技术选型

2.1 沉浸光感在心理疗愈中的价值

HarmonyOS 6的systemMaterialEffect通过模拟物理光照模型,为标题栏和导航组件带来细腻的光晕与反射效果 。在心理疗愈场景中,这种材质效果能够:

  • 营造安全感:玻璃拟态的半透明层配合柔和光效,模拟心理咨询室的温暖氛围
  • 情绪色彩疗法:动态环境光随情绪状态变化(焦虑时冷蓝安抚、疲惫时暖黄提振、平静时柔绿维持)
  • 降低认知负荷:通过光效强弱区分窗口焦点状态,多窗口协作时视觉层级柔和不刺眼

2.2 Face AR在心理疗愈中的创新应用

HarmonyOS 6的Face AR能力支持实时精确捕捉人脸表情变化 ,在心理疗愈中可以:

  • 情绪状态识别:识别平静、焦虑、愉悦、疲惫、悲伤等基础情绪状态
  • 微表情预警:捕捉眉头紧锁、嘴角下垂等微表情,及时触发安抚干预
  • 疗效反馈:通过表情变化曲线评估疗愈效果,为后续方案调整提供数据

三、项目实战:"心流空间"架构设计

3.1 应用场景与功能规划

面向HarmonyOS PC的心理疗愈场景,核心功能包括:

功能模块 技术实现 沉浸光感/Face AR应用
主冥想窗口 Canvas + 动态粒子系统 背景光效随情绪状态变化
悬浮场景导航 HdsTabs + systemMaterialEffect 玻璃拟态页签,选中光晕反馈
Face AR情绪感知 AR Engine + 表情系数 实时情绪识别与环境响应
浮动呼吸引导窗口 子窗口 + 动画圆环 呼吸节奏光效同步
情绪日记窗口 子窗口 + TextArea 情绪色标签
白噪音控制窗口 子窗口 + Slider 音量光效可视化

3.2 技术架构图

复制代码
┌─────────────────────────────────────────────────────────────┐
│                    UI Layer (ArkUI)                          │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────┐  │
│  │ ImmersiveTitle│  │ Meditation   │  │ FloatTabBar      │  │
│  │ Bar (HDS)     │  │ View (Canvas)│  │ (HdsTabs)        │  │
│  └──────────────┘  └──────────────┘  └──────────────────┘  │
└─────────────────────────────────────────────────────────────┘
                              │
┌─────────────────────────────────────────────────────────────┐
│              AR Engine Layer (Face AR)                       │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────┐  │
│  │ Emotion      │  │ Micro-       │  │ Response         │  │
│  │ Detection    │  │ Expression   │  │ Trigger          │  │
│  └──────────────┘  └──────────────┘  └──────────────────┘  │
└─────────────────────────────────────────────────────────────┘
                              │
┌─────────────────────────────────────────────────────────────┐
│              Window Manager (PC Multi-Window)                │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────┐  │
│  │ Main Window  │  │ Breath Win   │  │ Diary Win        │  │
│  │ (FullScreen) │  │ (Floating)   │  │ (Floating)       │  │
│  └──────────────┘  └──────────────┘  └──────────────────┘  │
│  ┌──────────────┐                                            │
│  │ Ambient Win  │                                            │
│  │ (Floating)   │                                            │
│  └──────────────┘                                            │
└─────────────────────────────────────────────────────────────┘

四、环境配置与模块依赖

4.1 模块依赖配置

json 复制代码
{
  "name": "flow-space",
  "version": "1.0.0",
  "description": "Immersive Mental Wellness Platform for HarmonyOS PC",
  "dependencies": {
    "@kit.AbilityKit": "^6.1.0",
    "@kit.ArkUI": "^6.1.0",
    "@kit.UIDesignKit": "^6.1.0",
    "@kit.BasicServicesKit": "^6.1.0",
    "@kit.AREngineKit": "^6.1.0",
    "@kit.MultimediaKit": "^6.1.0",
    "@kit.GraphicsKit": "^6.1.0"
  }
}

4.2 权限声明(module.json5)

json 复制代码
{
  "module": {
    "name": "entry",
    "type": "entry",
    "mainElement": "WellnessAbility",
    "deviceTypes": [
      "2in1",
      "tablet",
      "default"
    ],
    "requestPermissions": [
      {
        "name": "ohos.permission.CAMERA",
        "reason": "$string:permission_camera_reason"
      },
      {
        "name": "ohos.permission.MICROPHONE",
        "reason": "$string:permission_mic_reason"
      },
      {
        "name": "ohos.permission.INTERNET",
        "reason": "$string:permission_internet_reason"
      }
    ]
  }
}

五、核心组件实战

5.1 窗口沉浸配置(WellnessAbility.ets)

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

export default class WellnessAbility extends UIAbility {
  private mainWindow: window.Window | null = null;

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

  private async initializeWellnessWindow(windowStage: window.WindowStage): Promise<void> {
    try {
      this.mainWindow = windowStage.getMainWindowSync();

      await this.mainWindow.setWindowSizeType(window.WindowSizeType.FREE);
      await this.mainWindow.setWindowMode(window.WindowMode.FULLSCREEN);
      await this.mainWindow.setWindowTitleBarEnable(false);
      await this.mainWindow.setWindowLayoutFullScreen(true);
      await this.mainWindow.setWindowShadowEnabled(true);
      await this.mainWindow.setWindowCornerRadius(12);
      await this.mainWindow.setWindowBackgroundColor('#00000000');

      AppStorage.setOrCreate('main_window', this.mainWindow);

      windowStage.loadContent('pages/WellnessPage', (err) => {
        if (err.code) {
          console.error('Failed to load wellness content:', JSON.stringify(err));
          return;
        }
        console.info('Flow Space main window initialized');
      });

    } catch (error) {
      console.error('Window initialization failed:', (error as BusinessError).message);
    }
  }

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

5.2 沉浸光感标题栏(ImmersiveTitleBar.ets)

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

export enum EmotionState {
  CALM = 'calm',           // 平静-柔绿
  ANXIOUS = 'anxious',     // 焦虑-冷蓝
  JOYFUL = 'joyful',       // 愉悦-暖金
  TIRED = 'tired',         // 疲惫-暖黄
  SAD = 'sad'             // 悲伤-淡紫
}

export enum SessionType {
  MEDITATION = 'meditation',   // 正念冥想
  BREATHING = 'breathing',    // 呼吸练习
  FOCUS = 'focus',            // 专注训练
  SLEEP = 'sleep'            // 睡眠引导
}

@Component
export struct ImmersiveTitleBar {
  @Prop currentSession: string = '晨间冥想';
  @Prop sessionType: SessionType = SessionType.MEDITATION;
  @Prop emotionState: EmotionState = EmotionState.CALM;
  @State isWindowFocused: boolean = true;
  @State titleBarHeight: number = 48;

  // 情绪状态主题色映射
  private emotionColors: Map<EmotionState, string> = new Map([
    [EmotionState.CALM, '#7ED321'],       // 柔绿-平静
    [EmotionState.ANXIOUS, '#50E3C2'],    // 冷蓝-焦虑
    [EmotionState.JOYFUL, '#F5A623'],     // 暖金-愉悦
    [EmotionState.TIRED, '#F8E71C'],      // 暖黄-疲惫
    [EmotionState.SAD, '#BD10E0']         // 淡紫-悲伤
  ]);

  // 疗愈类型图标色
  private sessionColors: Map<SessionType, string> = new Map([
    [SessionType.MEDITATION, '#7ED321'],
    [SessionType.BREATHING, '#50E3C2'],
    [SessionType.FOCUS, '#F5A623'],
    [SessionType.SLEEP, '#9013FE']
  ]);

  aboutToAppear(): void {
    AppStorage.watch('window_focused', (focused: boolean) => {
      this.isWindowFocused = focused;
    });
  }

  private getThemeColor(): string {
    return this.emotionColors.get(this.emotionState) || '#7ED321';
  }

  private getSessionText(): string {
    const texts: Map<SessionType, string> = new Map([
      [SessionType.MEDITATION, '正念冥想'],
      [SessionType.BREATHING, '呼吸练习'],
      [SessionType.FOCUS, '专注训练'],
      [SessionType.SLEEP, '睡眠引导']
    ]);
    return texts.get(this.sessionType) || '正念冥想';
  }

  build() {
    HdsNavigation({
      title: `心流空间 - ${this.currentSession}`,
      subtitle: `${this.getSessionText()} · ${this.getEmotionText(this.emotionState)}`,
      systemMaterialEffect: SystemMaterialEffect.IMMERSIVE,
      backgroundOpacity: this.isWindowFocused ? 0.85 : 0.55,
      height: this.titleBarHeight,
      leading: this.buildLeadingActions(),
      trailing: this.buildTrailingActions()
    })
    .width('100%')
    .border({
      width: { bottom: 1 },
      color: this.isWindowFocused 
        ? this.getThemeColor() 
        : 'rgba(255,255,255,0.1)'
    })
    .shadow({
      radius: this.isWindowFocused ? 15 : 5,
      color: this.getThemeColor(),
      offsetX: 0,
      offsetY: 2
    })
    .animation({
      duration: 300,
      curve: Curve.EaseInOut
    })
  }

  @Builder
  buildLeadingActions(): void {
    Row({ space: 12 }) {
      // 情绪状态指示灯(柔和呼吸效果)
      Stack() {
        Circle()
          .width(20)
          .height(20)
          .fill(this.getThemeColor())
          .opacity(0.2)
          .animation({
            duration: 3000,
            curve: Curve.EaseInOut,
            iterations: -1,
            playMode: PlayMode.Alternate
          })
          .scale({ x: 1.8, y: 1.8 })

        Circle()
          .width(14)
          .height(14)
          .fill(this.getThemeColor())
      }
      .width(24)
      .height(24)

      Button({ type: ButtonType.Circle }) {
        Image($r('app.media.ic_play_pause'))
          .width(18)
          .height(18)
          .fillColor('#FFFFFF')
      }
      .width(32)
      .height(32)
      .backgroundColor('rgba(255,255,255,0.1)')
      .onClick(() => {
        AppStorage.setOrCreate('session_action', 'toggle_play');
      })

      Button({ type: ButtonType.Circle }) {
        Image($r('app.media.ic_timer'))
          .width(18)
          .height(18)
          .fillColor('#FFFFFF')
      }
      .width(32)
      .height(32)
      .backgroundColor('rgba(255,255,255,0.1)')
      .onClick(() => {
        AppStorage.setOrCreate('session_action', 'set_timer');
      })
    }
    .padding({ left: 16 })
  }

  @Builder
  buildTrailingActions(): void {
    Row({ space: 12 }) {
      // Face AR状态
      Circle()
        .width(8)
        .height(8)
        .fill(AppStorage.get<boolean>('face_ar_active') ? '#27C93F' : '#95A5A6')
        .shadow({
          radius: 8,
          color: AppStorage.get<boolean>('face_ar_active') ? '#27C93F' : 'transparent'
        })

      Button({ type: ButtonType.Circle }) {
        Image($r('app.media.ic_breath'))
          .width(18)
          .height(18)
          .fillColor('#FFFFFF')
      }
      .width(32)
      .height(32)
      .backgroundColor('rgba(255,255,255,0.1)')
      .onClick(() => {
        AppStorage.setOrCreate('window_action', 'open_breath');
      })

      Button({ type: ButtonType.Circle }) {
        Image($r('app.media.ic_diary'))
          .width(18)
          .height(18)
          .fillColor('#FFFFFF')
      }
      .width(32)
      .height(32)
      .backgroundColor('rgba(255,255,255,0.1)')
      .onClick(() => {
        AppStorage.setOrCreate('window_action', 'open_diary');
      })

      Button({ type: ButtonType.Circle }) {
        Image($r('app.media.ic_ambient'))
          .width(18)
          .height(18)
          .fillColor('#FFFFFF')
      }
      .width(32)
      .height(32)
      .backgroundColor('rgba(255,255,255,0.1)')
      .onClick(() => {
        AppStorage.setOrCreate('window_action', 'open_ambient');
      })
    }
    .padding({ right: 16 })
  }

  private getEmotionText(emotion: EmotionState): string {
    const texts: Map<EmotionState, string> = new Map([
      [EmotionState.CALM, '平静'],
      [EmotionState.ANXIOUS, '焦虑'],
      [EmotionState.JOYFUL, '愉悦'],
      [EmotionState.TIRED, '疲惫'],
      [EmotionState.SAD, '低落']
    ]);
    return texts.get(emotion) || '平静';
  }
}

5.3 Face AR情绪感知与环境响应组件(EmotionResponsiveEnvironment.ets)

核心创新组件,基于AR Engine实现情绪识别并驱动环境参数实时调节 。

typescript 复制代码
// entry/src/main/ets/components/EmotionResponsiveEnvironment.ets
import { arEngine } from '@kit.AREngineKit';
import { camera } from '@kit.CameraKit';

export interface EnvironmentParameters {
  colorTemperature: number;   // 色温 2700K-6500K
  brightness: number;           // 亮度 0.1-1.0
  pulseSpeed: number;           // 呼吸节奏 0.5-3.0秒
  particleDensity: number;     // 粒子密度 0-1
  ambientSound: string;         // 环境音类型
}

@Component
export struct EmotionResponsiveEnvironment {
  @State isARActive: boolean = false;
  @State currentEmotion: string = 'calm';
  @State emotionConfidence: number = 0;
  @State envParams: EnvironmentParameters = {
    colorTemperature: 4000,
    brightness: 0.6,
    pulseSpeed: 2.0,
    particleDensity: 0.5,
    ambientSound: 'forest'
  };
  @State trackingStatus: string = '未启动';

  private arSession: arEngine.ARSession | null = null;
  private faceTracker: arEngine.FaceTracker | null = null;
  private envCallback: ((params: EnvironmentParameters) => void) | null = null;

  aboutToAppear(): void {
    this.initializeAR();
  }

  aboutToDisappear(): void {
    this.releaseAR();
  }

  setEnvironmentCallback(callback: (params: EnvironmentParameters) => void): void {
    this.envCallback = callback;
  }

  private async initializeAR(): Promise<void> {
    try {
      this.arSession = arEngine.createARSession({
        mode: arEngine.ARMode.FACE,
        cameraConfig: {
          cameraFacing: camera.CameraFacing.CAMERA_FACING_FRONT
        }
      });

      this.faceTracker = this.arSession.createFaceTracker({
        maxFaceCount: 1,
        enableExpression: true,
        enablePose: true
      });

      await this.arSession.start();
      this.isARActive = true;
      this.trackingStatus = '情绪感知中';
      AppStorage.setOrCreate('face_ar_active', true);

      this.startEmotionTracking();
      console.info('Emotion Responsive Environment initialized');
    } catch (error) {
      console.error('Failed to initialize Face AR:', error);
      this.trackingStatus = '初始化失败';
    }
  }

  private startEmotionTracking(): void {
    if (!this.arSession || !this.faceTracker) return;

    this.arSession.on('frame', (frame: arEngine.ARFrame) => {
      const faces = this.faceTracker?.track(frame);
      if (faces && faces.length > 0) {
        const face = faces[0];
        this.analyzeEmotionAndRespond(face);
      }
    });
  }

  private analyzeEmotionAndRespond(face: arEngine.Face): void {
    const expressions = face.getExpressions();
    const pose = face.getPose();

    // 提取表情特征
    const smile = expressions.get(arEngine.FaceExpressionType.SMILE) || 0;
    const frown = expressions.get(arEngine.FaceExpressionType.FROWN) || 0;
    const browRaise = expressions.get(arEngine.FaceExpressionType.BROW_RAISE_LEFT) || 0;
    const eyeWide = expressions.get(arEngine.FaceExpressionType.EYE_WIDE_LEFT) || 0;
    const mouthOpen = expressions.get(arEngine.FaceExpressionType.MOUTH_OPEN) || 0;

    // 情绪状态判断
    let emotion = 'calm';
    let confidence = 0;

    if (frown > 0.4 && browRaise > 0.3) {
      emotion = 'anxious';
      confidence = (frown + browRaise) / 2;
    } else if (smile > 0.6 && eyeWide > 0.4) {
      emotion = 'joyful';
      confidence = (smile + eyeWide) / 2;
    } else if (frown > 0.5 && mouthOpen < 0.2) {
      emotion = 'sad';
      confidence = frown;
    } else if (eyeWide < 0.3 && mouthOpen < 0.2 && smile < 0.3) {
      emotion = 'tired';
      confidence = 1 - (eyeWide + mouthOpen + smile);
    } else {
      emotion = 'calm';
      confidence = 0.5 + (1 - frown - browRaise) / 2;
    }

    this.currentEmotion = emotion;
    this.emotionConfidence = confidence;

    // 生成环境参数
    const newParams = this.generateEnvironmentParams(emotion, confidence);
    this.envParams = newParams;

    if (this.envCallback) {
      this.envCallback(newParams);
    }

    AppStorage.setOrCreate('emotion_state', emotion);
    AppStorage.setOrCreate('environment_params', newParams);
  }

  private generateEnvironmentParams(emotion: string, confidence: number): EnvironmentParameters {
    const params: Map<string, EnvironmentParameters> = new Map([
      ['calm', {
        colorTemperature: 4000,
        brightness: 0.6,
        pulseSpeed: 2.0,
        particleDensity: 0.5,
        ambientSound: 'forest'
      }],
      ['anxious', {
        colorTemperature: 6500,
        brightness: 0.4,
        pulseSpeed: 1.0,
        particleDensity: 0.2,
        ambientSound: 'rain'
      }],
      ['joyful', {
        colorTemperature: 3500,
        brightness: 0.8,
        pulseSpeed: 1.5,
        particleDensity: 0.8,
        ambientSound: 'stream'
      }],
      ['tired', {
        colorTemperature: 2700,
        brightness: 0.3,
        pulseSpeed: 3.0,
        particleDensity: 0.3,
        ambientSound: 'ocean'
      }],
      ['sad', {
        colorTemperature: 3000,
        brightness: 0.35,
        pulseSpeed: 2.5,
        particleDensity: 0.4,
        ambientSound: 'wind'
      }]
    ]);

    return params.get(emotion) || params.get('calm')!;
  }

  private releaseAR(): void {
    if (this.arSession) {
      this.arSession.stop();
      this.arSession = null;
    }
    this.faceTracker = null;
    this.isARActive = false;
    AppStorage.setOrCreate('face_ar_active', false);
    this.trackingStatus = '已停止';
  }

  build() {
    Stack() {
      // AR预览层
      Column() {
        if (this.isARActive) {
          XComponent({
            id: 'emotion_ar_preview',
            type: XComponentType.SURFACE,
            controller: new XComponentController()
          })
            .width('100%')
            .height('100%')
            .opacity(0.15)
        }
      }
      .width('100%')
      .height('100%')

      // 情绪状态可视化
      Column() {
        this.buildEmotionStatusPanel()
      }
      .width('100%')
      .height('100%')

      // AR状态指示器
      this.buildARStatusIndicator()
    }
    .width('100%')
    .height('100%')
  }

  @Builder
  buildEmotionStatusPanel(): void {
    Column({ space: 10 }) {
      // 当前情绪
      Text(this.getEmotionLabel(this.currentEmotion))
        .fontSize(20)
        .fontColor(this.getEmotionColor(this.currentEmotion))
        .fontWeight(FontWeight.Bold)

      // 置信度
      Progress({
        value: this.emotionConfidence * 100,
        total: 100,
        type: ProgressType.Ring
      })
        .width(60)
        .height(60)
        .color(this.getEmotionColor(this.currentEmotion))

      // 环境参数
      Column({ space: 4 }) {
        Text(`色温: ${this.envParams.colorTemperature}K`)
          .fontSize(10)
          .fontColor('#AAAAAA')
        Text(`亮度: ${Math.round(this.envParams.brightness * 100)}%`)
          .fontSize(10)
          .fontColor('#AAAAAA')
        Text(`呼吸: ${this.envParams.pulseSpeed.toFixed(1)}s`)
          .fontSize(10)
          .fontColor('#AAAAAA')
        Text(`环境音: ${this.getSoundLabel(this.envParams.ambientSound)}`)
          .fontSize(10)
          .fontColor('#AAAAAA')
      }
    }
    .width(160)
    .height('auto')
    .padding(16)
    .backgroundColor('rgba(0,0,0,0.6)')
    .borderRadius(16)
    .position({ x: '85%', y: '20%' })
    .anchor('100% 0%')
    .backdropFilter($r('sys.blur.10'))
  }

  @Builder
  buildARStatusIndicator(): void {
    Row({ space: 6 }) {
      Circle()
        .width(8)
        .height(8)
        .fill(this.isARActive ? '#27C93F' : '#95A5A6')
        .shadow({
          radius: 6,
          color: this.isARActive ? '#27C93F' : 'transparent'
        })

      Text(`情绪感知 · ${this.trackingStatus}`)
        .fontSize(11)
        .fontColor(this.isARActive ? '#27C93F' : '#95A5A6')
    }
    .width('auto')
    .height(28)
    .padding({ left: 10, right: 10 })
    .backgroundColor('rgba(0,0,0,0.6)')
    .borderRadius(14)
    .position({ x: 16, y: 16 })
    .backdropFilter($r('sys.blur.10'))
  }

  private getEmotionLabel(emotion: string): string {
    const labels: Map<string, string> = new Map([
      ['calm', '平静'],
      ['anxious', '焦虑'],
      ['joyful', '愉悦'],
      ['tired', '疲惫'],
      ['sad', '低落']
    ]);
    return labels.get(emotion) || '平静';
  }

  private getEmotionColor(emotion: string): string {
    const colors: Map<string, string> = new Map([
      ['calm', '#7ED321'],
      ['anxious', '#50E3C2'],
      ['joyful', '#F5A623'],
      ['tired', '#F8E71C'],
      ['sad', '#BD10E0']
    ]);
    return colors.get(emotion) || '#7ED321';
  }

  private getSoundLabel(sound: string): string {
    const labels: Map<string, string> = new Map([
      ['forest', '森林'],
      ['rain', '雨声'],
      ['stream', '溪流'],
      ['ocean', '海浪'],
      ['wind', '风声']
    ]);
    return labels.get(sound) || '森林';
  }
}

5.4 动态冥想粒子场景(MeditationScene.ets)

typescript 复制代码
// entry/src/main/ets/components/MeditationScene.ets
import { EnvironmentParameters } from './EmotionResponsiveEnvironment';

interface Particle {
  x: number;
  y: number;
  size: number;
  speed: number;
  opacity: number;
  color: string;
}

@Component
export struct MeditationScene {
  @Prop envParams: EnvironmentParameters = {
    colorTemperature: 4000,
    brightness: 0.6,
    pulseSpeed: 2.0,
    particleDensity: 0.5,
    ambientSound: 'forest'
  };
  @State particles: Particle[] = [];
  @State pulsePhase: number = 0;
  @State isPlaying: boolean = false;

  private animationTimer: number | null = null;

  aboutToAppear(): void {
    this.generateParticles();
    this.startAnimation();
  }

  aboutToDisappear(): void {
    if (this.animationTimer) {
      clearInterval(this.animationTimer);
    }
  }

  private generateParticles(): void {
    const count = Math.floor(this.envParams.particleDensity * 50);
    this.particles = Array.from({ length: count }, () => ({
      x: Math.random() * 100,
      y: Math.random() * 100,
      size: 2 + Math.random() * 6,
      speed: 0.2 + Math.random() * 0.8,
      opacity: 0.1 + Math.random() * 0.5,
      color: this.getTemperatureColor()
    }));
  }

  private startAnimation(): void {
    this.animationTimer = setInterval(() => {
      this.pulsePhase = (this.pulsePhase + 0.02) % (Math.PI * 2);
      this.updateParticles();
    }, 50);
  }

  private updateParticles(): void {
    this.particles = this.particles.map(p => ({
      ...p,
      y: (p.y - p.speed + 100) % 100,
      opacity: p.opacity * (0.8 + 0.2 * Math.sin(this.pulsePhase))
    }));
  }

  private getTemperatureColor(): string {
    // 根据色温返回颜色
    const temp = this.envParams.colorTemperature;
    if (temp < 3000) return '#FF9500';      // 暖黄
    if (temp < 4000) return '#FFCC00';      // 暖白
    if (temp < 5000) return '#FFFFFF';      // 正白
    return '#B8D4E3';                        // 冷白
  }

  build() {
    Stack() {
      // 背景层
      this.buildEmotionBackground()

      // 粒子层
      ForEach(this.particles, (particle, index) => {
        Circle()
          .width(particle.size)
          .height(particle.size)
          .fill(particle.color)
          .opacity(particle.opacity * this.envParams.brightness)
          .position({ x: `${particle.x}%`, y: `${particle.y}%` })
          .blur(particle.size / 2)
          .animation({
            duration: 100,
            curve: Curve.Linear
          })
      })

      // 呼吸引导圆环
      this.buildBreathingGuide()

      // 中心焦点
      this.buildFocusPoint()
    }
    .width('100%')
    .height('100%')
  }

  @Builder
  buildEmotionBackground(): void {
    Column() {
      // 基础背景色
      Column()
        .width('100%')
        .height('100%')
        .backgroundColor(this.getEmotionBaseColor())

      // 渐变光晕
      Column()
        .width(800)
        .height(800)
        .backgroundColor(this.getTemperatureColor())
        .blur(300)
        .opacity(0.15 * this.envParams.brightness)
        .position({ x: '50%', y: '50%' })
        .anchor('50%')
        .animation({
          duration: this.envParams.pulseSpeed * 1000,
          curve: Curve.EaseInOut,
          iterations: -1,
          playMode: PlayMode.Alternate
        })
        .scale({ x: 1.5, y: 1.5 })
    }
    .width('100%')
    .height('100%')
  }

  @Builder
  buildBreathingGuide(): void {
    Stack() {
      // 外环
      Circle()
        .width(200 + Math.sin(this.pulsePhase) * 40)
        .height(200 + Math.sin(this.pulsePhase) * 40)
        .fill('transparent')
        .border({
          width: 2,
          color: `rgba(255,255,255,${0.1 + 0.1 * Math.sin(this.pulsePhase)})`
        })
        .animation({
          duration: this.envParams.pulseSpeed * 1000,
          curve: Curve.EaseInOut,
          iterations: -1,
          playMode: PlayMode.Alternate
        })

      // 内环
      Circle()
        .width(150 + Math.sin(this.pulsePhase) * 30)
        .height(150 + Math.sin(this.pulsePhase) * 30)
        .fill(`rgba(255,255,255,${0.05 + 0.05 * Math.sin(this.pulsePhase)})`)
        .animation({
          duration: this.envParams.pulseSpeed * 1000,
          curve: Curve.EaseInOut,
          iterations: -1,
          playMode: PlayMode.Alternate
        })

      // 呼吸提示文字
      Text(Math.sin(this.pulsePhase) > 0 ? '吸气' : '呼气')
        .fontSize(16)
        .fontColor('rgba(255,255,255,0.6)')
        .fontWeight(FontWeight.Light)
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }

  @Builder
  buildFocusPoint(): void {
    Column() {
      Circle()
        .width(8)
        .height(8)
        .fill('rgba(255,255,255,0.8)')
        .shadow({
          radius: 20,
          color: 'rgba(255,255,255,0.3)'
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .position({ x: 0, y: 0 })
  }

  private getEmotionBaseColor(): string {
    const emotion = AppStorage.get<string>('emotion_state') || 'calm';
    const colors: Map<string, string> = new Map([
      ['calm', '#1a2f1a'],
      ['anxious', '#1a2f2f'],
      ['joyful', '#2f2a1a'],
      ['tired', '#2f2a1a'],
      ['sad', '#2a1a2f']
    ]);
    return colors.get(emotion) || '#1a2f1a';
  }
}

5.5 悬浮场景导航页签(FloatTabNavigation.ets)

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

export enum TransparencyLevel {
  STRONG = 0.85,
  BALANCED = 0.70,
  WEAK = 0.55
}

interface SessionTab {
  id: string;
  name: string;
  theme: string;
  duration: number;
  icon: Resource;
}

@Component
export struct FloatTabNavigation {
  @Prop currentIndex: number = 0;
  @State navTransparency: number = TransparencyLevel.BALANCED;
  @State isExpanded: boolean = false;
  @State bottomAvoidHeight: number = 0;
  @State tabs: SessionTab[] = [
    { id: '1', name: '晨间冥想', theme: '#7ED321', duration: 10, icon: $r('app.media.ic_sunrise') },
    { id: '2', name: '焦虑舒缓', theme: '#50E3C2', duration: 15, icon: $r('app.media.ic_calm') },
    { id: '3', name: '专注提升', theme: '#F5A623', duration: 25, icon: $r('app.media.ic_focus') },
    { id: '4', name: '睡眠引导', theme: '#9013FE', duration: 30, icon: $r('app.media.ic_moon') },
    { id: '5', name: '情绪释放', theme: '#BD10E0', duration: 20, icon: $r('app.media.ic_release') }
  ];

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

  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;
    } catch (error) {
      console.error('Failed to get avoid area:', error);
    }
  }

  build() {
    Stack({ alignContent: Alignment.Bottom }) {
      Column() {
        this.contentBuilder()
      }
      .padding({ bottom: this.bottomAvoidHeight + 88 })

      Column() {
        Stack() {
          Column()
            .width('100%')
            .height('100%')
            .backgroundBlurStyle(BlurStyle.REGULAR)
            .opacity(this.navTransparency)
            .backdropFilter($r('sys.blur.20'))

          Column()
            .width('100%')
            .height('100%')
            .linearGradient({
              direction: GradientDirection.Top,
              colors: [
                ['rgba(255,255,255,0.15)', 0.0],
                ['rgba(255,255,255,0.05)', 1.0]
              ]
            })
        }
        .width('100%')
        .height('100%')
        .borderRadius(20)
        .shadow({
          radius: 20,
          color: 'rgba(0,0,0,0.2)',
          offsetX: 0,
          offsetY: -4
        })

        Row() {
          ForEach(this.tabs, (tab: SessionTab, index: number) => {
            this.buildSessionTab(tab, index)
          }, (tab: SessionTab) => tab.id)
        }
        .width('100%')
        .height(64)
        .padding({ left: 16, right: 16 })
        .justifyContent(FlexAlign.Start)

        if (this.isExpanded) {
          this.buildTransparencyPanel()
        }
      }
      .width('96%')
      .height(this.isExpanded ? 108 : 64)
      .margin({ 
        bottom: this.bottomAvoidHeight + 12, 
        left: '2%', 
        right: '2%' 
      })
      .animation({
        duration: 300,
        curve: Curve.Spring,
        iterations: 1
      })
      .gesture(
        LongPressGesture({ duration: 500 })
          .onAction(() => {
            this.isExpanded = !this.isExpanded;
          })
      )
    }
    .width('100%')
    .height('100%')
  }

  @Builder
  buildSessionTab(tab: SessionTab, index: number): void {
    Row({ space: 6 }) {
      Image(tab.icon)
        .width(16)
        .height(16)
        .fillColor(tab.theme)

      Text(tab.name)
        .fontSize(13)
        .fontWeight(this.currentIndex === index ? FontWeight.Bold : FontWeight.Normal)
        .fontColor(this.currentIndex === index ? '#FFFFFF' : '#AAAAAA')
        .maxLines(1)
        .textOverflow({ overflow: TextOverflow.Ellipsis })
        .width(80)

      Text(`${tab.duration}min`)
        .fontSize(11)
        .fontColor('#666666')
        .backgroundColor('rgba(255,255,255,0.1)')
        .borderRadius(8)
        .padding({ left: 6, right: 6, top: 2, bottom: 2 })

      if (this.currentIndex === index) {
        Button({ type: ButtonType.Circle }) {
          Text('×')
            .fontSize(14)
            .fontColor('#AAAAAA')
        }
        .width(20)
        .height(20)
        .backgroundColor('transparent')
        .onClick(() => {
          this.closeTab(index);
        })
      }
    }
    .height(40)
    .padding({ left: 12, right: 12 })
    .backgroundColor(this.currentIndex === index 
      ? 'rgba(255,255,255,0.15)' 
      : 'transparent')
    .borderRadius(12)
    .border({
      width: this.currentIndex === index ? 1 : 0,
      color: tab.theme
    })
    .onClick(() => {
      this.currentIndex = index;
      AppStorage.setOrCreate('current_session', tab.name);
      AppStorage.setOrCreate('current_theme', tab.theme);
      AppStorage.setOrCreate('session_duration', tab.duration);
    })
  }

  @Builder
  buildTransparencyPanel(): void {
    Row({ space: 12 }) {
      Text('透明度')
        .fontSize(12)
        .fontColor('#AAAAAA')

      Slider({
        value: this.navTransparency * 100,
        min: 55,
        max: 85,
        step: 15,
        style: SliderStyle.InSet
      })
        .width(120)
        .onChange((value: number) => {
          this.navTransparency = value / 100;
        })

      Text(`${Math.round(this.navTransparency * 100)}%`)
        .fontSize(12)
        .fontColor('#AAAAAA')

      Button('强')
        .fontSize(11)
        .backgroundColor(this.navTransparency === TransparencyLevel.STRONG 
          ? '#7ED321' 
          : 'rgba(255,255,255,0.1)')
        .onClick(() => { this.navTransparency = TransparencyLevel.STRONG; })

      Button('平衡')
        .fontSize(11)
        .backgroundColor(this.navTransparency === TransparencyLevel.BALANCED 
          ? '#7ED321' 
          : 'rgba(255,255,255,0.1)')
        .onClick(() => { this.navTransparency = TransparencyLevel.BALANCED; })

      Button('弱')
        .fontSize(11)
        .backgroundColor(this.navTransparency === TransparencyLevel.WEAK 
          ? '#7ED321' 
          : 'rgba(255,255,255,0.1)')
        .onClick(() => { this.navTransparency = TransparencyLevel.WEAK; })
    }
    .width('100%')
    .height(44)
    .justifyContent(FlexAlign.Center)
    .backgroundColor('rgba(255,255,255,0.05)')
    .borderRadius({ topLeft: 12, topRight: 12 })
  }

  private closeTab(index: number): void {
    if (this.tabs.length <= 1) return;
    this.tabs.splice(index, 1);
    if (this.currentIndex >= index && this.currentIndex > 0) {
      this.currentIndex--;
    }
  }

  @BuilderParam contentBuilder: () => void = this.defaultContentBuilder;

  @Builder
  defaultContentBuilder(): void {
    Column() {
      Text('冥想区域')
        .fontSize(16)
        .fontColor('#999999')
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

5.6 多窗口光效同步管理器(WindowManager.ets)

typescript 复制代码
// entry/src/main/ets/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;
  themeColor?: string;
}

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;
  }

  async initializeMainWindow(windowStage: window.WindowStage): Promise<void> {
    this.mainWindow = windowStage.getMainWindowSync();
    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');

    this.mainWindow.on('windowFocusChange', (isFocused: boolean) => {
      AppStorage.setOrCreate('window_focused', isFocused);
      if (isFocused) {
        this.syncGlobalLightEffect(AppStorage.get<string>('global_theme_color') || '#7ED321');
      }
    });

    console.info('Main window initialized for Flow Space');
  }

  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, config.themeColor);
      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, themeColor?: string): void {
    subWindow.on('windowFocusChange', (isFocused: boolean) => {
      AppStorage.setOrCreate(`window_${name}_focused`, isFocused);
      if (isFocused && themeColor) {
        AppStorage.setOrCreate('global_theme_color', themeColor);
      }
    });

    AppStorage.watch('global_theme_color', (color: string) => {
      console.info(`Syncing theme color ${color} to window ${name}`);
    });
  }

  async syncGlobalLightEffect(color: string): Promise<void> {
    AppStorage.setOrCreate('global_theme_color', color);
  }

  async openBreathWindow(): Promise<void> {
    await this.createToolWindow({
      name: 'BreathWindow',
      title: '呼吸引导',
      width: 300,
      height: 400,
      x: 1000,
      y: 100,
      themeColor: '#50E3C2'
    });
  }

  async openDiaryWindow(): Promise<void> {
    await this.createToolWindow({
      name: 'DiaryWindow',
      title: '情绪日记',
      width: 400,
      height: 500,
      x: 200,
      y: 100,
      themeColor: '#F5A623'
    });
  }

  async openAmbientWindow(): Promise<void> {
    await this.createToolWindow({
      name: 'AmbientWindow',
      title: '白噪音',
      width: 350,
      height: 400,
      x: 50,
      y: 500,
      themeColor: '#9013FE'
    });
  }

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

5.7 浮动呼吸引导窗口(BreathWindow.ets)

typescript 复制代码
// entry/src/main/ets/pages/BreathWindow.ets
@Entry
@Component
struct BreathWindow {
  @State breathPhase: number = 0; // 0-1 吸气, 1-2 屏息, 2-3 呼气, 3-4 屏息
  @State breathProgress: number = 0;
  @State isFocused: boolean = false;
  @State themeColor: string = '#50E3C2';
  @State pulseSpeed: number = 2.0;

  private breathTimer: number | null = null;

  aboutToAppear(): void {
    AppStorage.watch('window_BreathWindow_focused', (focused: boolean) => {
      this.isFocused = focused;
    });
    AppStorage.watch('current_theme', (color: string) => {
      this.themeColor = color;
    });
    AppStorage.watch('environment_params', (params: EnvironmentParameters) => {
      this.pulseSpeed = params.pulseSpeed;
      this.restartBreathCycle();
    });

    this.startBreathCycle();
  }

  aboutToDisappear(): void {
    if (this.breathTimer) {
      clearInterval(this.breathTimer);
    }
  }

  private startBreathCycle(): void {
    const cycleDuration = this.pulseSpeed * 1000 * 4; // 4阶段呼吸
    this.breathTimer = setInterval(() => {
      this.breathProgress = (this.breathProgress + 0.01) % 4;
      this.breathPhase = Math.floor(this.breathProgress);
    }, cycleDuration / 400);
  }

  private restartBreathCycle(): void {
    if (this.breathTimer) {
      clearInterval(this.breathTimer);
    }
    this.startBreathCycle();
  }

  private getBreathText(): string {
    const texts = ['吸气', '屏息', '呼气', '屏息'];
    return texts[this.breathPhase] || '吸气';
  }

  private getBreathColor(): string {
    const colors = ['#50E3C2', '#F5A623', '#7ED321', '#F5A623'];
    return colors[this.breathPhase] || '#50E3C2';
  }

  private getCircleScale(): number {
    const phaseProgress = this.breathProgress % 1;
    if (this.breathPhase === 0) return 0.8 + phaseProgress * 0.4; // 吸气放大
    if (this.breathPhase === 1) return 1.2; // 屏息保持
    if (this.breathPhase === 2) return 1.2 - phaseProgress * 0.4; // 呼气缩小
    return 0.8; // 屏息保持
  }

  build() {
    Stack() {
      Column() {
        Column()
          .width(400)
          .height(400)
          .backgroundColor(this.themeColor)
          .blur(150)
          .opacity(this.isFocused ? 0.1 : 0.05)
          .position({ x: '50%', y: '30%' })
          .anchor('50%')
      }
      .width('100%')
      .height('100%')
      .backgroundColor('#0f0f1a')

      Column() {
        Row() {
          Text('呼吸引导')
            .fontSize(14)
            .fontColor(this.themeColor)
            .fontWeight(FontWeight.Bold)
          
          Row({ space: 8 }) {
            Circle().width(12).height(12).fill('#FF5F56')
            Circle().width(12).height(12).fill('#FFBD2E')
            Circle().width(12).height(12).fill('#27C93F')
          }
        }
        .width('100%')
        .height(36)
        .padding({ left: 16, right: 16 })
        .justifyContent(FlexAlign.SpaceBetween)
        .backgroundColor(`rgba(${this.hexToRgb(this.themeColor)},0.05)`)

        // 呼吸圆环
        Stack() {
          // 外环
          Circle()
            .width(180)
            .height(180)
            .fill('transparent')
            .border({
              width: 3,
              color: `rgba(255,255,255,0.1)`
            })

          // 呼吸环
          Circle()
            .width(180 * this.getCircleScale())
            .height(180 * this.getCircleScale())
            .fill(`rgba(${this.hexToRgb(this.getBreathColor())},0.2)`)
            .border({
              width: 2,
              color: this.getBreathColor()
            })
            .animation({
              duration: 100,
              curve: Curve.Linear
            })

          // 中心文字
          Column() {
            Text(this.getBreathText())
              .fontSize(24)
              .fontColor(this.getBreathColor())
              .fontWeight(FontWeight.Bold)

            Text(`${Math.round(this.pulseSpeed * 4)}秒/周期`)
              .fontSize(12)
              .fontColor('#AAAAAA')
          }
        }
        .width('100%')
        .height(250)
        .justifyContent(FlexAlign.Center)

        // 阶段指示器
        Row({ space: 8 }) {
          ForEach([0, 1, 2, 3], (phase) => {
            Column()
              .width(40)
              .height(4)
              .backgroundColor(phase === this.breathPhase ? this.getBreathColor() : 'rgba(255,255,255,0.1)')
              .borderRadius(2)
              .animation({
                duration: 300,
                curve: Curve.EaseInOut
              })
          })
        }
        .width('100%')
        .height(20)
        .justifyContent(FlexAlign.Center)

        // 调节控制
        Row({ space: 12 }) {
          Text('节奏')
            .fontSize(12)
            .fontColor('#AAAAAA')

          Slider({
            value: this.pulseSpeed,
            min: 1.0,
            max: 4.0,
            step: 0.5,
            style: SliderStyle.InSet
          })
            .width(150)
            .onChange((value: number) => {
              this.pulseSpeed = value;
              this.restartBreathCycle();
            })

          Text(`${this.pulseSpeed.toFixed(1)}s`)
            .fontSize(12)
            .fontColor('#AAAAAA')
        }
        .width('100%')
        .height(40)
        .justifyContent(FlexAlign.Center)
      }
      .width('100%')
      .height('100%')
    }
    .width('100%')
    .height('100%')
    .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
  }

  private hexToRgb(hex: string): string {
    const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
    return result ? 
      `${parseInt(result[1], 16)},${parseInt(result[2], 16)},${parseInt(result[3], 16)}` 
      : '80,227,194';
  }
}

5.8 主页面集成(WellnessPage.ets)

typescript 复制代码
// entry/src/main/ets/pages/WellnessPage.ets
import { ImmersiveTitleBar, EmotionState, SessionType } from '../components/ImmersiveTitleBar';
import { FloatTabNavigation } from '../components/FloatTabNavigation';
import { MeditationScene } from '../components/MeditationScene';
import { EmotionResponsiveEnvironment } from '../components/EmotionResponsiveEnvironment';
import { WindowManager } from '../utils/WindowManager';

@Entry
@Component
struct WellnessPage {
  @State currentSession: number = 0;
  @State currentEmotion: EmotionState = EmotionState.CALM;
  @State currentTheme: string = '#7ED321';
  @State sessionType: SessionType = SessionType.MEDITATION;
  @State envParams: EnvironmentParameters = {
    colorTemperature: 4000,
    brightness: 0.6,
    pulseSpeed: 2.0,
    particleDensity: 0.5,
    ambientSound: 'forest'
  };
  @State useFaceAR: boolean = true;
  @State lightIntensity: number = 0.6;

  aboutToAppear(): void {
    AppStorage.watch('current_session', (session: string) => {
      // 场景切换逻辑
    });

    AppStorage.watch('emotion_state', (emotion: string) => {
      this.currentEmotion = this.getEmotionEnum(emotion);
      this.currentTheme = this.getEmotionColor(emotion);
    });

    AppStorage.watch('environment_params', (params: EnvironmentParameters) => {
      this.envParams = params;
    });

    AppStorage.watch('session_action', (action: string) => {
      if (action === 'toggle_play') {
        // 播放/暂停
      } else if (action === 'set_timer') {
        // 设置计时器
      }
    });

    AppStorage.watch('window_action', (action: string) => {
      if (action === 'open_breath') {
        WindowManager.getInstance().openBreathWindow();
      } else if (action === 'open_diary') {
        WindowManager.getInstance().openDiaryWindow();
      } else if (action === 'open_ambient') {
        WindowManager.getInstance().openAmbientWindow();
      }
    });
  }

  private getEmotionEnum(emotion: string): EmotionState {
    const enums: Map<string, EmotionState> = new Map([
      ['calm', EmotionState.CALM],
      ['anxious', EmotionState.ANXIOUS],
      ['joyful', EmotionState.JOYFUL],
      ['tired', EmotionState.TIRED],
      ['sad', EmotionState.SAD]
    ]);
    return enums.get(emotion) || EmotionState.CALM;
  }

  private getEmotionColor(emotion: string): string {
    const colors: Map<string, string> = new Map([
      ['calm', '#7ED321'],
      ['anxious', '#50E3C2'],
      ['joyful', '#F5A623'],
      ['tired', '#F8E71C'],
      ['sad', '#BD10E0']
    ]);
    return colors.get(emotion) || '#7ED321';
  }

  private getSessionType(index: number): SessionType {
    const types = [SessionType.MEDITATION, SessionType.BREATHING, SessionType.FOCUS, SessionType.SLEEP, SessionType.MEDITATION];
    return types[index] || SessionType.MEDITATION;
  }

  build() {
    Stack() {
      this.buildAmbientLightLayer()

      Column() {
        ImmersiveTitleBar({ 
          currentSession: this.getSessionName(this.currentSession),
          sessionType: this.getSessionType(this.currentSession),
          emotionState: this.currentEmotion 
        })

        Stack() {
          MeditationScene({
            envParams: this.envParams
          })
            .width('100%')
            .height('100%')

          if (this.useFaceAR) {
            EmotionResponsiveEnvironment()
              .width('100%')
              .height('100%')
              .position({ x: 0, y: 0 })
          }
        }
        .layoutWeight(1)
      }
      .width('100%')
      .height('100%')

      FloatTabNavigation({
        currentIndex: this.currentSession,
        onTabChange: (index: number) => {
          this.currentSession = index;
          this.currentTheme = this.getSessionTheme(index);
          this.sessionType = this.getSessionType(index);
          WindowManager.getInstance().syncGlobalLightEffect(this.currentTheme);
        },
        contentBuilder: () => {}
      })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#0a0a0f')
    .expandSafeArea(
      [SafeAreaType.SYSTEM],
      [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM, SafeAreaEdge.START, SafeAreaEdge.END]
    )
  }

  private getSessionName(index: number): string {
    const names = ['晨间冥想', '焦虑舒缓', '专注提升', '睡眠引导', '情绪释放'];
    return names[index] || '晨间冥想';
  }

  private getSessionTheme(index: number): string {
    const themes = ['#7ED321', '#50E3C2', '#F5A623', '#9013FE', '#BD10E0'];
    return themes[index] || '#7ED321';
  }

  @Builder
  buildAmbientLightLayer(): void {
    Column() {
      Column()
        .width(600)
        .height(600)
        .backgroundColor(this.currentTheme)
        .blur(180)
        .opacity(this.lightIntensity * 0.3)
        .position({ x: '50%', y: '25%' })
        .anchor('50%')
        .animation({
          duration: this.envParams.pulseSpeed * 1000,
          curve: Curve.EaseInOut,
          iterations: -1,
          playMode: PlayMode.Alternate
        })
        .scale({ x: 1.4, y: 1.4 })

      Column()
        .width('100%')
        .height(250)
        .backgroundColor(this.currentTheme)
        .opacity(this.lightIntensity * 0.08)
        .blur(120)
        .position({ x: 0, y: '75%' })
        .linearGradient({
          direction: GradientDirection.Top,
          colors: [
            [this.currentTheme, 0.0],
            ['transparent', 1.0]
          ]
        })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#050508')
  }
}

六、关键技术总结

6.1 沉浸光感实现清单

技术点 API/方法 应用场景
系统材质效果 systemMaterialEffect: SystemMaterialEffect.IMMERSIVE HdsNavigation标题栏
背景模糊 backgroundBlurStyle(BlurStyle.REGULAR) 悬浮导航玻璃拟态
背景滤镜 backdropFilter($r('sys.blur.20')) 精细模糊控制
安全区扩展 expandSafeArea([SafeAreaType.SYSTEM], [...]) 全屏沉浸布局
窗口沉浸 setWindowLayoutFullScreen(true) 无边框模式
光效动画 animation({ duration, iterations: -1 }) 呼吸灯/情绪响应
动态透明度 backgroundOpacity 焦点感知降级

6.2 Face AR情绪感知实现要点

技术点 API/方法 说明
AR会话创建 arEngine.createARSession({ mode: ARMode.FACE }) 启用人脸模式
表情跟踪器 session.createFaceTracker({ enableExpression: true }) 启用表情捕捉
多表情融合 expressions.get(FaceExpressionType.SMILE) 微笑+睁眼=愉悦
情绪状态机 自定义情绪判断逻辑 多特征融合决策
环境参数映射 自定义EnvironmentParameters 情绪->色温/亮度/节奏
实时响应 AppStorage.watch('emotion_state') 全局状态驱动UI

6.3 PC端多窗口光效协同

  • 主窗口:全屏沉浸,环境光随情绪状态呼吸变化
  • 浮动工具窗口:置顶、圆角、阴影,跟随主窗口移动
  • 光效同步 :通过AppStorage全局状态实现跨窗口主题色联动
  • 焦点感知:窗口激活时边缘发光增强,失活时自动降低光效强度

七、调试与性能优化

7.1 真机调试建议

  1. Face AR情绪识别:需要良好光线环境,避免逆光或暗光
  2. 呼吸节奏同步:验证光效呼吸与引导提示的一致性
  3. 多窗口情绪同步:验证情绪变化时所有窗口的响应延迟

7.2 性能优化策略

typescript 复制代码
// 1. Face AR性能优化
private optimizeARPerformance(): void {
  if (this.arSession) {
    this.arSession.setCameraConfig({
      fps: 10,
      resolution: { width: 320, height: 240 }
    });
  }
}

// 2. 粒子系统优化
private optimizeParticles(): void {
  // 根据性能动态调整粒子数量
  const maxParticles = this.getDevicePerformance() > 0.7 ? 50 : 25;
}

// 3. 窗口创建优化
private async lazyLoadToolWindows(): Promise<void> {
  if (!this.breathWindow) {
    this.breathWindow = await WindowManager.getInstance().openBreathWindow();
  }
}

八、总结与展望

本文基于HarmonyOS 6(API 23)的悬浮导航沉浸光感Face AR特性,完整实战了一款面向PC端的"心流空间"沉浸式心理疗愈与正念冥想平台。核心创新点总结:

  1. 情绪响应光效系统:根据Face AR识别的实时情绪状态动态切换环境光色、色温、亮度和呼吸节奏,实现"情绪即环境"的响应式疗愈

  2. Face AR情绪感知:基于AR Engine实时捕捉微表情变化,识别平静、焦虑、愉悦、疲惫、悲伤等状态,自动调节疗愈方案

  3. 悬浮场景导航:底部悬浮页签替代传统菜单,玻璃拟态设计+三档透明度调节,在保持导航可达性的同时最大化冥想区域

  4. PC级多窗口疗愈 :主冥想窗口 + 浮动呼吸引导 + 情绪日记 + 白噪音控制的四层架构,通过WindowManager实现跨窗口光效联动与焦点感知

未来扩展方向

  • 接入分布式软总线,实现跨设备协同疗愈(手机情绪监测、平板呼吸引导、PC主控环境)
  • AI疗愈助手:基于长期情绪数据,AI个性化推荐疗愈方案
  • VR/AR融合冥想:支持VR头显接入,实现完全沉浸式的自然场景冥想
  • 生物反馈融合:接入心率、皮电等传感器,实现多模态情绪识别

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

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

相关推荐
想你依然心痛9 小时前
HarmonyOS 6(API 23)实战:基于悬浮导航、沉浸光感与AR双引擎的“量子实验室“——PC端沉浸式科学实验与虚拟仿真平台
华为·ar·harmonyos·悬浮导航·沉浸光感
nashane17 小时前
HarmonyOS Video组件预览图片优化实践:告别黑屏,提升视频播放体验
华为·音视频·harmonyos·harmonyos 5
maaath21 小时前
【maaath】Flutter for OpenHarmony 实战:旅游攻略应用开发指南
flutter·华为·harmonyos
三声三视1 天前
ArkTS 性能优化实战:从卡顿分析到高帧率应用全攻略
华为·性能优化·harmonyos·鸿蒙
小雨青年1 天前
鸿蒙 HarmonyOS 6 | PDFKit预览能力升级实战
华为·harmonyos
花先锋队长1 天前
鸿蒙6.1加持菜鸟App:地理围栏+实况窗,靠近驿站自动提醒,取件不再遗漏
华为·智能手机·harmonyos
nashane1 天前
HarmonyOS 6学习:页面跳转弹窗状态保持全解析
学习·华为·harmonyos·harmonyos 5
maaath1 天前
【maaath】Flutter for OpenHarmony 实战:电影榜单应用开发指南
flutter·华为·harmonyos
若兰幽竹1 天前
【HarmonyOS 6.1 全场景实战】开篇词:打造消除“吃饭焦虑”的《灵犀厨房》
harmonyos·鸿蒙开发·华为鸿蒙系统