HarmonyOS 3D 拆解分析:多色系统设计与视觉信息编码
前言
之前的 3D 拆解分析 App 全站只有紫色一个主色——按钮是紫色的,选中态是紫色的,部件编号也是紫色的。好看但单调,所有元素看起来都是"同一层级"的。
这次迭代引入了多色系统:红色代表手机、紫色代表手表、蓝色代表相机、绿色代表大件设备。每个场景有自己的颜色,部件清单里的每个部件也有自己的颜色。颜色不再是装饰,而是信息的一部分。
完整效果
首页:从单色到多色
调色板定义
const A: string = '#8A5DFA' // 品牌紫
const A2: string = '#FF6B6B' // 红色 — 手机
const A3: string = '#00B894' // 绿色 — 大件
const A4: string = '#5DADE2' // 蓝色 — 相机
const A5: string = '#FECA57' // 黄色 — 装饰

五个颜色各有明确的用途:
A(紫色):品牌主色,用于按钮、选中态、通用元素A2(红色):手机场景的专属色A3(绿色):大件设备场景的专属色A4(蓝色):相机场景的专属色A5(黄色):装饰性用途
HarmonyOS NEXT 标识
Row() {
Row() {}.width(6).height(6).borderRadius(3).backgroundColor(A3)
Text(' HarmonyOS NEXT').fontSize(12).fontColor(T3)
}.margin({ top: 4 })
标题下方加了一个绿色小圆点 + “HarmonyOS NEXT” 文字。绿色圆点是 HarmonyOS 品牌色,一行文字就交代了技术栈。比单独放一个 logo 更轻量。
统计卡片:信息密度的新维度
Row({ space: 10 }) {
this.Stat('5 种', '基底选择', A)
this.Stat('4 档', '拆解精度', A3)
this.Stat('2~16', '部件数量', A4)
this.Stat('3D', '爆炸展示', A5)
}
.width('100%').margin({ bottom: 26 })

四个统计卡片用不同颜色标记:
@Builder Stat(value: string, label: string, color: string) {
Column() {
Text(value).fontSize(16).fontWeight(FontWeight.Bold).fontColor(color)
Text(label).fontSize(10).fontColor(T3).margin({ top: 2 })
}
.layoutWeight(1).padding({ top: 12, bottom: 12 })
.backgroundColor(CW).borderRadius(14)
.shadow({ radius: 4, color: 'rgba(0,0,0,0.03)', offsetY: 1 })
}
每个卡片只有两个数字——值和标签。颜色区分了四个不同的维度:选择多样性、精度档位、部件范围、展示方式。
为什么不用统一颜色?
如果四个卡片都是紫色,用户需要读文字才能区分。用四种颜色后,用户扫一眼就知道"这是四个不同的东西"——颜色本身就在传递信息。
场景卡片:每个场景一个颜色
this.SCard(0, '手机拆解', '3×3 · 9部件', '#FF6B6B', '#FFF5F5')
this.SCard(1, '手表拆解', '3×3 · 9部件', A, AL)
this.SCard(2, '相机拆解', '4×3 · 12部件', '#5DADE2', '#EBF5FB')
this.SCard(3, '大件设备', '4×4 · 16部件', '#00B894', '#E8F8F5')

