HarmonyOS 7 实战:ArkTS + Canvas 2D 打造情绪可视化 AI 应用
HarmonyOS 7 实战:ArkTS + Canvas 2D 打造情绪可视化 AI 应用
当大语言模型遇上 Canvas 2D 渲染,情绪不再只是一行文字记录,而会长成一棵有枝叶、有花果、会随风摇摆的树。本文以"情绪树 Mood Tree"项目为完整案例,深入剖析 HarmonyOS 7 下 ArkTS/ArkUI 的声明式开发范式、Canvas 高性能渲染、多端协同 AI 架构,以及从 0 到 1 的工程化落地全过程。

图 1:情绪树 App 核心记录页 —— 七档情绪标签 + 自由文本 + AI 一键生成
一、为什么是"情绪树"?
心理健康类应用的核心矛盾在于:情绪是模糊的、流动的、难以量化的,而用户需要的是确定性的反馈与陪伴感。
传统情绪日记产品大多停留在"打标签 + 写日记 + 看折线图"的阶段。折线图能告诉用户"你这周焦虑上升了",却无法回答"那又怎样"。
“情绪树"给出的答案是一种具身化隐喻(Embodied Metaphor):把每一次情绪记录,转化为一棵独一无二的树。喜悦高时花开满枝,压力重时枝叶枯萎,平静久时树干挺拔。当 30 天的记录累积成一片"情绪森林”,用户看到的不是冷冰冰的数据,而是自己内心的四季流转。
这种设计背后有三层技术挑战:
- 情绪到视觉的映射:如何把多维情绪数据(喜悦/平静/活力/压力/情感倾向)稳定、可解释地映射为树的视觉状态?
- 渲染性能:树的枝干是递归生成的,一棵树的绘制可能涉及数百个图元,30 棵树同时渲染如何保持 60fps?
- AI 协同架构:大模型推理不应阻塞主线程,更不能把 API Key 写进客户端——如何设计安全的端云协同链路?
下面逐一拆解。
二、架构总览:端云协同的三层模型
"情绪树"采用经典的前后端分离 + 端云协同架构:

