HarmonyOS 3D 拆解分析:发现卡片的个性化改造
前言
上一版的发现页有一个问题:所有卡片都用同一个🔧图标。不管是 iPhone 拆解还是无人机电路分析,左边的徽章都是一个扳手。用户扫一眼列表,很难快速区分"这是什么东西的拆解"。
这次改造的核心是"让每个项目有自己的图标"——iPhone 用📱,MacBook 用💻,AirPods 用🎧。用户不需要读标题,看图标就知道是什么设备。
DiscoverItem 的字段升级
旧版
interfaceDiscoverItem{title:stringdesc:stringlikes:string// '👍 128'color:string}新版
interfaceDiscoverItem{title:stringdesc:stringicon:string// 📱💻🎧🎮⌨️🚁likes:string// '128'(不再带 emoji)color:string}两个变化:
- 新增
icon字段:设备专属的 emoji likes简化:去掉了👍前缀,只保留数字字符串
为什么去掉 likes 的 emoji 前缀?
因为卡片上已经加了一个心形图标(sys.symbol.heart),不需要再用 emoji 表示"点赞"。如果心形图标 +👍 128同时出现,信息重复。
Mock 数据:每个设备一个图标
privategetMockDiscover():DiscoverItem[]{return[{title:'iPhone 15 拆解',desc:'...',icon:'📱',likes:'128',color:'#FF6B6B'},{title:'MacBook 主板分析',desc:'...',icon:'💻',likes:'96',color:'#5DADE2'},{title:'AirPods 内部结构',desc:'...',icon:'🎧',likes:'203',color:'#00B894'},{title:'Switch 手柄拆解',desc:'...',icon:'🎮',likes:'74',color:'#FECA57'},{title:'机械键盘轴体',desc:'...',icon:'⌨️',likes:'51',color:A},{title:'无人机电路分析',desc:'...',icon:'🚁',likes:'37',color:'#6C5CE7'}]}六个设备,六个不同的 emoji:
| 设备 | Emoji | 为什么选这个 |
|---|---|---|
| iPhone | 📱 | 手机的通用符号 |
| MacBook | 💻 | 笔记本电脑 |
| AirPods | 🎧 | 耳机 |
| Switch | 🎮 | 游戏手柄 |
| 键盘 | ⌨️ | 键盘本身 |
| 无人机 | 🚁 | 直升机/飞行器 |
这些 emoji 都是 Unicode 标准的一部分,在所有设备上都能正常显示。
DiscoverCard 的重构
旧版:传四个参数
@BuilderDiscoverCard(title:string,desc:string,likes:string,color:string){Row(){Column(){}.width(44).height(44).borderRadius(12).backgroundColor(color+'18')Stack(){Text('🔧').fontSize(18)// 所有卡片都是🔧}// ...}}新版:传整个对象
@BuilderDiscoverCard(item:DiscoverItem){Row(){// 设备图标徽章Column(){Text(item.icon).fontSize(24)}.width(48).height(48).borderRadius(14).backgroundColor(item.color+'15').justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)// 标题和描述Column(){Text(item.title).fontSize(15).fontWeight(FontWeight.Bold).fontColor(T1)Text(item.desc).fontSize(11).fontColor(T2).margin({top:2})}// 点赞和箭头Column(){Row({space:3}){SymbolGlyph($r('sys.symbol.heart')).fontSize(11).fontColor([item.color])Text(item.likes).fontSize(12).fontWeight(FontWeight.Medium).fontColor(item.color)}SymbolGlyph($r('sys.symbol.chevron_right')).fontSize(12).fontColor(['#DDD']).margin({top:6})}}.width('100%').padding(14).backgroundColor(CW).borderRadius(16).border({width:1,color:'#F0EEF6'}).shadow({radius:4,color:'rgba(0,0,0,0.02)',offsetY:1})}为什么改成传对象?
- 可扩展性:后续加字段(比如
viewCount、author)不需要改 Builder 的参数列表 - 类型安全:传对象时 TypeScript 会检查字段名是否正确,传参数时不会
- 代码可读性:
item.icon比icon参数更明确地表达了"这是什么"
图标徽章的变化
// 旧版.width(44).height(44).borderRadius(12)Text('🔧').fontSize(18)// 新版.width(48).height(48).borderRadius(14)Text(item.icon).fontSize(24)三个变化:
- 尺寸从 44×44 增大到 48×48——图标是卡片的视觉焦点,大一点更醒目
- 圆角从 12 增大到 14——和卡片的圆角(16)更协调
- 字号从 18 增大到 24——emoji 在更大的容器里需要更大的字号才能居中
背景色透明度微调
// 旧版.backgroundColor(color+'18')// 约 9% 透明度// 新版.backgroundColor(item.color+'15')// 约 8% 透明度从'18'(24/255 ≈ 9.4%)降到'15'(21/255 ≈ 8.2%)。因为图标从🔧变成了彩色 emoji——emoji 本身已经有颜色,背景色再深就会和图标"抢色"。
点赞区域:心形图标
Row({space:3}){SymbolGlyph($r('sys.symbol.heart')).fontSize(11).fontColor([item.color])Text(item.likes).fontSize(12).fontWeight(FontWeight.Medium).fontColor(item.color)}用系统心形图标(sys.symbol.heart)+ 数字的组合,替代了旧版的👍 128。好处:
- 心形图标和卡片主题色一致,视觉更协调
- 数字更紧凑,不会因为 emoji 宽度导致对齐问题
sys.symbol.heart是矢量图标,在不同分辨率下都清晰
卡片阴影
// 旧版:无阴影// 新版.shadow({radius:4,color:'rgba(0,0,0,0.02)',offsetY:1})极淡的阴影(2% 透明度),给卡片一个"微微浮起"的感觉。在紫色背景上,白色卡片 + 极淡阴影 = 层次感。
调用方式的变化
旧版
ForEach(this.discoverItems,(item:DiscoverItem)=>{this.DiscoverCard(item.title,item.desc,item.likes,item.color)})新版
ForEach(this.discoverItems,(item:DiscoverItem)=>{this.DiscoverCard(item)})从传四个参数变成传一个对象。调用代码更简洁,Builder 的参数列表也更短。
视觉效果对比
旧版卡片
┌─────────────────────────────┐ │ ┌────┐ │ │ │ 🔧 │ iPhone 15 拆解 │ │ └────┘ 专业维修指南... │ │ 👍 128 │ │ │ └─────────────────────────────┘所有卡片左边都是🔧,需要读标题才能区分。
新版卡片
┌─────────────────────────────┐ │ ┌────┐ │ │ │ 📱 │ iPhone 15 拆解 │ │ └────┘ 专业维修指南... │ │ ♡ 128 │ │ │ └─────────────────────────────┘ ┌─────────────────────────────┐ │ ┌────┐ │ │ │ 💻 │ MacBook 主板分析 │ │ └────┘ 芯片级拆解... │ │ ♡ 96 │ │ │ └─────────────────────────────┘每个卡片左边是不同的设备图标,扫一眼就能区分。
踩坑记录
1. emoji 的宽度不一致
不同 emoji 的显示宽度不同——📱 比 🔧 宽,⌨️ 比 🎮 窗。在 48×48 的容器里,有些 emoji 会显得"挤",有些会显得"空"。
解决方案:用fontSize(24)统一字号,让 emoji 自然居中。如果某个 emoji 显示效果不好,可以微调fontSize。
2. sys.symbol.heart 的颜色
SymbolGlyph($r('sys.symbol.heart')).fontSize(11).fontColor([item.color])心形图标的颜色跟随卡片主题色。但如果主题色是黄色(#FECA57),心形图标在白色背景上对比度不够。这是多色系统的一个已知限制——亮色系的主题色在小图标上可读性较差。
3. ForEach 的 keyGenerator
当前的ForEach没有指定keyGenerator。ArkUI 会用默认的索引作为 key。如果列表顺序变化(比如排序、插入),可能导致动画异常。
建议后续加上:
ForEach(this.discoverItems,(item:DiscoverItem)=>{this.DiscoverCard(item)},(item:DiscoverItem)=>item.title)// 用标题作为 key4. 异步数据和 mock 数据的切换
ForEach(this.discoverItems.length>0?this.discoverItems:this.getMockDiscover(),(item:DiscoverItem)=>{this.DiscoverCard(item)})getMockDiscover()在ForEach的三元表达式中被调用——每次渲染都会创建新数组。如果discoverItems为空(比如 API 还没返回),每次渲染都会重新创建 6 个 mock 对象。
这个问题在上一篇文章中已经提过,这次仍然存在。建议在aboutToAppear中预计算 mock 数据。
写在最后
发现卡片的个性化改造解决了一个核心问题:信息辨识度。旧版的🔧图标让用户需要读文字才能区分项目,新版的设备 emoji 让用户扫一眼就知道"这是什么"。
图标的选择遵循一个原则:用最常见的符号。📱代表手机、💻代表电脑、🎧代表耳机——这些符号已经被用户广泛认知,不需要学习成本。
后续如果接入真实 API,后端需要为每个拆解项目提供icon字段。可以是预定义的 emoji 列表,也可以是自定义图标(需要处理跨平台兼容性)。
