
本文是「食刻 (ShiKe)」技术系列第 2 篇,深入解析 Neumorphism 2.5D 环形进度图 的纯 ArkUI 实现方案 ------ 7 层组件叠加,零依赖,零 GPU 开销。
一、先看效果
食刻首页的核心视觉元素是一个 环形进度图(Ring Progress) :


- 显示今日热量摄入进度
- 实时更新(添加食物后自动刷新)
- 超标时自动变红
- 看起来像 3D 的 ------ 有厚度、有光影、有材质感
这不是 Canvas 绘制的,不是图片,不是 3D 引擎渲染的。
这是用 7 个 ArkUI 原生组件叠加出来的。
二、为什么不用现成方案?
方案对比
| 方案 | 立体感 | 性能 | 依赖 | 可定制性 |
|---|---|---|---|---|
系统 Progress(Ring) |
❌ 平面 | ✅ 最优 | 无 | 低 |
| Canvas 2D 手绘 | ⚠️ 有限 | ⚠️ 中等 | 无 | 中 |
| ArkGraphics 3D | ✅ 真 3D | ❌ 重 | 引擎包 | 高 |
| Lottie 动画 | ✅ 好 | ⚠️ 中等 | json 文件 | 低 |
| Neumorphism 7层叠加 | ✅ 拟物 2.5D | ✅ 零 GPU 开销 | 无 | 极高 |
我们的选择:第 5 种 ------ 纯 ArkUI 声明式组件叠加
理由:
- 鸿蒙 App 包体积敏感,不想引入任何第三方库
- 进度环需要实时响应状态变化(@State 驱动),Canvas 重绘逻辑复杂
- ArkGraphics 3D 引擎对于"一个圆环"来说杀鸡用牛刀
- 我们要的是拟物质感,不是真 3D
三、7 层结构拆解
完整层级清单
Stack (130×130, alignContent: Center)
│
├── Layer 1: Circle strokeWidth(14) stroke(#C8D5CE)
│ .offset(y: 4) ← 底部厚度远层(最深的阴影)
│
├── Layer 2: Circle strokeWidth(14) stroke(#B8C7BE)
│ .offset(y: 2) ← 底部厚度近层
│
├── Layer 3: Circle strokeWidth(14) stroke(#E8F0EC)
│ ← 轨道凹陷底色(浅灰绿)
│
├── Layer 4: Progress Ring value(ratio)
│ strokeWidth(14) ← 绿色填充弧(主进度)
│ .color(#52B788)
│ .shadow(r=8, color=rgba(64,82,184,136)) ← 绿色投影辉光
│
├── Layer 5: Progress Ring value(35%)
│ strokeWidth(2.5) ← 顶部白色高光弧
│ .color(rgba(255,255,255,0.35))
│
├── Layer 6: Circle strokeWidth(1.5)
│ stroke(rgba(150,170,160,0.3)) ← 内圈暗面
│
└── Layer 7: Column center overlay
Text: "还可摄入 1723 kcal" ← 中心文字
每一层的作用
🎭 厚度模拟(Layer 1-2)
Layer 1: offset(y:4) + #C8D5CE(深)
Layer 2: offset(y:2) + #B8C7BE(中)
向下偏移 + 深于轨道的颜色 = 圆柱管壁截面的视觉错觉
就像你从侧面看一根管子,能看到它的厚度。
💡 光源模拟(Layer 5-6)
Layer 5: 白色 35% 弧(顶部高光)
Layer 6: 暗色内圈(底部阴影)
顶部亮 + 底部暗 = 光线从上方照射的直觉
这是 Neumorphism 的核心技巧。
四、关键代码实现
核心组件代码
typescript
// HomePage.ets --- Neumorphism Ring 核心实现
@Builder
NeumoRing(totalKcal: number, dailyLimit: number) {
Stack({ alignContent: Alignment.Center }) {
// Layer 1: 底部厚度远层
Circle()
.width(130)
.height(130)
.fill(Color.Transparent)
.strokeWidth(14)
.stroke('#C8D5CE')
.offset({ y: 4 })
// Layer 2: 底部厚度近层
Circle()
.width(130)
.height(130)
.fill(Color.Transparent)
.strokeWidth(14)
.stroke('#B8C7BE')
.offset({ y: 2 })
// Layer 3: 轨道底色
Circle()
.width(130)
.height(130)
.fill(Color.Transparent)
.strokeWidth(14)
.stroke('#E8F0EC')
// Layer 4: 主填充弧
Progress({
value: Math.min(100, (totalKcal / dailyLimit) * 100),
type: ProgressType.Ring
})
.width(130)
.height(130)
.strokeWidth(14)
.color(totalKcal > dailyLimit ? '#E07B5A' : '#52B788')
.shadow({ radius: 8, color: 'rgba(82,183,136,0.5)' })
.enableScan(false)
// Layer 5: 顶部高光弧
Progress({ value: 35, type: ProgressType.Ring })
.width(130)
.height(130)
.strokeWidth(2.5)
.color('rgba(255,255,255,0.35)')
.enableScan(false)
// Layer 6: 内圈暗面
Circle()
.width(130)
.height(130)
.fill(Color.Transparent)
.strokeWidth(1.5)
.stroke('rgba(150,170,160,0.3)')
// Layer 7: 中心文字
Column() {
Text(`${Math.max(0, dailyLimit - totalKcal)}`)
.fontSize(28)
.fontWeight(FontWeight.Bold)
.fontColor('#1A1A1A')
Text('还可摄入 kcal')
.fontSize(10)
.fontColor('#888888')
.margin({ top: 2 })
}
}
.width(130)
.height(130)
}
五、踩过的坑(血泪经验)
坑 1:Progress Ring 的"幽灵线"
现象 :使用 Progress(Ring) 作为底层轨道时,即使 value=0,也会显示一条极细的默认底轨线。
原因 :Progress 组件自带默认底轨样式,无法完全透明化。
解决 :所有不需要进度的圆环层全部改用 Circle().stroke() 替代 Progress(Ring)。Circle 是纯粹的描边圆,没有任何默认样式。
✅ 规则:静态装饰层用 Circle,动态进度层用 Progress
坑 2:抗锯齿黑边
现象 :圆环边缘出现黑色锯齿边,在浅色背景上特别明显。
原因 :.shadow() 使用纯黑半透明 rgba(0,0,0,x) 时,抗锯齿算法会产生黑边。
解决 :所有阴影颜色改用绿色系 rgba:
typescript
// ❌ 错误:黑色阴影 → 黑边
.shadow({ radius: 8, color: 'rgba(0,0,0,0.15)' })
// ✅ 正确:绿色系阴影 → 融合自然
.shadow({ radius: 8, color: 'rgba(82,183,136,0.5)' })
坑 3:超标变色
需求 :当 totalKcal > dailyLimit 时,填充色要从绿色变为红色/珊瑚色。
实现 :三元表达式直接控制 .color():
typescript
.color(totalKcal > dailyLimit ? '#E07B5A' : '#52B788')
// #E07B5A = 珊瑚色(警示但不刺眼)
// #52B788 = 草绿色(健康自然)
同时阴影颜色也要跟随变化:
typescript
.shadow({
radius: 8,
color: totalKcal > dailyLimit
? 'rgba(224,123,90,0.5)' // 珊瑚色投影
: 'rgba(82,183,136,0.5)' // 绿色投影
})
六、视觉效果对比
| 维度 | 传统 Progress Ring | 食刻 Neumorphism 环 |
|---|---|---|
| 立体感 | 平板 2D | 7 层拟物 2.5D |
| 材质感 | 无 | 管壁厚度 + 反光 + 阴影 |
| 状态反馈 | 单一颜色 | 0-100% 绿渐变 + 超标珊瑚色 |
| 性能 | 1 组件 | 7 组件(零 GPU 开销) |
| 依赖 | 系统组件 | 纯 ArkUI,零外部依赖 |
七、可复用性
这个 7 层环形进度图的方案不限于饮食场景,可以复用于:
- ✅ 健身 App:训练完成度
- ✅ 财务 App:月度预算消耗
- ✅ 学习 App:课程进度
- ✅ 任务管理:项目完成率
- ✅ 储水/电量类展示
- ✅ 任何 0-100% 的环形指标
- 只需替换颜色 Token 和中心文字
八、总结与下篇预告
核心要点回顾
- 7 层 = 2 层厚度 + 1 层底轨 + 1 层进度 + 1 层高光 + 1 层暗面 + 1 层文字
- 静态层用
Circle,动态层用Progress,避免幽灵线 - 阴影用同色系 rgba,避免抗锯齿黑边
.offset({y: 2,4})模拟厚度,.color(rgba白光)模拟光源- 整个方案零依赖、零 GPU 开销、纯声明式
下一篇:《鸿蒙 Widget 开发实战:3 张卡片实现桌面-App 全链路同步》
我们将深入 Form Kit 的跨进程通信机制,揭秘 Widget 如何与主 App 实时同步数据,以及如何实现"点击桌面卡片一键直达 AI 记录面板"的丝滑体验。