关键设计决策:
- App 不直接持有大模型 API Key。ArkTS 侧只调用本地局域网内的 FastAPI 服务,Key 保存在后端环境变量,避免逆向破解导致密钥泄露。
- AI 失败可降级。网络异常时,App 自动切换到本地启发式算法(
offlineAnalyze),保证核心体验不中断——这就是为什么你在图 2 的反馈里偶尔会看到"(离线模式,连接服务器获取更精准分析)"的提示。 - 渲染与数据解耦。情绪维度(
MoodDimension)是纯数据结构,树的生成(TreeGenerator)和绘制(TreeRenderer)完全独立,便于单元测试与逻辑复用。
三、ArkTS 声明式 UI:从状态到界面的单向流动
HarmonyOS 7 的 ArkUI 采用彻底声明式的开发范式。与传统命令式"找到 View → 修改属性"不同,ArkTS 的核心是状态驱动 UI:当被 @State、@Prop、@Link 等装饰器标记的数据变化时,框架自动 diff 并更新最小化的 UI 节点。
3.1 记录页的声明式表达
记录页(图 1)的核心交互是"七档情绪选择 + 文本输入 + 触发生成"。用 ArkTS 表达极为简洁:
@Component
export struct RecordTab {
@State selectedMood: string = '';
@State story: string = '';
@State isAnalyzing: boolean = false;
// 七档情绪的定义(标签 + emoji + 权重值)
private readonly moods: MoodOption[] = [
{ label: '狂喜', emoji: '😍', value: 1.0 },
{ label: '开心', emoji: '😄', value: 0.8 },
{ label: '平静', emoji: '😌', value: 0.5 },
{ label: '一般', emoji: '😐', value: 0.3 },
{ label: '焦虑', emoji: '😟', value: -0.4 },
{ label: '低落', emoji: '😢', value: -0.7 },
{ label: '崩溃', emoji: '😭', value: -1.0 },
];
build() {
Column() {
Text('今天感觉怎么样?')
.fontSize(20)
.fontColor('#E8E8F0')
.margin({ top: 24, bottom: 16 })
// 情绪标签网格
Wrap({ space: 12 }) {
ForEach(this.moods, (m: MoodOption) => {
this.MoodChip(m)
})
}
// 文本输入
TextArea({ text: this.story, placeholder: '今天发生了什么?你的感受是...' })
.onChange((v: string) => { this.story = v; })
.margin({ top: 20 })
// 生成按钮
Button(this.isAnalyzing ? '正在种树...' : '让 AI 种一棵树')
.enabled(!this.isAnalyzing)
.onClick(() => this.onPlant())
}
}
@Builder MoodChip(m: MoodOption) {
Row() {
Text(m.emoji).fontSize(22)
Text(m.label).fontSize(14).fontColor('#E8E8F0')
}
.padding({ left: 14, right: 14, top: 8, bottom: 8 })
.borderRadius(20)
// 选中态通过状态驱动样式,无需手动操作 DOM
.backgroundColor(this.selectedMood === m.label ? '#4ECB71' : '#2A2A4A')
.onClick(() => { this.selectedMood = m.label; })
}
}
这里有几个 ArkTS 的关键点值得强调:
@State声明的selectedMood一旦变化,MoodChip的背景色会自动重算,开发者不需要写任何"if selected then set red"的命令式代码。ForEach是 ArkUI 的列表渲染原语,它要求每项有稳定的key(默认用数组下标,复杂场景应传keyGenerator),否则在增删时会导致错误的节点复用。@Builder装饰的方法相当于"局部 UI 片段函数",用于消除重复布局代码,是 ArkTS 中组织复杂界面的核心手段。
3.2 状态管理的层次
随着页面增多,"情绪树"出现了跨页面共享状态:记录页生成的新树,需要实时反映到"我的树"和"森林"页。ArkTS 提供了从局部到全局的多级状态管理:
| 装饰器 | 作用域 | 典型用途 |
|---|---|---|
@State |
组件内 | 局部 UI 状态(如选中态) |
@Prop |
父→子单向 | 子组件接收不可变快照 |
@Link |
父↔子双向 | 子组件需要回写父状态 |
@Provide/@Consume |
跨层级 | 祖先与后代组件共享,跳过中间层 |
AppStorage |
全局单例 | 跨页面、跨 Ability 的持久状态 |
LocalStorage |
Ability 级 | 同一 Ability 内多页面共享 |
"情绪树"把用户的全部情绪记录列表放在 AppStorage 中,这样任何页面刷新都无需层层透传参数:
// 写入
AppStorage.setOrCreate('moodRecords', records);
// 任意页面读取
@StorageLink('moodRecords') records: MoodRecord[];
四、Canvas 2D 渲染:把情绪画成一棵树
这是项目最硬核的部分。HarmonyOS 7 的 ArkUI 提供了 Canvas 组件,通过 CanvasRenderingContext2D 暴露了与 Web Canvas 高度一致的 2D 绘图 API。

