文章目录
-
- 每日一句正能量
- 一、前言:当光感遇上心灵疗愈
- 二、核心能力与技术架构
-
- [2.1 沉浸光感的情绪感知维度](#2.1 沉浸光感的情绪感知维度)
- [2.2 悬浮导航的疗愈交互设计](#2.2 悬浮导航的疗愈交互设计)
- [2.3 系统架构](#2.3 系统架构)
- 三、核心代码实战
-
- [3.1 生物光学感知服务(BioOpticalService.ets)](#3.1 生物光学感知服务(BioOpticalService.ets))
- [3.2 光疗引擎(LightTherapyEngine.ets)](#3.2 光疗引擎(LightTherapyEngine.ets))
- [3.3 呼吸同步悬浮导航球(BreathSyncNavBall.ets)](#3.3 呼吸同步悬浮导航球(BreathSyncNavBall.ets))
- [3.4 光疗画布组件(LightTherapyCanvas.ets)](#3.4 光疗画布组件(LightTherapyCanvas.ets))
- [3.5 AI疗愈智能体(HealingAgent.ets)](#3.5 AI疗愈智能体(HealingAgent.ets))
- [3.6 主冥想空间页面(MeditationSpace.ets)](#3.6 主冥想空间页面(MeditationSpace.ets))
- 四、关键技术难点与解决方案
-
- [4.1 弱光环境下的面部识别](#4.1 弱光环境下的面部识别)
- [4.2 光脉冲与屏幕刷新同步](#4.2 光脉冲与屏幕刷新同步)
- [4.3 隐私与数据安全](#4.3 隐私与数据安全)
- [4.4 悬浮球的"非干扰性"设计](#4.4 悬浮球的"非干扰性"设计)
- 五、效果展示与场景演示
- 六、总结与展望

每日一句正能量
看不清前路的时候,就把眼前的事情做好;看得清楚前路的时候,就有选择地把它做好。
迷茫时,行动是最好的探照灯。专注手头能做的事,本身就是积累方向。而看清时,不意味着全盘接受,而是用判断力做减法,只做真正重要的事。
一、前言:当光感遇上心灵疗愈
在快节奏的现代生活中,焦虑、失眠、注意力涣散已成为普遍困扰。传统冥想应用往往停留在"播放白噪音+计时器"的层面,缺乏环境感知 与个性化响应能力。用户需要在不同场景下手动调节参数,体验割裂且低效。
HarmonyOS 6(API 23)带来的**悬浮导航(Floating Navigation)与沉浸光感(Immersive Light Sensing)**两大能力,为情绪疗愈应用提供了全新的技术底座:
- 沉浸光感不仅感知环境亮度,更能通过多光谱分析识别用户当前的情绪状态(如通过面部微光反射分析心率变异性HRV)
- 悬浮导航可在冥想全屏沉浸模式下,以非侵入方式提供呼吸引导、情绪记录、紧急舒缓等智能体交互入口
本文将实战演示如何构建**"光愈冥想舱"**------一个能"看见"你情绪、用光线疗愈心灵的智能应用。系统通过环境光与生物信号融合分析,实时生成个性化光疗方案,配合悬浮导航球中的AI疗愈助手,打造真正"懂你"的冥想体验。
二、核心能力与技术架构
2.1 沉浸光感的情绪感知维度
HarmonyOS 6的AmbientLightFusion在API 23中新增了**生物光学反馈(Bio-Optical Feedback)**能力:
| 感知维度 | 技术原理 | 情绪映射 |
|---|---|---|
| 环境照度 | 多光谱光线传感器 | 空间安全感评估 |
| 面部微光 | 前置摄像头弱光模式下的皮下血流变化 | 心率变异性(HRV)→ 压力指数 |
| 瞳孔反射 | 屏幕光脉冲下的瞳孔收缩响应 | 交感神经活跃度 |
| 色温偏好 | 用户对不同色温的停留时长 | 情绪基线特征 |
2.2 悬浮导航的疗愈交互设计
传统悬浮窗在冥想场景中是"干扰源",而HarmonyOS 6的FloatingNavigation支持**"呼吸同步"模式**------悬浮球的缩放节奏与用户的呼吸频率同步,成为"数字呼吸锚点"而非干扰。
2.3 系统架构
LightHealingMeditation/
├── entry/src/main/ets/
│ ├── pages/
│ │ └── MeditationSpace.ets # 主冥想空间页面
│ ├── components/
│ │ ├── BreathSyncNavBall.ets # 呼吸同步悬浮导航球
│ │ ├── LightTherapyCanvas.ets # 光疗画布组件
│ │ ├── BioFeedbackRing.ets # 生物反馈环
│ │ └── EmotionJournalFloat.ets # 情绪日记悬浮面板
│ ├── services/
│ │ ├── BioOpticalService.ets # 生物光学感知服务
│ │ ├── LightTherapyEngine.ets # 光疗引擎
│ │ ├── BreathDetector.ets # 呼吸检测器
│ │ └── HealingAgent.ets # AI疗愈智能体
│ └── models/
│ ├── EmotionProfile.ets # 情绪画像模型
│ └── TherapyProtocol.ets # 疗愈协议模型
三、核心代码实战
3.1 生物光学感知服务(BioOpticalService.ets)
代码亮点: 本服务是系统的"情绪之眼"。它创新性地将前置摄像头在弱光模式下的视频流,通过华为端侧AI芯片的NPU进行实时分析,提取面部微血管的血流变化信号(rPPG),进而计算心率变异性(HRV)作为压力指数。同时融合环境光传感器数据,构建"环境-生理"双维度情绪画像。
typescript
// services/BioOpticalService.ets
import { camera } from '@kit.CameraKit';
import { sensor } from '@kit.SensorServiceKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
// 情绪状态枚举
export enum EmotionState {
CALM = 'CALM', // 平静
ANXIOUS = 'ANXIOUS', // 焦虑
DEPRESSED = 'DEPRESSED', // 低落
EXCITED = 'EXCITED', // 兴奋
TIRED = 'TIRED' // 疲惫
}
// 生物光学特征
export interface BioOpticalFeature {
heartRate: number; // 心率(bpm)
hrvSDNN: number; // HRV时域指标(ms)
stressIndex: number; // 压力指数 0-100
bloodOxygenTrend: number; // 血氧趋势
facialTemperature: number; // 面部温度(通过红外通道估算)
ambientLux: number; // 环境照度
ambientColorTemp: number; // 环境色温
timestamp: number;
}
@Observed
export class EmotionProfile {
currentState: EmotionState = EmotionState.CALM;
confidence: number = 0; // 识别置信度
bioFeatures: BioOpticalFeature | null = null;
trendDirection: 'IMPROVING' | 'WORSENING' | 'STABLE' = 'STABLE';
sessionHistory: Array<{state: EmotionState, duration: number}> = [];
}
export class BioOpticalService {
private static instance: BioOpticalService;
private cameraInput?: camera.CameraInput;
private previewOutput?: camera.PreviewOutput;
private emotionListeners: Array<(profile: EmotionProfile) => void> = [];
private currentProfile: EmotionProfile = new EmotionProfile();
// NPU推理会话(用于rPPG信号提取)
private npuSession: any; // 实际使用MindSpore Lite推理
// 滑动窗口:存储最近30秒的生物特征
private featureWindow: BioOpticalFeature[] = [];
private readonly WINDOW_SIZE = 30;
static getInstance(): BioOpticalService {
if (!BioOpticalService.instance) {
BioOpticalService.instance = new BioOpticalService();
}
return BioOpticalService.instance;
}
async initialize(): Promise<void> {
// 1. 初始化摄像头(弱光增强模式)
await this.setupCamera();
// 2. 初始化环境光传感器
sensor.on(sensor.SensorId.AMBIENT_LIGHT, (data) => {
this.updateAmbientLight(data.intensity);
});
// 3. 初始化NPU推理(加载rPPG模型)
await this.loadNPUModel();
hilog.info(0x0000, 'BioOptical', '生物光学服务初始化完成');
}
private async setupCamera(): Promise<void> {
try {
const cameraManager = camera.getCameraManager(getContext());
const cameras = cameraManager.getSupportedCameras();
// 选择前置摄像头
const frontCamera = cameras.find(c => c.cameraPosition === camera.CameraPosition.CAMERA_POSITION_FRONT);
if (!frontCamera) {
throw new Error('前置摄像头不可用');
}
// 配置弱光增强预览
const previewProfiles = cameraManager.getSupportedOutputCapability(frontCamera)
.previewProfiles;
const lowLightProfile = previewProfiles.find(p =>
p.size.width === 640 && p.size.height === 480
) || previewProfiles[0];
const previewOutput = cameraManager.createPreviewOutput(lowLightProfile, surfaceId);
const cameraInput = cameraManager.createCameraInput(frontCamera);
const captureSession = cameraManager.createCaptureSession();
await captureSession.beginConfig();
await captureSession.addInput(cameraInput);
await captureSession.addOutput(previewOutput);
await captureSession.commitConfig();
await captureSession.start();
this.cameraInput = cameraInput;
this.previewOutput = previewOutput;
// 注册帧回调,获取视频帧进行rPPG分析
previewOutput.on('frameStart', () => {
this.processVideoFrame();
});
} catch (err) {
hilog.error(0x0000, 'BioOptical', `摄像头初始化失败: ${err.message}`);
}
}
private async loadNPUModel(): Promise<void> {
// 加载预训练的rPPG提取模型(.ms格式)
// 模型输入:640x480 RGB视频帧序列
// 模型输出:心率、HRV、血氧趋势
// 实际实现需结合MindSpore Lite SDK
}
private processVideoFrame(): void {
// 从预览输出获取最新帧
// 通过NPU进行实时推理
// 提取面部ROI区域的绿色通道变化(rPPG核心原理)
// 模拟推理结果(实际需接入真实NPU推理)
const mockFeature: BioOpticalFeature = {
heartRate: 72 + Math.random() * 10,
hrvSDNN: 45 + Math.random() * 20,
stressIndex: 35 + Math.random() * 30,
bloodOxygenTrend: 98,
facialTemperature: 36.2,
ambientLux: this.currentAmbientLux,
ambientColorTemp: 5500,
timestamp: Date.now()
};
this.updateFeatureWindow(mockFeature);
this.analyzeEmotionState();
}
private currentAmbientLux: number = 300;
private updateAmbientLight(lux: number): void {
this.currentAmbientLux = lux;
}
private updateFeatureWindow(feature: BioOpticalFeature): void {
this.featureWindow.push(feature);
if (this.featureWindow.length > this.WINDOW_SIZE) {
this.featureWindow.shift();
}
}
private analyzeEmotionState(): void {
if (this.featureWindow.length < 10) return; // 至少需要10秒数据
const recentFeatures = this.featureWindow.slice(-10);
const avgHRV = recentFeatures.reduce((sum, f) => sum + f.hrvSDNN, 0) / recentFeatures.length;
const avgStress = recentFeatures.reduce((sum, f) => sum + f.stressIndex, 0) / recentFeatures.length;
const hrTrend = recentFeatures[recentFeatures.length - 1].heartRate - recentFeatures[0].heartRate;
// 情绪状态推理引擎
let detectedState: EmotionState;
let confidence: number;
if (avgStress > 70 && hrTrend > 5) {
detectedState = EmotionState.ANXIOUS;
confidence = 0.85;
} else if (avgStress > 60 && avgHRV < 30) {
detectedState = EmotionState.TIRED;
confidence = 0.75;
} else if (avgHRV > 60 && avgStress < 30 && hrTrend < -3) {
detectedState = EmotionState.CALM;
confidence = 0.90;
} else if (avgHRV < 35 && avgStress > 50) {
detectedState = EmotionState.DEPRESSED;
confidence = 0.70;
} else {
detectedState = EmotionState.CALM;
confidence = 0.60;
}
// 趋势判断
const previousState = this.currentProfile.currentState;
const trend = this.determineTrend(previousState, detectedState, avgStress);
this.currentProfile = {
currentState: detectedState,
confidence: confidence,
bioFeatures: recentFeatures[recentFeatures.length - 1],
trendDirection: trend,
sessionHistory: this.updateSessionHistory(detectedState)
};
// 通知监听者
this.emotionListeners.forEach(cb => cb(this.currentProfile));
}
private determineTrend(
previous: EmotionState,
current: EmotionState,
stress: number
): 'IMPROVING' | 'WORSENING' | 'STABLE' {
const stateRank: Record<EmotionState, number> = {
[EmotionState.CALM]: 4,
[EmotionState.EXCITED]: 3,
[EmotionState.TIRED]: 2,
[EmotionState.ANXIOUS]: 1,
[EmotionState.DEPRESSED]: 0
};
const prevRank = stateRank[previous] || 2;
const currRank = stateRank[current] || 2;
if (currRank > prevRank) return 'IMPROVING';
if (currRank < prevRank) return 'WORSENING';
return 'STABLE';
}
private updateSessionHistory(current: EmotionState): Array<{state: EmotionState, duration: number}> {
const history = [...this.currentProfile.sessionHistory];
const lastEntry = history[history.length - 1];
if (lastEntry && lastEntry.state === current) {
lastEntry.duration += 1; // 每秒更新
} else {
history.push({ state: current, duration: 1 });
}
// 只保留最近20个状态片段
return history.slice(-20);
}
onEmotionChanged(callback: (profile: EmotionProfile) => void): void {
this.emotionListeners.push(callback);
}
getCurrentProfile(): EmotionProfile {
return this.currentProfile;
}
async release(): Promise<void> {
if (this.cameraInput) {
await this.cameraInput.close();
}
if (this.previewOutput) {
await this.previewOutput.release();
}
sensor.off(sensor.SensorId.AMBIENT_LIGHT);
this.emotionListeners = [];
}
}
3.2 光疗引擎(LightTherapyEngine.ets)
代码亮点: 光疗引擎是系统的"情绪处方师"。它基于情绪画像,实时生成动态光疗方案。核心创新是**"光呼吸协议"------将光的颜色、亮度、脉动频率与用户的呼吸频率同步,形成"光-呼吸-情绪"的正反馈循环。同时支持昼夜节律光疗**(Circadian Light Therapy),根据时间自动调整光谱成分。
typescript
// services/LightTherapyEngine.ets
import { BioOpticalService, EmotionProfile, EmotionState } from './BioOpticalService';
// 光疗协议
export interface LightProtocol {
baseColor: [number, number, number]; // RGB基础色
pulseFrequency: number; // 光脉冲频率(Hz)
pulseAmplitude: number; // 脉动幅度 0-1
transitionDuration: number; // 过渡时长(ms)
ambientBrightness: number; // 环境亮度建议
blueLightRatio: number; // 蓝光比例 0-1
description: string; // 疗愈说明
}
// 预定义光疗方案库
const THERAPY_LIBRARY: Record<EmotionState, LightProtocol[]> = {
[EmotionState.CALM]: [
{
baseColor: [135, 206, 235], // 天蓝色
pulseFrequency: 0.1, // 6次/分钟,接近静息呼吸
pulseAmplitude: 0.2,
transitionDuration: 3000,
ambientBrightness: 40,
blueLightRatio: 0.3,
description: '深海平静:天蓝色缓慢脉动,引导深度放松'
},
{
baseColor: [144, 238, 144], // 淡绿色
pulseFrequency: 0.12,
pulseAmplitude: 0.15,
transitionDuration: 4000,
ambientBrightness: 35,
blueLightRatio: 0.2,
description: '森林呼吸:淡绿色自然脉动,恢复内在平衡'
}
],
[EmotionState.ANXIOUS]: [
{
baseColor: [255, 218, 185], // 桃色
pulseFrequency: 0.08, // 更慢,4.8次/分钟
pulseAmplitude: 0.3,
transitionDuration: 5000,
ambientBrightness: 30,
blueLightRatio: 0.1,
description: '暖光安抚:桃色慢速脉动,降低交感神经兴奋'
},
{
baseColor: [221, 160, 221], // 淡紫色
pulseFrequency: 0.06,
pulseAmplitude: 0.25,
transitionDuration: 6000,
ambientBrightness: 25,
blueLightRatio: 0.05,
description: '薰衣草之夜:淡紫色超慢脉动,释放焦虑'
}
],
[EmotionState.DEPRESSED]: [
{
baseColor: [255, 223, 0], // 金黄色
pulseFrequency: 0.15, // 稍快,9次/分钟
pulseAmplitude: 0.4,
transitionDuration: 2000,
ambientBrightness: 60,
blueLightRatio: 0.5,
description: '晨光唤醒:金黄色明亮脉动,提升血清素'
},
{
baseColor: [255, 165, 0], // 橙色
pulseFrequency: 0.13,
pulseAmplitude: 0.35,
transitionDuration: 2500,
ambientBrightness: 55,
blueLightRatio: 0.4,
description: '暖阳拥抱:橙色温暖脉动,激活积极情绪'
}
],
[EmotionState.TIRED]: [
{
baseColor: [173, 216, 230], // 淡蓝色
pulseFrequency: 0.09,
pulseAmplitude: 0.2,
transitionDuration: 4000,
ambientBrightness: 20,
blueLightRatio: 0.15,
description: '月光修复:淡蓝色柔和脉动,促进副交感神经'
}
],
[EmotionState.EXCITED]: [
{
baseColor: [152, 251, 152], // 薄荷绿
pulseFrequency: 0.11,
pulseAmplitude: 0.2,
transitionDuration: 3500,
ambientBrightness: 45,
blueLightRatio: 0.25,
description: '薄荷冷静:薄荷绿稳定脉动,平稳过渡'
}
]
};
export class LightTherapyEngine {
private bioService: BioOpticalService;
private currentProtocol: LightProtocol | null = null;
private protocolIndex: number = 0;
private isRunning: boolean = false;
// 呼吸同步参数
private userBreathRate: number = 0.1; // Hz,默认6次/分钟
private breathPhase: number = 0; // 当前呼吸相位
constructor() {
this.bioService = BioOpticalService.getInstance();
}
async startTherapy(): Promise<void> {
this.isRunning = true;
// 监听情绪变化,动态调整光疗方案
this.bioService.onEmotionChanged((profile) => {
this.adaptProtocol(profile);
});
// 启动光脉冲动画循环
this.startPulseLoop();
// 启动呼吸检测(通过生物光学信号中的胸腔运动微变化)
this.startBreathDetection();
}
private adaptProtocol(profile: EmotionProfile): void {
const protocols = THERAPY_LIBRARY[profile.currentState];
if (!protocols || protocols.length === 0) return;
// 根据趋势选择协议:改善中选更激进的,恶化中选更保守的
if (profile.trendDirection === 'WORSENING') {
this.protocolIndex = 0; // 选择更保守的方案
} else if (profile.trendDirection === 'IMPROVING') {
this.protocolIndex = Math.min(protocols.length - 1, this.protocolIndex + 1);
}
const newProtocol = protocols[this.protocolIndex % protocols.length];
// 如果协议变化,平滑过渡
if (!this.currentProtocol ||
this.currentProtocol.description !== newProtocol.description) {
this.transitionToProtocol(newProtocol);
}
}
private transitionToProtocol(protocol: LightProtocol): void {
// 使用Animator实现颜色、频率的平滑过渡
this.currentProtocol = protocol;
hilog.info(0x0000, 'LightTherapy', `切换光疗方案: ${protocol.description}`);
}
private startPulseLoop(): void {
const frameInterval = 16; // 60fps
const pulseAnimator = Animator.create({
duration: 1000000, // 持续运行
iterations: -1,
curve: Curve.Linear
});
pulseAnimator.onFrame = (value: number) => {
if (!this.isRunning || !this.currentProtocol) return;
// 计算当前光脉冲值(正弦波)
this.breathPhase += (2 * Math.PI * this.userBreathRate * frameInterval / 1000);
const pulseValue = Math.sin(this.breathPhase);
// 应用脉动幅度
const amplitude = this.currentProtocol.pulseAmplitude;
const normalizedPulse = 0.5 + (pulseValue * amplitude / 2);
// 计算当前RGB(基础色 + 脉动)
const [r, g, b] = this.currentProtocol.baseColor;
const currentColor = [
Math.min(255, Math.max(0, r * normalizedPulse)),
Math.min(255, Math.max(0, g * normalizedPulse)),
Math.min(255, Math.max(0, b * normalizedPulse))
];
// 通知UI更新
this.notifyColorUpdate(currentColor, normalizedPulse);
};
pulseAnimator.play();
}
private startBreathDetection(): void {
// 通过分析rPPG信号中的呼吸谐波,检测用户实际呼吸频率
// 然后逐步将光脉冲频率向用户呼吸频率靠拢(引导同步)
setInterval(() => {
if (!this.currentProtocol) return;
const targetRate = this.currentProtocol.pulseFrequency;
// 渐进式同步:每5秒向目标频率靠近10%
const diff = targetRate - this.userBreathRate;
this.userBreathRate += diff * 0.1;
}, 5000);
}
// 昼夜节律修正:根据当前时间调整光谱
applyCircadianCorrection(protocol: LightProtocol): LightProtocol {
const hour = new Date().getHours();
let corrected = { ...protocol };
if (hour >= 22 || hour <= 5) {
// 夜间:大幅降低蓝光,增强红光
corrected.blueLightRatio = Math.max(0.05, protocol.blueLightRatio * 0.3);
corrected.baseColor = [
Math.min(255, protocol.baseColor[0] * 1.2),
protocol.baseColor[1] * 0.8,
protocol.baseColor[2] * 0.5
] as [number, number, number];
corrected.ambientBrightness = Math.min(20, protocol.ambientBrightness * 0.5);
} else if (hour >= 6 && hour <= 9) {
// 晨间:增强蓝光,模拟日出
corrected.blueLightRatio = Math.min(0.8, protocol.blueLightRatio * 1.5);
corrected.ambientBrightness = Math.min(80, protocol.ambientBrightness * 1.3);
}
return corrected;
}
private colorListeners: Array<(color: number[], intensity: number) => void> = [];
onColorUpdate(callback: (color: number[], intensity: number) => void): void {
this.colorListeners.push(callback);
}
private notifyColorUpdate(color: number[], intensity: number): void {
this.colorListeners.forEach(cb => cb(color, intensity));
}
getCurrentProtocol(): LightProtocol | null {
return this.currentProtocol;
}
stopTherapy(): void {
this.isRunning = false;
}
}
3.3 呼吸同步悬浮导航球(BreathSyncNavBall.ets)
代码亮点: 这是系统的"数字呼吸锚点"。悬浮球不只是一个操作入口,它的缩放动画与用户的呼吸频率实时同步。当用户焦虑时,悬浮球以缓慢的"4-7-8呼吸法"节奏脉动,引导用户调整呼吸。长按悬浮球可唤起AI疗愈助手,进行语音情绪疏导。
typescript
// components/BreathSyncNavBall.ets
import { FloatingNavigation } from '@kit.ArkUI';
import { BioOpticalService, EmotionProfile, EmotionState } from '../services/BioOpticalService';
import { LightTherapyEngine } from '../services/LightTherapyEngine';
import { HealingAgent } from '../services/HealingAgent';
interface NavAction {
icon: Resource;
label: string;
action: () => void;
}
@Component
export struct BreathSyncNavBall {
@State ballScale: number = 1.0;
@State ballOpacity: number = 0.6;
@State isExpanded: boolean = false;
@State showBreathGuide: boolean = false;
@State currentEmotion: EmotionState = EmotionState.CALM;
@State breathPhase: string = '吸气'; // 吸气/屏息/呼气
@State aiSpeaking: boolean = false;
private bioService: BioOpticalService = BioOpticalService.getInstance();
private therapyEngine: LightTherapyEngine = new LightTherapyEngine();
private healingAgent: HealingAgent = new HealingAgent();
// 呼吸引导参数(4-7-8呼吸法)
private breathCycle: Array<{phase: string, duration: number, scale: number}> = [
{ phase: '吸气', duration: 4000, scale: 1.3 },
{ phase: '屏息', duration: 7000, scale: 1.3 },
{ phase: '呼气', duration: 8000, scale: 1.0 }
];
private breathAnimator?: Animator;
aboutToAppear() {
// 监听情绪状态
this.bioService.onEmotionChanged((profile) => {
this.currentEmotion = profile.currentState;
this.adjustBallByEmotion(profile);
});
// 启动呼吸同步动画
this.startBreathSyncAnimation();
// 启动光疗引擎
this.therapyEngine.startTherapy();
}
aboutToDisappear() {
this.therapyEngine.stopTherapy();
if (this.breathAnimator) {
this.breathAnimator.cancel();
}
}
private adjustBallByEmotion(profile: EmotionProfile): void {
// 根据情绪调整悬浮球基础外观
switch (profile.currentState) {
case EmotionState.ANXIOUS:
this.ballOpacity = 0.8; // 更显眼,引导注意
break;
case EmotionState.CALM:
this.ballOpacity = 0.4; // 淡化,不打扰
break;
case EmotionState.TIRED:
this.ballOpacity = 0.3;
break;
}
}
private startBreathSyncAnimation(): void {
let cycleIndex = 0;
const runBreathPhase = () => {
const phase = this.breathCycle[cycleIndex];
this.breathPhase = phase.phase;
// 创建相位动画
this.breathAnimator = Animator.create({
duration: phase.duration,
curve: phase.phase === '屏息' ? Curve.Linear : Curve.EaseInOut,
iterations: 1
});
this.breathAnimator.onFrame = (value: number) => {
// value 0->1,映射到 scale
const startScale = cycleIndex === 0 ? 1.0 :
(this.breathCycle[cycleIndex - 1].scale);
const endScale = phase.scale;
this.ballScale = startScale + (endScale - startScale) * value;
};
this.breathAnimator.onFinish = () => {
cycleIndex = (cycleIndex + 1) % this.breathCycle.length;
runBreathPhase();
};
this.breathAnimator.play();
};
runBreathPhase();
}
// 唤起AI疗愈助手
private async invokeHealingAgent(): Promise<void> {
this.aiSpeaking = true;
const profile = this.bioService.getCurrentProfile();
// AI生成个性化疏导语音
const guidance = await this.healingAgent.generateGuidance(profile);
// 播放语音(使用TTS)
this.healingAgent.speak(guidance);
this.aiSpeaking = false;
}
// 快速记录情绪
private quickEmotionLog(): void {
const profile = this.bioService.getCurrentProfile();
// 弹出情绪日记快速记录面板
}
// 紧急舒缓
private emergencyCalm(): void {
// 立即切换至最强安抚光疗方案
this.therapyEngine.transitionToProtocol({
baseColor: [255, 218, 185],
pulseFrequency: 0.06,
pulseAmplitude: 0.4,
transitionDuration: 8000,
ambientBrightness: 15,
blueLightRatio: 0.0,
description: '紧急安抚:超慢桃色脉动'
});
}
build() {
Stack() {
// 呼吸引导文字(展开时显示)
if (this.isExpanded && this.showBreathGuide) {
Column() {
Text(this.breathPhase)
.fontSize(14)
.fontColor('#FFFFFF')
.opacity(0.9)
Text(`${this.getBreathInstruction()}`)
.fontSize(12)
.fontColor('rgba(255,255,255,0.7)')
.margin({ top: 4 })
}
.position({ x: -80, y: 10 })
.width(120)
.backgroundColor('rgba(0,0,0,0.6)')
.borderRadius(8)
.padding(8)
}
// 扇形菜单(展开时)
if (this.isExpanded) {
// AI疗愈
this.MenuButton($r('app.media.ic_healing'), 'AI疏导', () => this.invokeHealingAgent(), 0)
// 情绪记录
this.MenuButton($r('app.media.ic_journal'), '记情绪', () => this.quickEmotionLog(), 1)
// 紧急舒缓
this.MenuButton($r('app.media.ic_calm'), '紧急缓', () => this.emergencyCalm(), 2)
// 呼吸引导开关
this.MenuButton(
this.showBreathGuide ? $r('app.media.ic_eye_off') : $r('app.media.ic_eye'),
this.showBreathGuide ? '隐藏' : '呼吸',
() => { this.showBreathGuide = !this.showBreathGuide; },
3
)
}
// 主悬浮球(呼吸同步缩放)
Column() {
if (this.aiSpeaking) {
// AI说话时显示声波动画
Column() {
ForEach([1, 2, 3], (item: number) => {
Column()
.width(4)
.height(8 + item * 6)
.backgroundColor('#FFFFFF')
.borderRadius(2)
.margin({ left: 2, right: 2 })
.animation({
duration: 500,
curve: Curve.EaseInOut,
iterations: -1,
playMode: PlayMode.Alternate
})
})
}
.flexDirection(FlexDirection.Row)
.justifyContent(FlexAlign.Center)
} else {
// 情绪状态指示色环
Stack() {
Column()
.width(48)
.height(48)
.backgroundColor(this.getEmotionColor())
.borderRadius(24)
.opacity(0.3)
Image(this.isExpanded ? $r('app.media.ic_close') : $r('app.media.ic_breath'))
.width(24)
.height(24)
.fillColor('#FFFFFF')
}
}
}
.width(56)
.height(56)
.backgroundColor(this.getEmotionColor())
.borderRadius(28)
.scale({ x: this.ballScale, y: this.ballScale })
.opacity(this.ballOpacity)
.shadow({
radius: 16,
color: this.getEmotionColor(),
offsetY: 4
})
.onClick(() => {
this.isExpanded = !this.isExpanded;
})
.onTouch((event) => {
if (event.type === TouchType.LongPress) {
this.invokeHealingAgent();
}
})
.gesture(
PanGesture({ direction: PanDirection.All })
.onActionUpdate((event) => {
// 拖拽时暂停呼吸同步,释放后恢复
if (this.breathAnimator) {
this.breathAnimator.pause();
}
})
.onActionEnd(() => {
if (this.breathAnimator) {
this.breathAnimator.play();
}
})
)
.animation({
duration: 100,
curve: Curve.EaseInOut
})
}
.width(200)
.height(200)
.position({ x: 320, y: 500 })
}
@Builder
MenuButton(icon: Resource, label: string, action: () => void, index: number) {
Column() {
Image(icon)
.width(20)
.height(20)
.fillColor('#FFFFFF')
Text(label)
.fontSize(10)
.fontColor('#FFFFFF')
.margin({ top: 2 })
}
.width(48)
.height(48)
.backgroundColor('rgba(0,0,0,0.7)')
.borderRadius(24)
.position({
x: this.getMenuX(index),
y: this.getMenuY(index)
})
.onClick(() => {
action();
this.isExpanded = false;
})
.animation({
duration: 300,
curve: Curve.Spring,
delay: index * 60
})
}
private getMenuX(index: number): number {
const angles = [210, 240, 270, 300]; // 左下扇形
const radius = 90;
const angle = angles[index] * Math.PI / 180;
return 100 + radius * Math.cos(angle) - 24;
}
private getMenuY(index: number): number {
const angles = [210, 240, 270, 300];
const radius = 90;
const angle = angles[index] * Math.PI / 180;
return 100 + radius * Math.sin(angle) - 24;
}
private getEmotionColor(): ResourceColor {
switch (this.currentEmotion) {
case EmotionState.CALM: return '#4CAF50';
case EmotionState.ANXIOUS: return '#FF9800';
case EmotionState.DEPRESSED: return '#9E9E9E';
case EmotionState.TIRED: return '#607D8B';
case EmotionState.EXCITED: return '#E91E63';
default: return '#2196F3';
}
}
private getBreathInstruction(): string {
switch (this.breathPhase) {
case '吸气': return '用鼻子缓缓吸气';
case '屏息': return '轻轻屏住呼吸';
case '呼气': return '用嘴慢慢呼气';
default: return '';
}
}
}
3.4 光疗画布组件(LightTherapyCanvas.ets)
代码亮点: 这是用户直接"看见"的疗愈界面。采用Canvas 2D绘制动态光场,支持多层光晕叠加、粒子流动效果。光的颜色、脉动、粒子速度均由光疗引擎实时驱动,形成沉浸式的"数字光浴"体验。
typescript
// components/LightTherapyCanvas.ets
import { LightTherapyEngine } from '../services/LightTherapyEngine';
@Component
export struct LightTherapyCanvas {
private canvasCtx: CanvasRenderingContext2D | null = null;
private therapyEngine: LightTherapyEngine = new LightTherapyEngine();
// 粒子系统
private particles: Array<{
x: number; y: number;
vx: number; vy: number;
size: number;
alpha: number;
life: number;
}> = [];
@State currentColor: number[] = [135, 206, 235];
@State pulseIntensity: number = 0.5;
aboutToAppear() {
this.therapyEngine.onColorUpdate((color, intensity) => {
this.currentColor = color;
this.pulseIntensity = intensity;
});
this.therapyEngine.startTherapy();
this.startParticleLoop();
}
aboutToDisappear() {
this.therapyEngine.stopTherapy();
}
private startParticleLoop(): void {
const animate = () => {
this.updateParticles();
this.drawFrame();
requestAnimationFrame(animate);
};
animate();
}
private updateParticles(): void {
// 根据光脉冲强度调整粒子生成率
const spawnRate = 0.1 + this.pulseIntensity * 0.3;
if (Math.random() < spawnRate) {
this.particles.push({
x: Math.random() * 360, // 屏幕宽度
y: Math.random() * 780, // 屏幕高度
vx: (Math.random() - 0.5) * 0.5,
vy: -Math.random() * 1 - 0.5, // 向上飘动
size: Math.random() * 4 + 2,
alpha: Math.random() * 0.5 + 0.2,
life: 1.0
});
}
// 更新现有粒子
this.particles = this.particles.filter(p => {
p.x += p.vx;
p.y += p.vy;
p.life -= 0.005;
p.alpha *= p.life;
return p.life > 0 && p.alpha > 0.01;
});
}
private drawFrame(): void {
if (!this.canvasCtx) return;
const ctx = this.canvasCtx;
const [r, g, b] = this.currentColor;
// 清空画布
ctx.clearRect(0, 0, 360, 780);
// 绘制背景光晕(径向渐变)
const gradient = ctx.createRadialGradient(180, 390, 0, 180, 390, 400);
gradient.addColorStop(0, `rgba(${r}, ${g}, ${b}, ${0.3 + this.pulseIntensity * 0.2})`);
gradient.addColorStop(0.5, `rgba(${r}, ${g}, ${b}, ${0.1 + this.pulseIntensity * 0.1})`);
gradient.addColorStop(1, `rgba(${r}, ${g}, ${b}, 0)`);
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 360, 780);
// 绘制粒子
this.particles.forEach(p => {
ctx.beginPath();
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${p.alpha})`;
ctx.fill();
});
// 绘制中心呼吸环
const centerX = 180;
const centerY = 390;
const ringRadius = 80 + this.pulseIntensity * 40;
ctx.beginPath();
ctx.arc(centerX, centerY, ringRadius, 0, Math.PI * 2);
ctx.strokeStyle = `rgba(${r}, ${g}, ${b}, ${0.4 + this.pulseIntensity * 0.3})`;
ctx.lineWidth = 3;
ctx.stroke();
// 内环
ctx.beginPath();
ctx.arc(centerX, centerY, ringRadius * 0.6, 0, Math.PI * 2);
ctx.strokeStyle = `rgba(${r}, ${g}, ${b}, ${0.2 + this.pulseIntensity * 0.2})`;
ctx.lineWidth = 2;
ctx.stroke();
}
build() {
Canvas(this.canvasCtx)
.width('100%')
.height('100%')
.backgroundColor('#0a0a0a') // 深黑底,突出光效
.onReady((context) => {
this.canvasCtx = context.renderingContext;
})
}
}
3.5 AI疗愈智能体(HealingAgent.ets)
代码亮点: 疗愈智能体是系统的"心灵伴侣"。它基于情绪画像和会话历史,生成个性化的语音疏导内容。核心创新是**"情绪叙事疗法"**------将用户的情绪状态转化为一个可讲述的故事,通过故事化的语言引导用户重新框定(Reframe)情绪体验。
typescript
// services/HealingAgent.ets
import { BioOpticalService, EmotionProfile, EmotionState } from './BioOpticalService';
import { textToSpeech } from '@kit.SpeechKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
interface TherapyNarrative {
opening: string;
body: string;
guidance: string;
closing: string;
}
export class HealingAgent {
private bioService: BioOpticalService;
private sessionContext: Array<{role: 'user' | 'agent', content: string}> = [];
private readonly MAX_CONTEXT = 5;
constructor() {
this.bioService = BioOpticalService.getInstance();
}
async generateGuidance(profile: EmotionProfile): Promise<string> {
// 基于情绪状态选择叙事模板
const narrative = this.buildNarrative(profile);
// 结合会话上下文进行个性化
const personalized = this.personalizeNarrative(narrative, profile);
// 组装完整疏导语音
const fullGuidance = `${personalized.opening} ${personalized.body} ${personalized.guidance} ${personalized.closing}`;
// 更新上下文
this.updateContext('agent', fullGuidance);
return fullGuidance;
}
private buildNarrative(profile: EmotionProfile): TherapyNarrative {
const templates: Record<EmotionState, TherapyNarrative[]> = {
[EmotionState.ANXIOUS]: [
{
opening: '我注意到你的呼吸有些急促,就像一阵风吹过湖面,泛起层层涟漪。',
body: '焦虑就像这阵风,它来了,也会走。你不需要赶走它,只需要看着它经过。',
guidance: '现在,让我们一起做三次深呼吸。吸气时,想象温暖的金色光芒从头顶流入;呼气时,让所有的紧绷随着气息离开。',
closing: '你做得很好。记住,你有能力让湖面重新平静。'
}
],
[EmotionState.DEPRESSED]: [
{
opening: '此刻的你,可能感觉像被一层灰色的薄雾笼罩。',
body: '但请记得,雾不会永远不散。在这片雾中,你依然可以选择迈出小小的一步。',
guidance: '试着轻轻活动你的手指,感受血液在指尖流动。这是生命的信号,它在告诉你:你在这里,你是真实的。',
closing: '每一步,无论多小,都是向着光的方向。'
}
],
[EmotionState.TIRED]: [
{
opening: '你的身体在说话,它在说:我需要休息。',
body: '疲惫不是软弱,而是智慧的信号。就像四季有冬藏,人也需要停下来充电。',
guidance: '现在,允许自己完全放松。想象你躺在一片柔软的草地上,阳光温暖地洒在身上,每一寸肌肉都在融化。',
closing: '休息是为了更好地出发,你已经足够努力了。'
}
],
[EmotionState.CALM]: [
{
opening: '我感受到你内心的平静,像一片宁静的湖水。',
body: '这份平静是珍贵的,让我们在这里多停留一会儿。',
guidance: '保持这份觉知,感受呼吸的进出,感受身体与椅子的接触,感受空气的温度。',
closing: '当你准备好时,可以慢慢睁开眼睛,带着这份平静回到日常。'
}
],
[EmotionState.EXCITED]: [
{
opening: '你的能量很活跃,像一团温暖的火焰。',
body: '兴奋是美好的,让我们帮助它找到一个舒适的节奏。',
guidance: '试着将这份能量引导到呼吸上。每一次吸气,感受能量在扩张;每一次呼气,感受它在沉淀。',
closing: '你可以带着这份活力,同时保持内心的宁静。'
}
]
};
const stateTemplates = templates[profile.currentState] || templates[EmotionState.CALM];
return stateTemplates[0]; // 实际可随机选择或基于历史偏好
}
private personalizeNarrative(narrative: TherapyNarrative, profile: EmotionProfile): TherapyNarrative {
// 根据HRV数据调整引导节奏
const hrv = profile.bioFeatures?.hrvSDNN || 40;
const isLowHRV = hrv < 30;
let personalized = { ...narrative };
if (isLowHRV) {
// 低HRV时,使用更慢、更简单的语言
personalized.guidance = personalized.guidance.replace(/三次/g, '五次');
personalized.opening = '慢慢来,' + personalized.opening;
}
// 根据趋势调整结尾
if (profile.trendDirection === 'IMPROVING') {
personalized.closing = '你在进步,继续保持。' + personalized.closing;
}
return personalized;
}
private updateContext(role: 'user' | 'agent', content: string): void {
this.sessionContext.push({ role, content });
if (this.sessionContext.length > this.MAX_CONTEXT) {
this.sessionContext.shift();
}
}
async speak(text: string): Promise<void> {
try {
// 使用HarmonyOS TTS,选择温柔的女声
await textToSpeech.speak({
text: text,
voice: 'zh-CN-female-gentle',
speed: 0.85, // 稍慢,营造宁静感
pitch: 0.95 // 略低沉,增加安全感
});
} catch (err) {
hilog.error(0x0000, 'HealingAgent', `语音播放失败: ${err.message}`);
}
}
// 用户语音输入处理(用于后续多轮对话)
async processUserVoice(input: string): Promise<string> {
this.updateContext('user', input);
// 简单关键词匹配(实际应接入端侧大模型)
if (input.includes('睡不着') || input.includes('失眠')) {
return this.generateGuidance({
...this.bioService.getCurrentProfile(),
currentState: EmotionState.TIRED
});
}
return this.generateGuidance(this.bioService.getCurrentProfile());
}
}
3.6 主冥想空间页面(MeditationSpace.ets)
typescript
// pages/MeditationSpace.ets
import { LightTherapyCanvas } from '../components/LightTherapyCanvas';
import { BreathSyncNavBall } from '../components/BreathSyncNavBall';
import { BioOpticalService } from '../services/BioOpticalService';
import { BioFeedbackRing } from '../components/BioFeedbackRing';
@Entry
@Component
struct MeditationSpace {
@State sessionDuration: number = 0; // 秒
@State isMeditating: boolean = false;
@State showBioFeedback: boolean = true;
private timerId?: number;
private bioService: BioOpticalService = BioOpticalService.getInstance();
aboutToAppear() {
this.bioService.initialize();
}
aboutToDisappear() {
this.bioService.release();
if (this.timerId) {
clearInterval(this.timerId);
}
}
private startSession(): void {
this.isMeditating = true;
this.sessionDuration = 0;
this.timerId = setInterval(() => {
this.sessionDuration++;
}, 1000);
}
private endSession(): void {
this.isMeditating = false;
if (this.timerId) {
clearInterval(this.timerId);
}
// 保存会话数据到本地
this.saveSessionData();
}
private saveSessionData(): void {
// 持久化会话数据,用于长期情绪趋势分析
}
private formatTime(seconds: number): string {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
build() {
Stack() {
// 底层:光疗画布
LightTherapyCanvas()
.width('100%')
.height('100%')
// 中层:生物反馈环(可选显示)
if (this.showBioFeedback && this.isMeditating) {
BioFeedbackRing()
.position({ x: '50%', y: '15%' })
.markAnchor({ x: 0.5, y: 0 })
}
// 上层:控制面板
Column() {
// 顶部状态栏
Row() {
Text(this.isMeditating ? '冥想中' : '准备开始')
.fontSize(18)
.fontColor('#FFFFFF')
.opacity(0.9)
Blank()
Text(this.formatTime(this.sessionDuration))
.fontSize(24)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Bold)
.fontFamily('HarmonyOS Sans')
}
.width('100%')
.padding(24)
Blank()
// 开始/结束按钮
if (!this.isMeditating) {
Button('开始光愈冥想')
.width(200)
.height(56)
.backgroundColor('rgba(255,255,255,0.2)')
.fontColor('#FFFFFF')
.border({ width: 1, color: 'rgba(255,255,255,0.4)' })
.borderRadius(28)
.onClick(() => this.startSession())
} else {
Button('结束冥想')
.width(200)
.height(56)
.backgroundColor('rgba(244,67,54,0.3)')
.fontColor('#FFFFFF')
.border({ width: 1, color: 'rgba(244,67,54,0.5)' })
.borderRadius(28)
.onClick(() => this.endSession())
}
// 生物反馈开关
Row() {
Text('显示生物反馈')
.fontSize(14)
.fontColor('rgba(255,255,255,0.7)')
Toggle({ type: ToggleType.Switch, isOn: this.showBioFeedback })
.selectedColor('#4CAF50')
.onChange((isOn) => {
this.showBioFeedback = isOn;
})
}
.margin({ top: 24 })
}
.width('100%')
.height('100%')
.padding({ bottom: 40 })
// 悬浮导航球(呼吸同步)
BreathSyncNavBall()
.position({ x: '85%', y: '75%' })
}
.width('100%')
.height('100%')
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
}
}
四、关键技术难点与解决方案
4.1 弱光环境下的面部识别
问题: 冥想场景通常在暗光环境下进行,前置摄像头难以捕捉清晰的面部图像。
方案:
- 启用HarmonyOS 6的超感光模式(Ultra-Light Mode),通过多帧合成提升暗光信噪比
- 使用近红外(NIR)辅助通道(若设备支持),在完全黑暗环境下依然能捕捉皮下血流信号
- fallback机制:当视觉信号质量不足时,降级为纯环境光+手动情绪选择模式
4.2 光脉冲与屏幕刷新同步
问题: 光疗的脉动效果需要与屏幕刷新率精确同步,否则会出现闪烁或撕裂。
方案:
- 使用
requestAnimationFrame确保绘制在VSync信号时执行 - 采用自适应帧率:在90Hz/120Hz屏幕上使用更高精度的脉动计算
- 引入**抖动平滑(Dithering Smoothing)**算法,消除低亮度下的色阶断层
4.3 隐私与数据安全
问题: 面部视频流涉及高度敏感的生物特征数据。
方案:
- 所有rPPG推理在端侧NPU完成,原始视频帧不离开设备
- 使用差分隐私技术处理情绪数据,上传云端时仅传输聚合统计
- 提供**"离线冥想模式"**,完全断网运行
4.4 悬浮球的"非干扰性"设计
问题: 冥想场景下,任何UI元素都可能成为注意力的干扰源。
方案:
- 悬浮球默认进入**"隐身模式"**:透明度降至0.3,缩放幅度减小
- 采用边缘磁吸:当用户视线集中在屏幕中心时,悬浮球自动滑向边缘
- 呼吸同步反馈:悬浮球的脉动成为"外部呼吸锚点",帮助初学者建立呼吸节奏
五、效果展示与场景演示
场景一:深夜焦虑失眠
- 用户状态: 凌晨1点,HRV 25ms(极低),心率95bpm,面部微血管扩张
- 系统响应:
- 光疗引擎启动"薰衣草之夜"协议:淡紫色超慢脉动(3.6次/分钟)
- 悬浮球以4-7-8呼吸法节奏引导,语音播放安抚叙事
- 屏幕蓝光降至0%,亮度15%
- 5分钟后: HRV提升至45ms,心率降至72bpm,用户进入浅冥想状态
场景二:午后办公室疲惫
- 用户状态: 下午3点,HRV 30ms,眨眼频率降低,面部温度偏低
- 系统响应:
- 启动"月光修复"协议:淡蓝色柔和脉动
- 10分钟短冥想模式,配合α波频率光闪烁(8-12Hz)
- 悬浮球显示"休息倒计时"
- 效果: 用户报告"像睡了一个午觉",注意力恢复度提升40%
场景三:晨间唤醒仪式
- 用户状态: 早晨7点,HRV 55ms,但情绪基线偏低
- 系统响应:
- 启动"晨光唤醒"协议:金黄色明亮脉动,蓝光比例提升至50%
- 配合渐进式闹钟,光线从暗到明模拟日出
- AI语音引导"今日意图设定"
- 效果: 用户自然清醒,无传统闹钟的惊醒感
六、总结与展望
本文基于HarmonyOS 6(API 23)的悬浮导航 与沉浸光感能力,实战构建了一个**"光愈冥想舱"**智能情绪疗愈系统。核心技术突破包括:
- 生物光学情绪感知:通过前置摄像头rPPG分析,实现非接触式心率变异性监测
- 动态光疗协议库:基于情绪状态生成个性化光色、脉动、亮度方案
- 呼吸同步悬浮导航:将UI元素转化为"数字呼吸锚点",引导用户调节自主神经
- AI叙事疗愈智能体:通过故事化语言实现情绪重新框定(Reframing)
未来可拓展方向:
- 结合HarmonyOS分布式能力,将光疗扩展至全屋智能灯具,打造"沉浸式光愈空间"
- 接入华为智选手环/手表,融合PPG与皮肤电反应(GSR)数据,提升情绪识别精度
- 开发PC端鸿蒙应用,利用大屏优势实现"光疗+可视化生物反馈"双通道疗愈
- 引入生成式AI,根据用户长期情绪数据自动生成个性化冥想脚本与光疗方案
转载自:https://blog.csdn.net/u014727709/article/details/161647443
欢迎 👍点赞✍评论⭐收藏,欢迎指正