每个卡片传入两个颜色参数:color(文字/边框/选中态)和 bg(选中时的背景色)。背景色是主色的 10% 透明度版本:
| 场景 | 主色 | 背景色 |
|---|---|---|
| 手机 | #FF6B6B(红) | #FFF5F5(浅红) |
| 手表 | #8A5DFA(紫) | #F4F1FC(浅紫) |
| 相机 | #5DADE2(蓝) | #EBF5FB(浅蓝) |
| 大件 | #00B894(绿) | #E8F8F5(浅绿) |
选中态的变化:
.backgroundColor(this.preset === idx ? bg : CW)
.border({ width: this.preset === idx ? 1.5 : 1,
color: this.preset === idx ? color + '30' : '#F0EEF6' })
未选中时是白色背景 + 淡灰边框。选中时变成彩色背景 + 彩色半透明边框。用户一眼就能看出当前选的是哪个场景。
部件数量标签的颜色联动
Text(this.parts() + ' 部件').fontSize(11).fontWeight(FontWeight.Bold)
.fontColor(this.preset === 0 ? A2 : (this.preset === 1 ? A : (this.preset === 2 ? A4 : A3)))
.padding({ left: 8, right: 8, top: 3, bottom: 3 })
.backgroundColor(this.preset === 0 ? '#FFF5F5' : (this.preset === 1 ? AL : (this.preset === 2 ? '#EBF5FB' : '#E8F8F5')))
部件数量标签的颜色跟随当前选中的场景——选手机时显示红色,选手表时显示紫色。这种"颜色联动"让用户在切换场景时能直观感受到变化。
Step 步骤卡片:编号角标
@Builder Step(num: string, icon: string, title: string, color: string) {
Column() {
Stack({ alignContent: Alignment.TopEnd }) {
Column() {
Text(icon).fontSize(24).margin({ bottom: 8 })
Text(title).fontSize(13).fontWeight(FontWeight.Bold).fontColor(T1)
}
.padding({ top: 14, bottom: 14, left: 12, right: 12 })
.alignItems(HorizontalAlign.Center)
.backgroundColor(CW).borderRadius(16).border({ width: 1, color: '#F0EEF6' })
Row() {
Text(num).fontSize(9).fontWeight(FontWeight.Bold).fontColor(Color.White)
}
.width(18).height(18).borderRadius(9).backgroundColor(color)
.justifyContent(FlexAlign.Center).alignItems(VerticalAlign.Center)
.position({ x: 0, y: 0 })
}
}.width(85)
}
每个步骤卡片右上角有一个彩色编号角标:
- 步骤 1:紫色(品牌色)
- 步骤 2:绿色(代表"智能")
- 步骤 3:红色(代表"爆炸")
Stack({ alignContent: Alignment.TopEnd }) 让角标定位在卡片右上角。角标本身用 position({ x: 0, y: 0 }) 微调位置,确保不会被卡片边框裁剪。
Hero Card 渐变升级
旧版渐变:
.linearGradient({ angle: 150, colors: [[A,0],['#9B7CF4',0.4],['#C4B5FD',1]] })
新版渐变:
.linearGradient({ angle: 135, colors: [[A, 0], ['#6C3CE0', 0.5], ['#A78BFA', 1]] })
三个变化:
- 角度从 150° 改为 135°(更标准的对角线渐变)
- 中间色从
#9B7CF4(浅紫)变成#6C3CE0(深紫) - 渐变更深,和白色文字的对比度更高
装饰性圆也从 2 个增加到 3 个:
Circle({ width: 160, height: 160 }).fill('rgba(255,255,255,0.06)').position({ x: '60%', y: -40 })
Circle({ width: 100, height: 100 }).fill('rgba(255,255,255,0.08)').position({ x: '5%', y: 90 })
Circle({ width: 60, height: 60 }).fill('rgba(255,255,255,0.10)').position({ x: '80%', y: 120 })
三个圆大小递减(160 → 100 → 60),透明度递增(0.06 → 0.08 → 0.10),形成视觉层次。
分析工作台:部件的个性化着色
调色板数组
const PC: string[] = ['#8A5DFA','#FF6B6B','#00B894','#5DADE2','#FECA57','#E8734A','#6C5CE7','#48DBFB']
八个颜色,循环分配给部件。如果部件超过 8 个,用 i % PC.length 取模循环。
部件卡片的颜色分配
cards.push({
n: ns[i], idx: i + 1,
sub: '网格(' + col + ',' + row + ') · 深度' + Math.round(d * 100) + '%',
d: d, c: PC[i % PC.length]
})
每个部件的颜色在 rebuild() 时就确定了,存在 PCard 的 c 字段里。
部件清单:彩色编号 + 彩色深度条
ForEach(this.pc, (c: PCard) => {
Row() {
// 彩色编号圆圈
Column() {
Text(c.idx.toString()).fontSize(11).fontWeight(FontWeight.Bold).fontColor(Color.White)
}
.width(24).height(24).borderRadius(12).backgroundColor(c.c)
.justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)
// 彩色深度条
Row() {}.width(c.d * 44).height(5).borderRadius(3)
.backgroundColor(c.c + '55').margin({ left: 10, right: 10 })
// 名称和位置
Column() {
Text(c.n).fontSize(13).fontWeight(FontWeight.Medium).fontColor(T1)
Text(c.sub).fontSize(10).fontColor(T3)
}.alignItems(HorizontalAlign.Start).layoutWeight(1)
// 右箭头
SymbolGlyph($r('sys.symbol.chevron_right')).fontSize(12).fontColor(['#DDD'])
}
})
每个部件卡片有三个彩色元素:
- 编号圆圈:白色数字 + 彩色背景
- 深度条:彩色(55% 透明度)
- 右箭头:淡灰色(不参与颜色编码)
头部信息:彩色圆点
Row({ space: 8 }) {
this.InfoDot(A, this.count + ' 部件')
this.InfoDot(A3, this.cols + '×' + this.rows + ' 网格')
}
@Builder InfoDot(color: string, text: string) {
Row() {
Row() {}.width(5).height(5).borderRadius(3).backgroundColor(color)
Text(' ' + text).fontSize(10).fontColor(T2)
}
}
部件数量用紫色圆点,网格尺寸用绿色圆点。两个信息用不同颜色标记,一眼就能区分。
工作区背景:渐变 + 点阵
// 渐变背景
Column() {}
.width('100%').height('100%').borderRadius(18)
.linearGradient({ angle: 180,
colors: [['#F0EEFA', 0], ['#F8F7FC', 0.5], ['#F5F5F9', 1]] })
// 点阵装饰
Circle({ width: 3, height: 3 }).fill(A + '08').position({ x: '10%', y: '10%' })
Circle({ width: 3, height: 3 }).fill(A + '06').position({ x: '30%', y: '20%' })
// ... 更多圆点
工作区背景从纯色变成了渐变 + 点阵。点阵用 A + '08'(8% 透明度紫色)的微小圆点,间距不规则,形成类似"网格纸"的质感。
这种装饰不影响功能,但让空白区域不那么单调。
Slider 的状态联动
Slider({ value: this.val * 100, min: 0, max: 100, step: 5, style: SliderStyle.OutSet })
.blockColor(this.on ? A : '#D0CED8')
.trackColor('#F0ECF8')
.selectedColor('#A78BFA')
未爆炸时滑块是灰色(#D0CED8),爆炸时变成紫色(A)。颜色变化暗示"现在可以操作了"。
颜色系统设计的原则
1. 一个颜色一个含义
紫色(A) → 品牌色、通用操作、手表场景
红色(A2) → 手机场景、爆炸操作、步骤 3
绿色(A3) → 大件场景、网格信息、步骤 2
蓝色(A4) → 相机场景、部件数量
黄色(A5) → 装饰性用途
每个颜色都有明确的语义。用户一旦建立了"红色=手机"的心理映射,后续看到红色就能快速关联到手机相关的信息。
2. 背景色是主色的淡化版
主色 #FF6B6B → 背景色 #FFF5F5
主色 #8A5DFA → 背景色 #F4F1FC
主色 #5DADE2 → 背景色 #EBF5FB
主色 #00B894 → 背景色 #E8F8F5
背景色是主色加了约 90% 白色的结果。这种"同色系淡化"保证了视觉一致性——选中卡片的背景和它的文字颜色属于同一个色系。
3. 部件颜色循环但不重复
const PC: string[] = ['#8A5DFA','#FF6B6B','#00B894','#5DADE2','#FECA57','#E8734A','#6C5CE7','#48DBFB']
c: PC[i % PC.length]
八个颜色循环使用。在 4×4 网格(16 个部件)中,每个颜色会出现两次。但相邻的部件颜色不同,视觉上不会混淆。
4. 不是所有元素都需要颜色
右箭头 SymbolGlyph 用淡灰色(#DDD),不参与颜色编码。因为它只是装饰性的引导符号,不需要传递信息。如果右箭头也用彩色,会增加视觉噪音。
踩坑记录
1. 半透明背景色的计算
// 主色 #FF6B6B → 浅红背景 #FFF5F5
计算方式:把主色的 RGB 值和白色(#FFFFFF)按 10:90 的比例混合。
R: 255 * 0.1 + 255 * 0.9 = 255 → FF
G: 107 * 0.1 + 255 * 0.9 = 240 → F0
B: 107 * 0.1 + 255 * 0.9 = 240 → F0
实际结果是 #FFF0F0,和代码中的 #FFF5F5 有微小差异——手动微调过,让视觉效果更好。
2. 颜色对比度
选中态的彩色背景上需要放白色文字(✓ 符号)。确保对比度足够:
#FF6B6B(红)上的白色 → 对比度 3.5:1 ✓
#8A5DFA(紫)上的白色 → 对比度 4.2:1 ✓
#00B894(绿)上的白色 → 对比度 2.8:1 ⚠️ 偏低
#5DADE2(蓝)上的白色 → 对比度 2.5:1 ⚠️ 偏低
绿色和蓝色上的白色对比度偏低,小字号(12px)可能看不清。实际使用中可以通过加深主色或加粗文字来补偿。
3. 颜色过多导致的混乱
五种颜色已经接近用户短期记忆的上限(心理学上的 “7±2” 法则)。如果后续再加颜色,建议:
- 新颜色只用于新功能区块,不复用已有颜色
- 同一屏幕内同时出现的颜色不超过 5 种
- 用灰色系(T1/T2/T3)作为中性色平衡视觉
4. 渐变背景上的颜色叠加
Column() {}
.linearGradient({ angle: 180,
colors: [['#F0EEFA', 0], ['#F8F7FC', 0.5], ['#F5F5F9', 1]] })
工作区的渐变背景会影响上面叠加元素的颜色感知。浅色元素在浅紫渐变上会显得更紫,在浅灰渐变上会显得更灰。点阵圆点用 A + '08'(极低透明度)就是为了减少这种干扰。
写在最后
多色系统的核心价值是"用颜色传递信息"——红色告诉你是手机,紫色告诉你是手表,绿色告诉你是大件。用户不需要读文字,颜色本身就在做分类。
但颜色不是越多越好。五种颜色是目前的平衡点:足够区分不同场景,又不至于让用户眼花。如果后续功能增加,可能需要重新评估颜色系统的扩展性。
部件的个性化着色是这次最实用的改进——在部件清单里,彩色编号 + 彩色深度条让用户能快速定位"第 3 个部件(绿色)在网格的左下角"。比单一颜色的编号圆圈高效很多。
更多推荐

所有评论(0)