图 2:AI 返回的五维情绪分析 + 温暖心理解读,标签与文案均由大模型生成
4.1 情绪到视觉状态的映射函数
树的"长相"由一个纯函数 generateTreeState(dim, dayIndex) 决定。输入是五维情绪向量,输出是树的视觉参数:
export function generateTreeState(dim: MoodDimension, dayIndex: number): TreeVisualState {
const joy = dim.joy; // 0~1 喜悦
const calm = dim.calm; // 0~1 平静
const energy = dim.energy; // 0~1 活力
const stress = dim.stress; // 0~1 压力
// 喜悦高 → 花朵多、叶片翠绿
// 平静高 → 树干挺拔
// 活力高 → 分支多、叶密
// 压力高 → 枯萎因子上升
const trunkHeight = 80 + calm * 60 + energy * 30;
const branchCount = Math.floor(3 + energy * 4 + joy * 2);
const leafCount = Math.floor(15 + joy * 30 + energy * 25 - stress * 15);
// 叶色:喜悦→翠绿,低落→暗紫,压力→枯黄
let leafColor = '#4ECB71';
if (joy > 0.7) leafColor = '#5DD962';
else if (stress > 0.6) leafColor = '#8B7355';
else if (joy < 0.3) leafColor = '#7A6BB8';
const flowerCount = joy > 0.5 ? Math.floor(joy * 12) : 0;
const witherFactor = Math.min(1, stress * 0.7 + (1 - joy) * 0.3);
const glowIntensity = Math.min(1, joy * 0.5 + calm * 0.3);
return { trunkHeight, branchCount, leafCount, leafColor,
flowerCount, witherFactor, glowIntensity, /* ... */ };
}
设计亮点:映射函数是确定性的——同样的情绪输入永远生成同样的树。这带来两个好处:一是用户的树具有"身份感"(不会每次打开都变样),二是森林视图中每棵树都代表某一天的真实状态,可回溯、可对比。
4.2 递归生成树拓扑
树的枝干结构通过递归算法生成。为避免每次绘制都产生不同的随机树,项目实现了一个带种子的伪随机数生成器(SeededRandom):
class SeededRandom {
private seed: number;
constructor(seed: number) { this.seed = seed; }
next(): number {
this.seed = (this.seed * 9301 + 49297) % 233280;
return this.seed / 233280;
}
}
以日期(如 2026-07-16 → 20260716)作为种子,保证"7 月 16 日的树"在任何设备上、任何时间生成的拓扑完全一致。递归 generateChildren 从树干出发,按 branchCount 和 branchDepth 逐层分裂子枝,最终在叶子节点上分布式地分配叶片、花朵、果实。

图 3:"我的树"页 —— 单日情绪的具象化呈现,树干挺拔、枝叶分布由情绪维度驱动
4.3 渲染管线与性能优化
renderTree 是绘制入口,按"地面 → 枝干 → 叶 → 花 → 果"的顺序分层绘制:
export function renderTree(ctx, state, seed, config): void {
const root = generateTreeTopology(state, seed);
drawGround(ctx);
drawBranches(ctx, root, state, config); // 递归绘制所有枝干
drawLeaves(ctx, root, state, config); // 递归绘制叶片
drawFlowers(ctx, root, state, config);
drawFruits(ctx, root, state, config);
}
性能关键点:
- 摇摆动画的低成本实现。树的"随风摇摆"不是重新生成拓扑,而是在绘制时给每个节点叠加一个与
depth和swayPhase相关的水平偏移swayX。这样每一帧只需重绘,无需重建数据结构:
let swayX = 0;
if (config.showSway) {
swayX = Math.sin(config.swayPhase + node.depth * 0.3)
* state.swayAmplitude * (node.depth / (state.branchDepth + 1));
}
ctx.lineTo(node.endX + swayX, node.endY);
-
发光效果的按需开启。Canvas 的
shadowBlur非常耗性能。代码里只在glowIntensity > 0.3时才开启阴影,绘制完立即shadowBlur = 0关闭,避免污染后续绘制。 -
森林视图的缩放绘制。30 棵树同时渲染时,每棵小树通过
ctx.save() → translate → scale(0.6) → renderTree → ctx.restore()实现缩放复用,避免为森林单独写一套绘制逻辑。

