文章目录
-
- 每日一句正能量
- 前言
- 一、从被动交互到主动感知:AI助手的交互革命
-
- [1.1 传统AI助手的痛点](#1.1 传统AI助手的痛点)
- [1.2 "情绪感知"架构设计](#1.2 "情绪感知"架构设计)
- 二、环境配置与AR引擎初始化
-
- [2.1 模块依赖配置](#2.1 模块依赖配置)
- [2.2 AI助手Ability配置(EmotionAIAbility.ets)](#2.2 AI助手Ability配置(EmotionAIAbility.ets))
- 三、核心组件实战
-
- [3.1 情绪感知悬浮导航(EmotionAdaptiveNav.ets)](#3.1 情绪感知悬浮导航(EmotionAdaptiveNav.ets))
- [3.2 手势控制AI助手面板(GestureAIPanel.ets)](#3.2 手势控制AI助手面板(GestureAIPanel.ets))
- [3.3 主工作台页面:情绪光效与内容整合](#3.3 主工作台页面:情绪光效与内容整合)
- 四、关键技术总结
-
- [4.1 情绪-UI自适应映射体系](#4.1 情绪-UI自适应映射体系)
- [4.2 手势-AI指令映射](#4.2 手势-AI指令映射)
- [4.3 性能与隐私优化](#4.3 性能与隐私优化)
- 五、调试与部署建议
-
- [5.1 真机调试要点](#5.1 真机调试要点)
- [5.2 部署适配](#5.2 部署适配)
- 六、总结与展望

每日一句正能量
别去计较你现在付出了什么,别去怀疑你现在有没有收获,当你全身心地去投入你喜欢的事业时,一切美好的结果,都会在你未来的时光给你最优美的答案!早安!加油!
前言
摘要:HarmonyOS 6(API 23)的 Face AR 与 Body AR 能力不仅适用于娱乐场景,更为生产力工具带来了全新的交互维度。本文将实战开发一款面向 HarmonyOS PC 的"情绪感知智能工作台",通过 Face AR 实时识别用户情绪状态并自适应调整界面光效,通过 Body AR 手势实现无接触式AI助手操控,结合悬浮导航与沉浸光感打造"懂你的"智能办公体验。
一、从被动交互到主动感知:AI助手的交互革命
1.1 传统AI助手的痛点
传统AI助手(如语音助手、聊天机器人)存在明显的交互断裂:
| 痛点 | 表现 | 用户成本 |
|---|---|---|
| 唤醒延迟 | 需要明确的唤醒词或点击 | 打断工作流 |
| 意图误解 | 无法感知用户当前情绪状态 | 反复修正指令 |
| 操作繁琐 | 复杂功能需多层菜单导航 | 记忆负担重 |
| 反馈单调 | 统一的视觉/听觉反馈 | 缺乏情感连接 |
HarmonyOS 6(API 23)的 Face AR 与 Body AR 能力为AI助手带来了"感官升级"------通过摄像头实时捕捉用户面部表情和肢体动作,AI助手能够"看见"用户,从而做出更智能、更贴心的响应 。
1.2 "情绪感知"架构设计
┌─────────────────────────────────────────────────────────────┐
│ 用户感知层(AR Engine) │
│ ┌─────────────────────┐ ┌─────────────────────────────┐ │
│ │ Face AR 情绪识别 │ │ Body AR 手势控制 │ │
│ │ · 68点人脸Mesh │ │ · 20+骨骼关键点 │ │
│ │ · 7种基础情绪分类 │ │ · 6种手势状态识别 │ │
│ │ · 疲劳度检测 │ │ · 姿态角度计算 │ │
│ │ · 注意力评估 │ │ · 空间位置追踪 │ │
│ └──────────┬──────────┘ └──────────────┬──────────────┘ │
└─────────────┼────────────────────────────────┼────────────────┘
│ │
▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ AI决策引擎(端侧大模型) │
│ · 情绪-功能映射:焦虑→降噪模式,专注→深度工作,疲劳→休息提醒 │
│ · 手势-指令映射:单手抬起→唤醒AI,双手张开→全屏展示,捏合→缩放 │
│ · 上下文融合:结合时间、任务进度、历史习惯综合决策 │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 自适应UI层(ArkUI) │
│ ┌─────────────────────┐ ┌─────────────────────────────┐ │
│ │ 沉浸光效自适应 │ │ 悬浮导航动态调整 │ │
│ │ · 情绪色映射 │ │ · 根据手势位置智能避让 │ │
│ │ · 光强随疲劳度衰减 │ │ · 根据注意力集中度简化/丰富 │ │
│ │ · 呼吸灯提醒休息 │ │ · 预测性内容预加载 │ │
│ └─────────────────────┘ └─────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
二、环境配置与AR引擎初始化
2.1 模块依赖配置
json
// oh-package.json5
{
"dependencies": {
"@hms.core.ar.arengine": "^6.1.0",
"@kit.ArkUI": "^6.1.0",
"@kit.AbilityKit": "^6.1.0",
"@kit.SensorServiceKit": "^6.1.0",
"@kit.AIDrawingKit": "^6.1.0"
}
}
2.2 AI助手Ability配置(EmotionAIAbility.ets)
代码亮点:PC端AI助手需要常驻后台监听AR数据,同时支持多窗口浮动交互。配置为自由窗口模式,允许用户在工作时随时呼出AI助手悬浮球。
typescript
// entry/src/main/ets/ability/EmotionAIAbility.ets
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
import { arEngine, ARConfig, ARFeatureType, ARMultiFaceMode } from '@hms.core.ar.arengine';
/**
* 情绪状态枚举
*/
export enum EmotionState {
NEUTRAL = 'neutral', // 平静
HAPPY = 'happy', // 愉悦
SAD = 'sad', // 低落
ANGRY = 'angry', // 愤怒
SURPRISED = 'surprised', // 惊讶
FEAR = 'fear', // 焦虑
DISGUST = 'disgust', // 厌恶
TIRED = 'tired' // 疲劳(自定义状态)
}
/**
* 注意力级别
*/
export enum AttentionLevel {
FOCUSED = 'focused', // 高度专注
NORMAL = 'normal', // 正常
DISTRACTED = 'distracted', // 分心
DROWSY = 'drowsy' // 困倦
}
export default class EmotionAIAbility extends UIAbility {
private arSession: arEngine.ARSession | null = null;
private emotionHistory: Array<{ emotion: EmotionState; timestamp: number }> = [];
private readonly HISTORY_WINDOW = 30000; // 30秒情绪窗口
onWindowStageCreate(windowStage: window.WindowStage): void {
this.setupAIWindow(windowStage);
}
/**
* 配置AI助手窗口:浮动模式,常驻后台
*/
private async setupAIWindow(windowStage: window.WindowStage): Promise<void> {
const mainWindow = windowStage.getMainWindowSync();
// PC端自由窗口:可拖动、可最小化为悬浮球
await mainWindow.setWindowSizeType(window.WindowSizeType.FREE);
await mainWindow.setWindowMode(window.WindowMode.FLOATING);
await mainWindow.setWindowBackgroundColor('#00000000');
await mainWindow.setWindowShadowEnabled(true);
await mainWindow.setWindowCornerRadius(20);
// 初始位置:右下角
await mainWindow.moveWindowTo({ x: 1400, y: 800 });
await mainWindow.resize(400, 600);
windowStage.loadContent('pages/EmotionAIPage', (err) => {
if (err.code) {
console.error('Failed to load AI page:', JSON.stringify(err));
return;
}
this.initializeAREngine();
});
AppStorage.setOrCreate('ai_window', mainWindow);
}
/**
* 初始化AR引擎:情绪识别+手势控制双模态
*/
private async initializeAREngine(): Promise<void> {
try {
const context = getContext(this);
const isReady = await arEngine.isAREngineReady(context);
if (!isReady) return;
this.arSession = new arEngine.ARSession(context);
const config = new ARConfig();
// 启用Face AR(情绪识别)+ Body AR(手势控制)
config.featureType = ARFeatureType.ARENGINE_FEATURE_TYPE_FACE |
ARFeatureType.ARENGINE_FEATURE_TYPE_BODY;
config.multiFaceMode = ARMultiFaceMode.MULTIFACE_ENABLE;
config.maxDetectedBodyNum = 1;
config.cameraLensFacing = arEngine.ARCameraLensFacing.FRONT;
this.arSession.configure(config);
await this.arSession.start();
this.startEmotionAnalysisLoop();
AppStorage.setOrCreate('ar_session', this.arSession);
} catch (error) {
console.error('AR Engine init failed:', error);
}
}
/**
* 情绪分析主循环:实时处理Face AR数据
*/
private startEmotionAnalysisLoop(): void {
const loop = () => {
if (!this.arSession) return;
const frame = this.arSession.acquireFrame();
if (!frame) {
requestAnimationFrame(loop);
return;
}
// 处理Face AR:情绪识别
const faceAnchors = frame.getFaceAnchors ? frame.getFaceAnchors() : [];
if (faceAnchors.length > 0) {
this.analyzeEmotion(faceAnchors[0]);
}
// 处理Body AR:手势+姿态
const bodies = frame.acquireBodySkeleton ? frame.acquireBodySkeleton() : [];
if (bodies.length > 0) {
this.analyzeGestureAndPosture(bodies[0]);
}
frame.release();
requestAnimationFrame(loop);
};
requestAnimationFrame(loop);
}
/**
* 情绪分析核心算法
* 基于BlendShape参数映射到7种基础情绪+疲劳度
*/
private analyzeEmotion(faceAnchor: arEngine.ARFaceAnchor): void {
const face = faceAnchor.getFace();
if (!face) return;
const blendShapes = face.getBlendShapes();
const data = new Float32Array(blendShapes.getData());
const types = blendShapes.getTypes();
// 构建表情参数Map
const expressions = new Map<string, number>();
types.forEach((type, i) => expressions.set(type.toString(), data[i]));
// 情绪识别规则引擎(简化版,实际应使用训练好的模型)
const emotion = this.detectEmotion(expressions);
const fatigue = this.calculateFatigue(expressions);
const attention = this.assessAttention(expressions);
// 更新情绪历史
this.emotionHistory.push({ emotion, timestamp: Date.now() });
this.emotionHistory = this.emotionHistory.filter(
e => Date.now() - e.timestamp < this.HISTORY_WINDOW
);
// 计算稳定情绪(滑动窗口众数)
const stableEmotion = this.calculateStableEmotion();
// 同步到全局状态
AppStorage.setOrCreate('current_emotion', stableEmotion);
AppStorage.setOrCreate('fatigue_level', fatigue);
AppStorage.setOrCreate('attention_level', attention);
AppStorage.setOrCreate('face_detected', true);
// 触发AI决策
this.triggerAIDecision(stableEmotion, fatigue, attention);
}
/**
* 基于BlendShape的情绪检测规则
*/
private detectEmotion(expressions: Map<string, number>): EmotionState {
const jawOpen = expressions.get('JAW_OPEN') || 0;
const mouthSmile = expressions.get('MOUTH_SMILE_LEFT') || 0;
const eyeWide = expressions.get('EYE_WIDE_LEFT') || 0;
const browDown = expressions.get('BROW_DOWN_LEFT') || 0;
const browUp = expressions.get('BROW_UP_LEFT') || 0;
const noseWrinkle = expressions.get('NOSE_WRINKLE_LEFT') || 0;
const mouthFrown = expressions.get('MOUTH_FROWN_LEFT') || 0;
// 优先级判断
if (mouthSmile > 0.6 && eyeWide > 0.3) return EmotionState.HAPPY;
if (mouthFrown > 0.5 && browDown > 0.4) return EmotionState.SAD;
if (browDown > 0.7 && noseWrinkle > 0.3) return EmotionState.ANGRY;
if (eyeWide > 0.7 && jawOpen > 0.3) return EmotionState.SURPRISED;
if (eyeWide > 0.6 && browUp > 0.4) return EmotionState.FEAR;
if (noseWrinkle > 0.6) return EmotionState.DISGUST;
return EmotionState.NEUTRAL;
}
/**
* 疲劳度计算:基于眨眼频率和头部姿态
*/
private calculateFatigue(expressions: Map<string, number>): number {
const eyeBlink = expressions.get('EYE_BLINK_LEFT') || 0;
// 简化:高眨眼频率=疲劳(实际应结合时间窗口统计)
return Math.min(1.0, eyeBlink * 2);
}
/**
* 注意力评估:基于瞳孔注视点稳定性
*/
private assessAttention(expressions: Map<string, number>): AttentionLevel {
// 简化实现,实际应追踪注视点移动速度
const eyeLookUp = expressions.get('EYE_LOOK_UP_LEFT') || 0;
if (eyeLookUp > 0.8) return AttentionLevel.DROWSY;
return AttentionLevel.NORMAL;
}
/**
* 计算稳定情绪(滑动窗口众数)
*/
private calculateStableEmotion(): EmotionState {
if (this.emotionHistory.length === 0) return EmotionState.NEUTRAL;
const counts = new Map<EmotionState, number>();
this.emotionHistory.forEach(e => {
counts.set(e.emotion, (counts.get(e.emotion) || 0) + 1);
});
let maxCount = 0;
let stableEmotion = EmotionState.NEUTRAL;
counts.forEach((count, emotion) => {
if (count > maxCount) {
maxCount = count;
stableEmotion = emotion;
}
});
return stableEmotion;
}
/**
* AI决策触发:根据情绪状态调整系统行为
*/
private triggerAIDecision(emotion: EmotionState, fatigue: number, attention: AttentionLevel): void {
// 疲劳度过高:提醒休息
if (fatigue > 0.8) {
AppStorage.setOrCreate('ai_suggestion', {
type: 'rest',
message: '检测到您比较疲劳,建议休息5分钟',
priority: 'high'
});
}
// 焦虑状态:开启降噪模式
if (emotion === EmotionState.FEAR || emotion === EmotionState.ANGRY) {
AppStorage.setOrCreate('ai_suggestion', {
type: 'focus',
message: '已为您开启专注模式,屏蔽非紧急通知',
priority: 'medium'
});
}
// 愉悦状态:记录高效时段
if (emotion === EmotionState.HAPPY && attention === AttentionLevel.FOCUSED) {
AppStorage.setOrCreate('ai_suggestion', {
type: 'record',
message: '您当前状态很好,已记录为高效时段',
priority: 'low'
});
}
}
/**
* 手势与姿态分析
*/
private analyzeGestureAndPosture(body: arEngine.ARBody): void {
const landmarks = body.getLandmarks2D();
const leftWrist = this.findLandmark(landmarks, arEngine.ARBodyLandmarkType.LEFT_WRIST);
const rightWrist = this.findLandmark(landmarks, arEngine.ARBodyLandmarkType.RIGHT_WRIST);
const leftShoulder = this.findLandmark(landmarks, arEngine.ARBodyLandmarkType.LEFT_SHOULDER);
const rightShoulder = this.findLandmark(landmarks, arEngine.ARBodyLandmarkType.RIGHT_SHOULDER);
const nose = this.findLandmark(landmarks, arEngine.ARBodyLandmarkType.NOSE);
// 手势识别
const gesture = this.recognizeGesture(leftWrist, rightWrist, leftShoulder, rightShoulder);
// 姿态识别
const posture = this.recognizePosture(nose, leftShoulder, rightShoulder);
AppStorage.setOrCreate('body_gesture', gesture);
AppStorage.setOrCreate('body_posture', posture);
AppStorage.setOrCreate('body_detected', true);
// 手势触发AI指令
this.executeGestureCommand(gesture, posture);
}
private findLandmark(landmarks: arEngine.ARBodyLandmark2D[], type: arEngine.ARBodyLandmarkType): arEngine.ARBodyLandmark2D | undefined {
return landmarks.find(lm => lm.type === type && lm.isValid);
}
private recognizeGesture(
leftWrist?: arEngine.ARBodyLandmark2D,
rightWrist?: arEngine.ARBodyLandmark2D,
leftShoulder?: arEngine.ARBodyLandmark2D,
rightShoulder?: arEngine.ARBodyLandmark2D
): { type: string; confidence: number } {
const leftUp = leftWrist && leftShoulder ? leftWrist.y < leftShoulder.y - 50 : false;
const rightUp = rightWrist && rightShoulder ? rightWrist.y < rightShoulder.y - 50 : false;
if (leftUp && rightUp) {
const distance = leftWrist && rightWrist
? Math.sqrt(Math.pow(leftWrist.x - rightWrist.x, 2) + Math.pow(leftWrist.y - rightWrist.y, 2))
: 0;
if (distance < 80) return { type: 'pinch', confidence: 0.9 };
if (distance > 200) return { type: 'spread', confidence: 0.85 };
return { type: 'both_hands_up', confidence: 0.8 };
} else if (leftUp || rightUp) {
return { type: 'point', confidence: 0.75 };
}
return { type: 'idle', confidence: 1.0 };
}
private recognizePosture(
nose?: arEngine.ARBodyLandmark2D,
leftShoulder?: arEngine.ARBodyLandmark2D,
rightShoulder?: arEngine.ARBodyLandmark2D
): { type: string; angle: number } {
if (!nose || !leftShoulder || !rightShoulder) return { type: 'neutral', angle: 0 };
const shoulderCenterY = (leftShoulder.y + rightShoulder.y) / 2;
const offset = nose.y - shoulderCenterY;
if (offset > 60) return { type: 'lean_forward', angle: offset };
if (offset < -40) return { type: 'lean_back', angle: offset };
return { type: 'upright', angle: 0 };
}
/**
* 执行手势指令
*/
private executeGestureCommand(gesture: { type: string }, posture: { type: string }): void {
const now = Date.now();
const lastGesture = AppStorage.get<number>('last_gesture_time') || 0;
// 防抖:500ms内不重复触发
if (now - lastGesture < 500) return;
switch (gesture.type) {
case 'both_hands_up':
// 双手举起:唤醒AI助手
AppStorage.setOrCreate('ai_awaken', now);
AppStorage.setOrCreate('last_gesture_time', now);
break;
case 'pinch':
// 捏合:最小化AI窗口
AppStorage.setOrCreate('ai_minimize', now);
AppStorage.setOrCreate('last_gesture_time', now);
break;
case 'spread':
// 张开:最大化AI窗口
AppStorage.setOrCreate('ai_maximize', now);
AppStorage.setOrCreate('last_gesture_time', now);
break;
}
// 姿态指令
if (posture.type === 'lean_forward') {
AppStorage.setOrCreate('ai_focus_mode', true);
} else if (posture.type === 'lean_back') {
AppStorage.setOrCreate('ai_focus_mode', false);
}
}
onWindowStageDestroy(): void {
if (this.arSession) {
this.arSession.stop();
this.arSession = null;
}
}
}
三、核心组件实战
3.1 情绪感知悬浮导航(EmotionAdaptiveNav.ets)
代码亮点:导航栏根据用户情绪状态动态调整颜色、透明度和动画速度。焦虑时切换冷色调+低透明度减少干扰,愉悦时切换暖色调+动态光效增强反馈,疲劳时自动简化导航项并提醒休息 。
typescript
// components/EmotionAdaptiveNav.ets
import { window } from '@kit.ArkUI';
import { EmotionState, AttentionLevel } from '../ability/EmotionAIAbility';
/**
* 情绪-UI映射配置
*/
interface EmotionUIConfig {
primaryColor: string;
secondaryColor: string;
navTransparency: number;
animationSpeed: number;
glowIntensity: number;
showRestReminder: boolean;
simplifyUI: boolean;
}
@Component
export struct EmotionAdaptiveNav {
@State currentEmotion: EmotionState = EmotionState.NEUTRAL;
@State fatigueLevel: number = 0;
@State attentionLevel: AttentionLevel = AttentionLevel.NORMAL;
@State navExpanded: boolean = false;
@State bottomAvoidHeight: number = 0;
@State aiSuggestion: { type: string; message: string; priority: string } | null = null;
// 情绪-UI配置映射表
private emotionConfigMap: Record<EmotionState, EmotionUIConfig> = {
[EmotionState.NEUTRAL]: {
primaryColor: '#4A90E2',
secondaryColor: '#5BA0F2',
navTransparency: 0.7,
animationSpeed: 1.0,
glowIntensity: 0.5,
showRestReminder: false,
simplifyUI: false
},
[EmotionState.HAPPY]: {
primaryColor: '#FF9500',
secondaryColor: '#FFB84D',
navTransparency: 0.75,
animationSpeed: 1.2,
glowIntensity: 0.8,
showRestReminder: false,
simplifyUI: false
},
[EmotionState.SAD]: {
primaryColor: '#5B8BD4',
secondaryColor: '#7BA3E0',
navTransparency: 0.6,
animationSpeed: 0.8,
glowIntensity: 0.3,
showRestReminder: true,
simplifyUI: true
},
[EmotionState.ANGRY]: {
primaryColor: '#E74C3C',
secondaryColor: '#FF6B6B',
navTransparency: 0.85,
animationSpeed: 0.5,
glowIntensity: 0.9,
showRestReminder: true,
simplifyUI: true
},
[EmotionState.SURPRISED]: {
primaryColor: '#9B59B6',
secondaryColor: '#BB8FCE',
navTransparency: 0.8,
animationSpeed: 1.5,
glowIntensity: 0.7,
showRestReminder: false,
simplifyUI: false
},
[EmotionState.FEAR]: {
primaryColor: '#2ECC71',
secondaryColor: '#58D68D',
navTransparency: 0.55,
animationSpeed: 0.6,
glowIntensity: 0.4,
showRestReminder: true,
simplifyUI: true
},
[EmotionState.DISGUST]: {
primaryColor: '#34495E',
secondaryColor: '#5D6D7E',
navTransparency: 0.65,
animationSpeed: 0.7,
glowIntensity: 0.3,
showRestReminder: false,
simplifyUI: false
},
[EmotionState.TIRED]: {
primaryColor: '#95A5A6',
secondaryColor: '#B2BABB',
navTransparency: 0.5,
animationSpeed: 0.4,
glowIntensity: 0.2,
showRestReminder: true,
simplifyUI: true
}
};
private navItems = [
{ id: 'home', icon: $r('app.media.ic_home'), label: '首页' },
{ id: 'tasks', icon: $r('app.media.ic_tasks'), label: '任务' },
{ id: 'ai', icon: $r('app.media.ic_ai'), label: 'AI助手' },
{ id: 'focus', icon: $r('app.media.ic_focus'), label: '专注' },
{ id: 'profile', icon: $r('app.media.ic_profile'), label: '我的' }
];
aboutToAppear(): void {
this.getBottomAvoidArea();
this.setupEmotionListening();
}
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);
}
}
private setupEmotionListening(): void {
AppStorage.watch('current_emotion', (emotion: EmotionState) => {
if (emotion !== this.currentEmotion) {
this.currentEmotion = emotion;
this.triggerEmotionTransition();
}
});
AppStorage.watch('fatigue_level', (level: number) => {
this.fatigueLevel = level;
});
AppStorage.watch('attention_level', (level: AttentionLevel) => {
this.attentionLevel = level;
});
AppStorage.watch('ai_suggestion', (suggestion: { type: string; message: string }) => {
this.aiSuggestion = suggestion;
// 高优先级建议自动展开导航显示
if (suggestion && suggestion.priority === 'high') {
this.navExpanded = true;
setTimeout(() => this.navExpanded = false, 5000);
}
});
}
/**
* 情绪切换过渡动画
*/
private triggerEmotionTransition(): void {
// 触发全屏光效过渡
AppStorage.setOrCreate('emotion_transition', {
from: this.currentEmotion,
to: this.currentEmotion,
timestamp: Date.now()
});
}
/**
* 获取当前情绪对应的UI配置
*/
private getCurrentConfig(): EmotionUIConfig {
return this.emotionConfigMap[this.currentEmotion] || this.emotionConfigMap[EmotionState.NEUTRAL];
}
build() {
const config = this.getCurrentConfig();
Stack({ alignContent: Alignment.Bottom }) {
// 内容区域
Column() {
this.contentBuilder()
}
.width('100%')
.height('100%')
.padding({ bottom: this.bottomAvoidHeight + 100 })
// AI建议浮层(高优先级时显示)
if (this.aiSuggestion && this.aiSuggestion.priority === 'high') {
this.buildAISuggestionCard()
}
// 情绪自适应悬浮导航
Column() {
Stack() {
// 情绪色光晕背景
Column()
.width('100%')
.height('100%')
.backgroundColor(config.primaryColor)
.opacity(config.glowIntensity * 0.3)
.blur(80)
.animation({
duration: 3000 / config.animationSpeed,
curve: Curve.EaseInOut,
iterations: -1,
playMode: PlayMode.Alternate
})
// 毛玻璃主体
Column()
.width('100%')
.height('100%')
.backgroundBlurStyle(BlurStyle.COMPONENT_THICK)
.opacity(config.navTransparency)
// 高光层
Column()
.width('100%')
.height('100%')
.linearGradient({
direction: GradientDirection.Top,
colors: [
['rgba(255,255,255,0.2)', 0.0],
['rgba(255,255,255,0.05)', 0.5],
['transparent', 1.0]
]
})
}
.width('100%')
.height('100%')
.borderRadius(24)
.shadow({
radius: 20,
color: config.primaryColor + (Math.floor(config.glowIntensity * 255).toString(16).padStart(2, '0')),
offsetX: 0,
offsetY: -4
})
// 导航内容
Column() {
// 情绪状态指示器
Row({ space: 8 }) {
// 情绪色点(呼吸动画)
Column()
.width(8)
.height(8)
.backgroundColor(config.primaryColor)
.borderRadius(4)
.shadow({ radius: 6, color: config.primaryColor })
.animation({
duration: 2000 / config.animationSpeed,
curve: Curve.EaseInOut,
iterations: -1,
playMode: PlayMode.Alternate
})
.scale({ x: 1.5, y: 1.5 })
Text(this.getEmotionLabel(this.currentEmotion))
.fontSize(11)
.fontColor(config.primaryColor)
.fontWeight(FontWeight.Medium)
// 疲劳度指示条
if (this.fatigueLevel > 0.3) {
Row() {
Column()
.width(`${this.fatigueLevel * 100}%`)
.height(4)
.backgroundColor(this.fatigueLevel > 0.7 ? '#FF4444' : '#FFAA00')
.borderRadius(2)
}
.width(40)
.height(4)
.backgroundColor('rgba(255,255,255,0.1)')
.borderRadius(2)
}
}
.width('100%')
.justifyContent(FlexAlign.Center)
.padding({ top: 8, bottom: 4 })
// 导航项(根据情绪简化)
Row({ space: 0 }) {
ForEach(this.getVisibleNavItems(config.simplifyUI), (item: {id: string; icon: Resource; label: string}) => {
Column({ space: 4 }) {
Stack() {
// 选中光晕
if (this.getSelectedNavId() === item.id) {
Column()
.width(48)
.height(48)
.backgroundColor(config.primaryColor)
.borderRadius(16)
.opacity(0.25)
.blur(10)
.animation({
duration: 1500 / config.animationSpeed,
curve: Curve.EaseInOut,
iterations: -1,
playMode: PlayMode.Alternate
})
}
Image(item.icon)
.width(24)
.height(24)
.fillColor(this.getSelectedNavId() === item.id ? '#FFFFFF' : 'rgba(255,255,255,0.6)')
}
.width(48)
.height(48)
.justifyContent(FlexAlign.Center)
if (!config.simplifyUI) {
Text(item.label)
.fontSize(10)
.fontColor(this.getSelectedNavId() === item.id ? '#FFFFFF' : 'rgba(255,255,255,0.5)')
}
}
.width(60)
.height(config.simplifyUI ? 56 : 68)
.onClick(() => this.handleNavClick(item.id))
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceAround)
.padding({ left: 8, right: 8, bottom: 8 })
// 休息提醒(疲劳时显示)
if (config.showRestReminder && this.fatigueLevel > 0.6) {
this.buildRestReminder(config)
}
}
.width('100%')
.height('100%')
}
.width('92%')
.height(this.navExpanded ? 160 : (config.showRestReminder && this.fatigueLevel > 0.6 ? 120 : 90))
.margin({
bottom: this.bottomAvoidHeight + 16,
left: '4%',
right: '4%'
})
.animation({
duration: 400,
curve: Curve.Spring,
iterations: 1
})
.gesture(
LongPressGesture({ duration: 500 })
.onAction(() => this.navExpanded = !this.navExpanded)
)
}
.width('100%')
.height('100%')
}
/**
* 根据情绪状态决定显示的导航项
* 焦虑/疲劳时简化UI,只显示核心功能
*/
private getVisibleNavItems(simplify: boolean): Array<{id: string; icon: Resource; label: string}> {
if (simplify) {
// 简化模式:只保留首页、AI助手、我的
return this.navItems.filter(item => ['home', 'ai', 'profile'].includes(item.id));
}
return this.navItems;
}
private getSelectedNavId(): string {
return AppStorage.get<string>('selected_nav_id') || 'home';
}
private handleNavClick(id: string): void {
AppStorage.setOrCreate('selected_nav_id', id);
// 触发选中反馈(根据情绪调整强度)
const config = this.getCurrentConfig();
this.triggerSelectFeedback(config.animationSpeed);
}
private triggerSelectFeedback(speed: number): void {
try {
import('@kit.SensorServiceKit').then(sensor => {
sensor.vibrator.startVibration({
type: 'time',
duration: Math.round(30 / speed)
}, { id: 0 });
});
} catch (error) {
console.error('Haptic feedback failed:', error);
}
}
private getEmotionLabel(emotion: EmotionState): string {
const labels: Record<EmotionState, string> = {
[EmotionState.NEUTRAL]: '平静',
[EmotionState.HAPPY]: '愉悦',
[EmotionState.SAD]: '低落',
[EmotionState.ANGRY]: '烦躁',
[EmotionState.SURPRISED]: '惊讶',
[EmotionState.FEAR]: '焦虑',
[EmotionState.DISGUST]: '不适',
[EmotionState.TIRED]: '疲劳'
};
return labels[emotion] || '平静';
}
@Builder
buildAISuggestionCard(): void {
Column({ space: 8 }) {
Row({ space: 8 }) {
Image($r('app.media.ic_ai'))
.width(20)
.height(20)
.fillColor('#FFFFFF')
Text('AI助手建议')
.fontSize(12)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Medium)
Text('刚刚')
.fontSize(10)
.fontColor('rgba(255,255,255,0.5)')
.layoutWeight(1)
}
.width('100%')
Text(this.aiSuggestion?.message || '')
.fontSize(13)
.fontColor('#FFFFFF')
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Row({ space: 8 }) {
Button('立即执行')
.type(ButtonType.Capsule)
.fontSize(12)
.fontColor('#FFFFFF')
.backgroundColor('rgba(255,255,255,0.2)')
.height(32)
.onClick(() => this.executeAISuggestion())
Button('忽略')
.type(ButtonType.Capsule)
.fontSize(12)
.fontColor('rgba(255,255,255,0.6)')
.backgroundColor('transparent')
.height(32)
.onClick(() => {
this.aiSuggestion = null;
})
}
.width('100%')
.justifyContent(FlexAlign.End)
}
.width('85%')
.padding(16)
.backgroundColor('rgba(74,144,226,0.9)')
.borderRadius(16)
.shadow({ radius: 16, color: 'rgba(74,144,226,0.4)' })
.margin({ bottom: 12 })
.position({ x: '7.5%', y: undefined })
.animation({
duration: 300,
curve: Curve.Spring
})
}
@Builder
buildRestReminder(config: EmotionUIConfig): void {
Row({ space: 8 }) {
Image($r('app.media.ic_rest'))
.width(16)
.height(16)
.fillColor('#FFAA00')
Text(`疲劳度 ${Math.round(this.fatigueLevel * 100)}%,建议休息`)
.fontSize(11)
.fontColor('#FFAA00')
Button('开始休息')
.type(ButtonType.Capsule)
.fontSize(10)
.fontColor('#FFFFFF')
.backgroundColor('#FFAA00')
.height(24)
.padding({ left: 12, right: 12 })
.onClick(() => this.startRestMode())
}
.width('100%')
.justifyContent(FlexAlign.Center)
.padding({ top: 4, bottom: 4 })
}
private executeAISuggestion(): void {
// 执行AI建议的具体逻辑
this.aiSuggestion = null;
}
private startRestMode(): void {
AppStorage.setOrCreate('rest_mode_active', true);
}
@BuilderParam contentBuilder: () => void = this.defaultContentBuilder;
@Builder
defaultContentBuilder(): void {
Column() {
Text('AI工作台内容区')
.fontSize(16)
.fontColor('#FFFFFF40')
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
3.2 手势控制AI助手面板(GestureAIPanel.ets)
代码亮点:AI助手面板响应Body AR手势指令,双手举起唤醒、捏合最小化、张开最大化,无需点击即可操控。面板位置智能避让手势活动区域 。
typescript
// components/GestureAIPanel.ets
import { window } from '@kit.ArkUI';
@Component
export struct GestureAIPanel {
@State panelVisible: boolean = false;
@State panelMinimized: boolean = false;
@State panelPosition: { x: number; y: number } = { x: 1200, y: 600 };
@State panelSize: { width: number; height: number } = { width: 380, height: 520 };
@State gestureState: string = 'idle';
@State focusMode: boolean = false;
private aiWindow: window.Window | null = null;
aboutToAppear(): void {
this.setupGestureListening();
this.initializeAIWindow();
}
private setupGestureListening(): void {
// 监听唤醒手势
AppStorage.watch('ai_awaken', (timestamp: number) => {
if (timestamp) {
this.panelVisible = true;
this.panelMinimized = false;
this.animatePanelEntry();
}
});
// 监听最小化手势
AppStorage.watch('ai_minimize', (timestamp: number) => {
if (timestamp) {
this.panelMinimized = true;
}
});
// 监听最大化手势
AppStorage.watch('ai_maximize', (timestamp: number) => {
if (timestamp) {
this.panelMinimized = false;
}
});
// 监听专注模式
AppStorage.watch('ai_focus_mode', (active: boolean) => {
this.focusMode = active;
if (active) {
// 专注模式:AI面板自动靠边,减少干扰
this.panelPosition = { x: 1600, y: 200 };
}
});
// 监听手势状态(用于UI反馈)
AppStorage.watch('body_gesture', (gesture: { type: string; confidence: number }) => {
this.gestureState = gesture.type;
});
}
private async initializeAIWindow(): Promise<void> {
try {
const mainWindow = await window.getLastWindow();
this.aiWindow = await mainWindow.createSubWindow('AIAssistant');
await this.aiWindow.setWindowSizeType(window.WindowSizeType.FREE);
await this.aiWindow.moveWindowTo(this.panelPosition);
await this.aiWindow.resize(this.panelSize.width, this.panelSize.height);
await this.aiWindow.setWindowBackgroundColor('#00000000');
await this.aiWindow.setWindowShadowEnabled(true);
await this.aiWindow.setWindowCornerRadius(20);
await this.aiWindow.setWindowTopmost(true);
// 加载AI助手内容
await this.aiWindow.setUIContent('pages/AIChatPage');
} catch (error) {
console.error('AI window init failed:', error);
}
}
private animatePanelEntry(): void {
// 入场动画:从底部滑入+光效脉冲
this.panelPosition = { x: this.panelPosition.x, y: 800 };
setTimeout(() => {
this.panelPosition = { x: 1200, y: 600 };
}, 50);
}
build() {
if (!this.panelVisible) {
// 隐藏状态:显示悬浮唤醒按钮
this.buildAwakenButton();
return;
}
if (this.panelMinimized) {
// 最小化状态:显示为悬浮球
this.buildMinimizedBall();
return;
}
// 正常状态:完整AI面板
this.buildFullPanel();
}
@Builder
buildAwakenButton(): void {
Stack({ alignContent: Alignment.BottomEnd }) {
Column()
.width(56)
.height(56)
.backgroundColor('rgba(74,144,226,0.8)')
.borderRadius(28)
.shadow({ radius: 12, color: 'rgba(74,144,226,0.4)' })
.margin({ right: 24, bottom: 120 })
.onClick(() => {
this.panelVisible = true;
})
.gesture(
// 支持手势唤醒:在按钮区域做举起手势
PanGesture()
.onActionStart(() => {
this.panelVisible = true;
})
)
Image($r('app.media.ic_ai'))
.width(28)
.height(28)
.fillColor('#FFFFFF')
.margin({ right: 24, bottom: 120 })
}
.width('100%')
.height('100%')
}
@Builder
buildMinimizedBall(): void {
Stack({ alignContent: Alignment.BottomEnd }) {
Column()
.width(48)
.height(48)
.backgroundColor('rgba(74,144,226,0.6)')
.borderRadius(24)
.shadow({ radius: 8, color: 'rgba(74,144,226,0.3)' })
.margin({ right: 24, bottom: 120 })
.onClick(() => {
this.panelMinimized = false;
})
.animation({
duration: 2000,
curve: Curve.EaseInOut,
iterations: -1,
playMode: PlayMode.Alternate
})
.scale({ x: 1.1, y: 1.1 })
// 手势状态指示环
if (this.gestureState !== 'idle') {
Column()
.width(56)
.height(56)
.borderWidth(2)
.borderColor('#00FF88')
.borderRadius(28)
.margin({ right: 20, bottom: 116 })
.animation({
duration: 1000,
curve: Curve.EaseInOut,
iterations: -1,
playMode: PlayMode.Alternate
})
.opacity({ x: 0.3, y: 1.0 })
}
}
.width('100%')
.height('100%')
}
@Builder
buildFullPanel(): void {
Stack() {
// 面板主体
Column() {
// 玻璃拟态背景
Stack() {
Column()
.width('100%')
.height('100%')
.backgroundColor(this.focusMode ? '#2C3E50' : '#1a1a2e')
.opacity(0.9)
.blur(40)
Column()
.width('100%')
.height('100%')
.backgroundBlurStyle(BlurStyle.COMPONENT_THICK)
// 顶部光效
Column()
.width('100%')
.height(60)
.linearGradient({
direction: GradientDirection.Top,
colors: [
['rgba(74,144,226,0.2)', 0.0],
['transparent', 1.0]
]
})
}
.width('100%')
.height('100%')
.borderRadius(20)
// 面板内容
Column({ space: 0 }) {
// 标题栏(支持拖拽移动)
this.buildPanelHeader()
// AI对话区域
this.buildAIChatArea()
// 手势状态指示器
this.buildGestureIndicator()
// 输入区域
this.buildInputArea()
}
.width('100%')
.height('100%')
.padding(16)
}
.width(this.panelSize.width)
.height(this.panelSize.height)
.position({
x: this.panelPosition.x,
y: this.panelPosition.y
})
.shadow({
radius: 24,
color: 'rgba(0,0,0,0.3)',
offsetX: 0,
offsetY: 8
})
.animation({
duration: 300,
curve: Curve.Spring
})
.gesture(
PanGesture()
.onActionUpdate((event: GestureEvent) => {
// 拖拽移动面板
this.panelPosition = {
x: this.panelPosition.x + event.offsetX,
y: this.panelPosition.y + event.offsetY
};
})
)
}
.width('100%')
.height('100%')
}
@Builder
buildPanelHeader(): void {
Row({ space: 8 }) {
// AI头像(带情绪感知光晕)
Stack() {
Column()
.width(40)
.height(40)
.backgroundColor('#4A90E2')
.borderRadius(20)
.opacity(0.3)
.blur(8)
.animation({
duration: 2000,
curve: Curve.EaseInOut,
iterations: -1,
playMode: PlayMode.Alternate
})
Image($r('app.media.ic_ai_avatar'))
.width(36)
.height(36)
.borderRadius(18)
}
Column() {
Text('AI助手')
.fontSize(15)
.fontColor('#FFFFFF')
.fontWeight(FontWeight.Medium)
Text('情绪感知模式已开启')
.fontSize(11)
.fontColor('#4A90E2')
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
// 窗口控制
Row({ space: 8 }) {
Button() { Text('−').fontColor('#FFFFFF').fontSize(14) }
.type(ButtonType.Circle)
.backgroundColor('rgba(255,255,255,0.1)')
.width(28)
.height(28)
.onClick(() => this.panelMinimized = true)
Button() { Text('×').fontColor('#FFFFFF').fontSize(14) }
.type(ButtonType.Circle)
.backgroundColor('rgba(255,255,255,0.1)')
.width(28)
.height(28)
.onClick(() => this.panelVisible = false)
}
}
.width('100%')
.height(56)
.padding({ bottom: 12 })
}
@Builder
buildAIChatArea(): void {
Column() {
// 对话内容(简化示例)
List({ space: 12 }) {
ListItem() {
this.buildAIMessage('检测到您当前状态比较专注,需要我帮您屏蔽干扰通知吗?', 'suggest')
}
ListItem() {
this.buildUserMessage('好的,开启专注模式')
}
ListItem() {
this.buildAIMessage('已为您开启专注模式,当前任务进度:文档编辑 65%,预计还需 12 分钟', 'info')
}
}
.width('100%')
.layoutWeight(1)
.scrollBar(BarState.Auto)
}
.width('100%')
.layoutWeight(1)
.padding({ top: 8, bottom: 8 })
}
@Builder
buildAIMessage(content: string, type: string): void {
Row({ space: 8 }) {
Column()
.width(32)
.height(32)
.backgroundColor('#4A90E2')
.borderRadius(16)
Column() {
Text(content)
.fontSize(13)
.fontColor('#FFFFFF')
.maxLines(5)
.textOverflow({ overflow: TextOverflow.Ellipsis })
if (type === 'suggest') {
Row({ space: 8 }) {
Button('接受')
.type(ButtonType.Capsule)
.fontSize(12)
.fontColor('#FFFFFF')
.backgroundColor('#4A90E2')
.height(28)
Button('忽略')
.type(ButtonType.Capsule)
.fontSize(12)
.fontColor('rgba(255,255,255,0.6)')
.backgroundColor('transparent')
.height(28)
}
.margin({ top: 8 })
}
}
.layoutWeight(1)
.padding(12)
.backgroundColor('rgba(255,255,255,0.05)')
.borderRadius(12)
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.padding({ right: 40 })
}
@Builder
buildUserMessage(content: string): void {
Row({ space: 8 }) {
Column()
.layoutWeight(1)
Column() {
Text(content)
.fontSize(13)
.fontColor('#FFFFFF')
}
.padding(12)
.backgroundColor('#4A90E2')
.borderRadius(12)
}
.width('100%')
.padding({ left: 40 })
}
@Builder
buildGestureIndicator(): void {
Row({ space: 8 }) {
// 当前手势图标
Text(this.getGestureIcon(this.gestureState))
.fontSize(20)
Text(this.getGestureLabel(this.gestureState))
.fontSize(11)
.fontColor('rgba(255,255,255,0.5)')
}
.width('100%')
.height(32)
.justifyContent(FlexAlign.Center)
.backgroundColor('rgba(0,0,0,0.2)')
.borderRadius(8)
.margin({ bottom: 8 })
.visibility(this.gestureState !== 'idle' ? Visibility.Visible : Visibility.Hidden)
}
@Builder
buildInputArea(): void {
Row({ space: 8 }) {
TextInput({ placeholder: '输入指令,或用手势控制...' })
.placeholderColor('rgba(255,255,255,0.3)')
.fontColor('#FFFFFF')
.backgroundColor('rgba(255,255,255,0.05)')
.borderRadius(20)
.height(40)
.layoutWeight(1)
Button() {
Image($r('app.media.ic_send'))
.width(20)
.height(20)
.fillColor('#FFFFFF')
}
.type(ButtonType.Circle)
.backgroundColor('#4A90E2')
.width(40)
.height(40)
}
.width('100%')
.height(56)
}
private getGestureIcon(gesture: string): string {
const icons: Record<string, string> = {
'idle': '✋',
'point': '👆',
'pinch': '🤏',
'spread': '🙌',
'both_hands_up': '🙋'
};
return icons[gesture] || '✋';
}
private getGestureLabel(gesture: string): string {
const labels: Record<string, string> = {
'idle': '等待手势',
'point': '单手指示',
'pinch': '捏合缩放',
'spread': '张开放大',
'both_hands_up': '双手唤醒'
};
return labels[gesture] || '等待手势';
}
}
3.3 主工作台页面:情绪光效与内容整合
typescript
// pages/EmotionAIPage.ets
import { EmotionAdaptiveNav } from '../components/EmotionAdaptiveNav';
import { GestureAIPanel } from '../components/GestureAIPanel';
import { EmotionState } from '../ability/EmotionAIAbility';
@Entry
@Component
struct EmotionAIPage {
@State currentEmotion: EmotionState = EmotionState.NEUTRAL;
@State themeColor: string = '#4A90E2';
@State restModeActive: boolean = false;
aboutToAppear(): void {
AppStorage.watch('current_emotion', (emotion: EmotionState) => {
this.currentEmotion = emotion;
this.updateThemeColor(emotion);
});
AppStorage.watch('rest_mode_active', (active: boolean) => {
this.restModeActive = active;
});
}
private updateThemeColor(emotion: EmotionState): void {
const colorMap: Record<EmotionState, string> = {
[EmotionState.NEUTRAL]: '#4A90E2',
[EmotionState.HAPPY]: '#FF9500',
[EmotionState.SAD]: '#5B8BD4',
[EmotionState.ANGRY]: '#E74C3C',
[EmotionState.SURPRISED]: '#9B59B6',
[EmotionState.FEAR]: '#2ECC71',
[EmotionState.DISGUST]: '#34495E',
[EmotionState.TIRED]: '#95A5A6'
};
this.themeColor = colorMap[emotion] || '#4A90E2';
}
build() {
Stack() {
// 第一层:情绪环境光背景
this.buildEmotionAmbientLight()
// 第二层:休息模式覆盖层
if (this.restModeActive) {
this.buildRestModeOverlay()
}
// 第三层:主内容区
EmotionAdaptiveNav({
contentBuilder: () => {
this.buildMainContent()
}
})
// 第四层:手势控制AI面板
GestureAIPanel()
// 第五层:情绪状态浮层
this.buildEmotionStatusOverlay()
}
.width('100%')
.height('100%')
.backgroundColor('#0a0a12')
.expandSafeArea(
[SafeAreaType.SYSTEM],
[SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM, SafeAreaEdge.START, SafeAreaEdge.END]
)
}
@Builder
buildEmotionAmbientLight(): void {
Column() {
// 主光源(情绪色)
Column()
.width(800)
.height(800)
.backgroundColor(this.themeColor)
.blur(250)
.opacity(0.12)
.position({ x: '50%', y: '40%' })
.anchor('50%')
.animation({
duration: 8000,
curve: Curve.EaseInOut,
iterations: -1,
playMode: PlayMode.Alternate
})
// 辅助光点(情绪互补色)
Column()
.width(400)
.height(400)
.backgroundColor(this.getComplementaryColor(this.themeColor))
.blur(150)
.opacity(0.06)
.position({ x: '20%', y: '70%' })
.animation({
duration: 12000,
curve: Curve.EaseInOut,
iterations: -1,
playMode: PlayMode.AlternateReverse
})
}
.width('100%')
.height('100%')
}
@Builder
buildRestModeOverlay(): void {
Column() {
Column() {
Text('🌙 休息模式')
.fontSize(32)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
.margin({ bottom: 16 })
Text('您已连续工作 90 分钟,建议休息 5 分钟')
.fontSize(16)
.fontColor('rgba(255,255,255,0.7)')
.margin({ bottom: 24 })
// 呼吸指导动画
Column()
.width(120)
.height(120)
.backgroundColor('rgba(255,255,255,0.1)')
.borderRadius(60)
.animation({
duration: 4000,
curve: Curve.EaseInOut,
iterations: -1,
playMode: PlayMode.Alternate
})
.scale({ x: 1.3, y: 1.3 })
Text('跟随圆环呼吸')
.fontSize(14)
.fontColor('rgba(255,255,255,0.5)')
.margin({ top: 24 })
Button('结束休息')
.type(ButtonType.Capsule)
.fontSize(16)
.fontColor('#FFFFFF')
.backgroundColor('#4A90E2')
.width(160)
.height(48)
.margin({ top: 32 })
.onClick(() => {
AppStorage.setOrCreate('rest_mode_active', false);
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
.width('100%')
.height('100%')
.backgroundColor('rgba(0,0,0,0.85)')
.zIndex(100)
}
@Builder
buildMainContent(): void {
Column({ space: 16 }) {
// 欢迎语(情绪感知)
Text(this.getEmotionGreeting(this.currentEmotion))
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
.alignSelf(ItemAlign.Start)
// 今日任务概览
Column({ space: 12 }) {
Text('今日任务')
.fontSize(18)
.fontColor('#FFFFFF')
.alignSelf(ItemAlign.Start)
ForEach([1, 2, 3], (index: number) => {
Row({ space: 12 }) {
Column()
.width(12)
.height(12)
.backgroundColor(index === 1 ? this.themeColor : 'rgba(255,255,255,0.2)')
.borderRadius(6)
Text(`任务 ${index}`)
.fontSize(14)
.fontColor(index === 1 ? '#FFFFFF' : 'rgba(255,255,255,0.5)')
.layoutWeight(1)
Text(index === 1 ? '进行中' : '待开始')
.fontSize(12)
.fontColor(index === 1 ? this.themeColor : 'rgba(255,255,255,0.3)')
}
.width('100%')
.height(48)
.padding({ left: 16, right: 16 })
.backgroundColor('rgba(255,255,255,0.03)')
.borderRadius(12)
})
}
.width('100%')
.padding(16)
.backgroundColor('rgba(255,255,255,0.02)')
.borderRadius(16)
// 效率统计
Row({ space: 16 }) {
Column({ space: 4 }) {
Text('专注时长')
.fontSize(12)
.fontColor('rgba(255,255,255,0.5)')
Text('2h 35m')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
}
.layoutWeight(1)
Column({ space: 4 }) {
Text('情绪状态')
.fontSize(12)
.fontColor('rgba(255,255,255,0.5)')
Text(this.getEmotionLabel(this.currentEmotion))
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor(this.themeColor)
}
.layoutWeight(1)
Column({ space: 4 }) {
Text('AI互动')
.fontSize(12)
.fontColor('rgba(255,255,255,0.5)')
Text('12次')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#FFFFFF')
}
.layoutWeight(1)
}
.width('100%')
.padding(16)
.backgroundColor('rgba(255,255,255,0.02)')
.borderRadius(16)
}
.width('100%')
.height('100%')
.padding(24)
}
@Builder
buildEmotionStatusOverlay(): void {
Column() {
// 右上角情绪指示器
Row({ space: 8 }) {
Column()
.width(8)
.height(8)
.backgroundColor(this.themeColor)
.borderRadius(4)
.shadow({ radius: 4, color: this.themeColor })
.animation({
duration: 2000,
curve: Curve.EaseInOut,
iterations: -1,
playMode: PlayMode.Alternate
})
Text(this.getEmotionLabel(this.currentEmotion))
.fontSize(12)
.fontColor(this.themeColor)
}
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.backgroundColor('rgba(0,0,0,0.4)')
.borderRadius(16)
}
.position({ x: '85%', y: 20 })
.visibility(this.currentEmotion !== EmotionState.NEUTRAL ? Visibility.Visible : Visibility.Hidden)
}
private getEmotionGreeting(emotion: EmotionState): string {
const greetings: Record<EmotionState, string> = {
[EmotionState.NEUTRAL]: '你好,开始今天的工作吧',
[EmotionState.HAPPY]: '看起来心情不错,效率会很高哦',
[EmotionState.SAD]: '今天有点低落,需要我放点轻音乐吗',
[EmotionState.ANGRY]: '检测到您有些烦躁,已开启降噪模式',
[EmotionState.SURPRISED]: '有什么新发现吗?',
[EmotionState.FEAR]: '别紧张,慢慢来,我帮您简化界面',
[EmotionState.DISGUST]: '有什么不满意的地方吗?',
[EmotionState.TIRED]: '您看起来很累,注意休息'
};
return greetings[emotion] || '你好';
}
private getEmotionLabel(emotion: EmotionState): string {
const labels: Record<EmotionState, string> = {
[EmotionState.NEUTRAL]: '平静',
[EmotionState.HAPPY]: '愉悦',
[EmotionState.SAD]: '低落',
[EmotionState.ANGRY]: '烦躁',
[EmotionState.SURPRISED]: '惊讶',
[EmotionState.FEAR]: '焦虑',
[EmotionState.DISGUST]: '不适',
[EmotionState.TIRED]: '疲劳'
};
return labels[emotion] || '平静';
}
private getComplementaryColor(color: string): string {
// 简化实现,返回固定互补色
const complements: Record<string, string> = {
'#4A90E2': '#E2A44A',
'#FF9500': '#0095FF',
'#5B8BD4': '#D4A85B',
'#E74C3C': '#3CE7A8',
'#9B59B6': '#59B69B',
'#2ECC71': '#CC2E71',
'#34495E': '#5E4434',
'#95A5A6': '#A69595'
};
return complements[color] || '#FFFFFF';
}
}
四、关键技术总结
4.1 情绪-UI自适应映射体系
| 情绪状态 | 主色调 | 导航透明度 | 动画速度 | UI简化 | 特殊行为 |
|---|---|---|---|---|---|
| 平静 | 海洋蓝 #4A90E2 | 70% | 1.0x | 否 | 标准交互 |
| 愉悦 | 活力橙 #FF9500 | 75% | 1.2x | 否 | 增强反馈光效 |
| 低落 | 柔和蓝 #5B8BD4 | 60% | 0.8x | 是 | 显示鼓励提示 |
| 烦躁 | 警示红 #E74C3C | 85% | 0.5x | 是 | 自动开启降噪 |
| 惊讶 | 神秘紫 #9B59B6 | 80% | 1.5x | 否 | 高亮新内容 |
| 焦虑 | 镇静绿 #2ECC71 | 55% | 0.6x | 是 | 简化界面+呼吸引导 |
| 不适 | 沉稳灰 #34495E | 65% | 0.7x | 否 | 询问反馈 |
| 疲劳 | 柔和灰 #95A5A6 | 50% | 0.4x | 是 | 强制休息提醒 |
4.2 手势-AI指令映射
| 手势 | 触发条件 | AI响应 | 视觉反馈 |
|---|---|---|---|
| 双手举起 | 双腕高于肩 | 唤醒AI面板 | 面板从底部滑入 |
| 单手抬起 | 单腕高于肩 | 选中/指向 | 光标跟随手势 |
| 捏合 | 双手靠近<80px | 最小化面板 | 面板收缩为悬浮球 |
| 张开 | 双手分开>200px | 最大化面板 | 面板展开全屏 |
| 前倾 | 鼻-肩距>60px | 专注模式 | 面板自动靠边 |
| 后仰 | 鼻-肩距<-40px | 全局视图 | 面板恢复默认 |
4.3 性能与隐私优化
typescript
// 1. 情绪识别节流:每100ms处理一次,避免过度计算
private emotionThrottleMs = 100;
// 2. 端侧处理:所有AR数据本地处理,不上传云端
// AR Engine 6.1.0 端侧推理
// 3. 情绪数据脱敏:只存储情绪分类,不保存原始面部图像
private storeEmotionSummaryOnly = true;
// 4. 用户可控:提供关闭AR感知的隐私开关
AppStorage.setOrCreate('ar_perception_enabled', true); // 用户可关闭
五、调试与部署建议
5.1 真机调试要点
- 摄像头权限:首次启动需引导用户授权摄像头访问
- 光线条件:避免逆光,确保面部和手部清晰可见
- 距离范围:建议用户距离摄像头0.5-1.5米
- 多窗口测试:验证AI面板与主窗口的层级关系
5.2 部署适配
| 设备类型 | 摄像头要求 | 功能适配 |
|---|---|---|
| 华为MateBook | 内置720P | 基础情绪+手势 |
| 华为MateStation | 外接1080P | 全功能高精度 |
| 第三方PC | 需USB摄像头 | 降级为表情识别 |
六、总结与展望
本文基于 HarmonyOS 6(API 23)的 Face AR 、Body AR 、悬浮导航 与沉浸光感四大特性,完整实战了一款"情绪感知智能工作台"。核心创新点:
- 情绪驱动UI:通过Face AR实时识别7种基础情绪,自动调整界面色调、透明度、动画速度和内容复杂度,实现"UI懂你的心情"
- 手势零接触交互:通过Body AR识别6种手势,实现AI助手的唤醒、缩放、移动等操作,无需鼠标键盘
- 主动式AI建议:基于情绪历史滑动窗口,AI主动提出休息提醒、专注模式、降噪等建议,从被动响应升级为主动关怀
- 全链路沉浸光效:情绪色驱动环境光晕、导航光效、AI面板主题,打造视觉一致的情绪化体验
未来扩展方向:
- 接入 分布式软总线,实现跨设备情绪状态同步(手机检测到焦虑,PC自动开启降噪)
- 结合 AI大模型,基于情绪状态生成个性化工作内容建议
- 探索 眼动追踪(Face AR注视点),实现"看哪里高亮哪里"的精准交互
- 引入 生物反馈闭环:检测心率变异性(HRV),与情绪识别交叉验证
转载自:https://blog.csdn.net/u014727709/article/details/147283885
欢迎 👍点赞✍评论⭐收藏,欢迎指正