图 4:树的量化状态面板 —— 叶片数、花朵数、枯萎率、花期、光辉度,将视觉参数透明化展示给用户
五、AI 协同:五维情绪分析的后端架构
前端的渲染再精美,也需要"灵魂"——即大模型对情绪的深层理解。项目后端是一个不到 200 行的 FastAPI 服务,核心职责是把用户的自由文本 + 情绪标签,转换为结构化的五维向量 + 温度恰好合适的心理解读文案。
5.1 分析 Prompt 的设计
大模型不是"直接回答用户",而是被要求输出严格的结构化 JSON:
SYSTEM_PROMPT = """你是一位温柔而专业的心理陪伴师。
请根据用户的情绪标签和描述,输出 JSON:
{
"joy": 0~1, "calm": 0~1, "energy": 0~1, "stress": 0~1, "sentiment": 0~1,
"analysis": "不超过60字的心理解读,温柔、不评判",
"keywords": ["2-4个情绪标签,带#"]
}"""
把情绪维度量化为 0~1 的连续值,是为了让前端映射函数能平滑插值——用户从"开心"滑到"狂喜",树的花朵数会连续增长,而不是跳变。
5.2 客户端如何安全调用
ArkTS 侧通过 http 模块发起请求,URL 指向局域网内的后端(开发期用 Mac 局域网 IP,生产可替换为 HTTPS 域名):
import { http } from '@kit.NetworkKit';
async function analyzeMood(moodLabel: string, story: string): Promise<MoodDimension> {
const req = http.createHttp();
const resp = await req.request(SERVER_BASE_URL + '/api/mood/analyze', {
method: http.RequestMethod.POST,
header: { 'Content-Type': 'application/json' },
extraData: JSON.stringify({ mood_label: moodLabel, description: story }),
});
return JSON.parse(resp.result as string);
}
安全红线:永远不要把大模型 API Key 打包进 App。ArkTS 代码最终会被编译,Key 可被逆向提取。正确做法是通过自己的后端中转,Key 仅存在于后端环境变量或密钥管理服务中。
5.3 离线降级:体验的兜底网
网络永远不可靠。当请求超时或后端不可达时,App 不应崩溃或白屏,而是调用本地启发式算法:
try {
const dim = await analyzeMood(this.selectedMood, this.story);
// 用 AI 结果生成树
} catch (e) {
// 降级:基于情绪标签的本地映射,保证核心功能可用
const dim = offlineAnalyze(this.selectedMood);
promptAction.showToast({ message: '离线模式,连接服务器获取更精准分析' });
}
这正是图 2 中那行"(离线模式)"提示的来源——它是设计好的优雅降级,而非 bug。

图 5:情绪森林 —— 30 天情绪轨迹,每棵树都是一天的缩影,左侧繁茂的树代表积极情绪积累
六、本地持久化:Preferences 的正确姿势
鸿蒙提供了 @ohos.data.preferences 轻量级 KV 存储。但在 ArkTS 严格模式下,有几个坑需要避开:
坑 1:getPreferencesSync 的第二个参数在 API 12+ 变成了 Options 对象,而非字符串。
// ❌ 旧写法(API 11 及以前)
prefStore = preferences.getPreferencesSync(ctx, 'mood_tree_store');
// ✅ 新写法(HarmonyOS 7 / API 23)
prefStore = preferences.getPreferencesSync(ctx, { name: 'mood_tree_store' });
坑 2:globalThis 与 getContext 已被标记为 deprecated。 不应在工具类里依赖全局上下文,而应把 Context 作为参数显式传入:
// 推荐:首次使用时传入 UIAbility 的 context
StorageUtil.init(getContext(this));
坑 3:同步 API 虽方便但有抛异常风险。 编译器会警告"Function may throw exceptions",生产代码应包裹 try/catch 或在调用处加 try 块。
七、工程化:从 DevEco Studio 到真机
7.1 SDK 版本对齐
项目的 build-profile.json5 必须声明与已安装 SDK 匹配的 compatibleSdkVersion:
{
"app": {
"products": [{
"compatibleSdkVersion": "6.1.0(23)",
"targetSdkVersion": "6.1.0(23)",
"runtimeOS": "HarmonyOS"
}]
}
}
版本不匹配会直接导致 Configuration Error。通过 hdc 查看已安装系统镜像的 apiVersion 可快速定位正确版本号。
7.2 构建与安装命令
纯命令行构建 HAP(适合 CI 或远程开发):
# 设置 SDK 与 JDK 路径(避免 IDE 环境变量污染)
export DEVECO_SDK_HOME="/Applications/DevEco-Studio.app/Contents/sdk"
export JAVA_HOME="/Applications/DevEco-Studio.app/Contents/jbr/Contents/Home"
# 用 hvigor 构建(注意:需在独立终端中运行,避开外部注入的环境变量)
node hvigorw.js assembleHap --mode module -p module=entry@default
# 通过 hdc 安装到设备
hdc -t 127.0.0.1:5555 install entry/build/default/outputs/default/entry-default-unsigned.hap
实战经验:在 macOS 上若从某些桌面应用启动终端,可能会被注入 NODE_OPTIONS 等环境变量,导致 hvigor 的 Node worker 崩溃。最稳妥的方式是从 Finder/Spotlight 独立启动 DevEco Studio,或在命令前 unset NODE_OPTIONS。
7.3 真机/模拟器调试链路
开发期,后端跑在 Mac 上(端口 18081),模拟器通过局域网 IP 直接访问,绕过失效的端口转发:
模拟器 App (http://192.168.1.35:18081)
│
▼
Mac 上的 FastAPI (0.0.0.0:18081)
│
▼
大模型服务 (兼容 OpenAI 协议)
注意:模拟器访问 127.0.0.1 指向的是模拟器自己,要让 App 连到宿主机的后端,必须使用宿主机的局域网 IP,并确保 Mac 防火墙放行对应端口。

图 6:关于页 —— 完整技术栈标注:HarmonyOS 7 · ArkTS/ArkUI · Canvas 2D 渲染 · 大语言模型 · FastAPI
八、设计哲学:技术服务于情感
回顾整个项目,技术选型的每一处都不是炫技,而是服务于"让情绪被看见"这一核心体验:
- 选用 Canvas 2D 而非预渲染图片:因为每棵树都是数据驱动的独特存在,图片无法表达情绪的连续性。
- 确定性伪随机:让用户的树具有身份感和可追溯性。
- 离线降级:心理类产品最忌讳"我想记录时它挂了",降级是基本尊重。
- 端云分离 + Key 隔离:既享受了大模型的能力,又守住了安全底线。

图 7:从一句话到一棵树 —— 记录、生成、可视化,构成情绪树完整的体验闭环
九、结语与延伸
"情绪树"证明了 HarmonyOS 7 + ArkTS 完全能够承载"重交互 + AI 协同 + 高性能渲染"的复杂应用场景。它不依赖任何第三方 UI 框架,纯用原生 ArkUI 与 Canvas 2D 就实现了细腻的视觉表达。
如果想进一步打磨这个项目,以下几个方向值得探索:
- 动效升级:引入
Particle粒子系统,让花瓣飘落、星光闪烁更具沉浸感。 - 多模态情绪输入:接入
Core Vision Kit或语音识别,让用户通过自拍表情或语音语调辅助情绪判断。 - 社交森林:在合规与隐私前提下,把单用户的森林扩展为可分享、可共鸣的社区情绪地图。
- 端侧推理:未来可将轻量大模型部署到端侧(如通过 NPU 加速),彻底摆脱网络依赖,实现真正的离线 AI 陪伴。
种一棵树最好的时间是十年前,其次是现在。而记录一种情绪最好的方式,也许是——看它长成一棵树。
技术栈:HarmonyOS 7 · ArkTS / ArkUI · Canvas 2D 渲染 · 大语言模型 · FastAPI
项目结构:
mood-tree-demo/
├── entry/src/main/ets/
│ ├── pages/ # ArkUI 页面(记录/我的树/森林/关于)
│ ├── utils/ # TreeGenerator / TreeRenderer / StorageUtil / AIService
│ ├── common/ # Constants(情绪维度、Canvas 常量)
│ └── entryability/ # EntryAbility 入口
└── server/ # FastAPI 后端(五维情绪分析 + 大模型中转)
本文基于真实项目"情绪树 Mood Tree v1.0"创作,所有界面截图均来自 DevEco Studio 模拟器实机运行。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐




所有评论(0)