HarmonyOS 校园应用系列之鸿蒙 ArkUI 实战:校园打印我的页 —— 负 Margin 重叠与三种可视化组合技
HarmonyOS 校园应用系列之鸿蒙 ArkUI 实战:校园打印我的页 —— 负 Margin 重叠与三种可视化组合技
一、页面定位与技术复杂度
个人中心页(ProfileTab,底部 Tab 为 "我的")是本应用中 代码量最大(226 行)、组件种类最多(6 个 Builder)、视觉层次最丰富 的页面。它是一个微型的 "数据仪表盘 + 游戏化激励中心 + 功能导航枢纽"。
技术复杂度指标:
- 6 个独立 Builder 组件(UserCard、StatRow、WeekChartCard、AchievementWall、MenuList、About)
- 3 种数据可视化形式(经验进度条、柱状图、成就网格)
- 多种交互模式(点击事件、条件渲染、动态样式、负 Margin 重叠布局)



二、UserCard —— 全屏渐变用户信息卡
@Builder
UserCard() {
Column({ space: 12 }) {
Row({ space: 14 }) {
// 头像 + Lv 角标(Stack 叠放)
Stack({ alignContent: Alignment.BottomEnd }) {
Row() { Text('👤').fontSize(34) }
.width(60).height(60).backgroundColor('rgba(255,255,255,0.35)').borderRadius(D.rLg).justifyContent(FlexAlign.Center)
.border({ width: 2, color: 'rgba(255,255,255,0.7)' })
Row() { Text('Lv.4').fontSize(9).fontColor('#FFFFFF').fontWeight(FontWeight.Bold) }
.padding({ left: 5, right: 5, top: 1, bottom: 1 })
.backgroundColor(C.warn).borderRadius(4).border({ width: 1.5, color: '#FFFFFF' })
}
// 信息列
Column({ space: 5 }) {
Row({ space: 8 }) {
Text('小明同学').fontSize(18).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
Text('认证').fontSize(9).fontColor('#FFFFFF') // 认证徽章
.backgroundColor('rgba(255,255,255,0.3)').padding({ left: 5, right: 5, top: 1, bottom: 1 }).borderRadius(3)
}
Text('活跃用户 · 已使用 28 天').fontSize(11).fontColor('rgba(255,255,255,0.85)')
// 升级提示 + 经验条
Row({ space: 4 }) {
Text('距 Lv.5 还需').fontSize(9).fontColor('rgba(255,255,255,0.7)')
Text('120').fontSize(11).fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
Text('经验').fontSize(9).fontColor('rgba(255,255,255,0.7)')
}
Stack({ alignContent: Alignment.Start }) {
Column().width('100%').height(4).backgroundColor('rgba(255,255,255,0.25)').borderRadius(2)
Column().width('74%').height(4).backgroundColor('#FFFFFF').borderRadius(2)
}.width(140)
}.alignItems(HorizontalAlign.Start).layoutWeight(1)
}.width('100%').padding({ left: D.pad, right: D.pad, top: this.safeTop + 30, bottom: 16 })
// 快捷操作按钮
Row() {
Row({ space: 5 }) {
Text('🎁').fontSize(11)
Text('每日签到').fontSize(11).fontColor('#FFFFFF')
}
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.backgroundColor('rgba(255,255,255,0.2)').borderRadius(D.rSm)
.onClick(() => { promptAction.showToast({ message: '签到成功 +5 经验' }); })
Blank()
Row({ space: 5 }) {
Text('📊').fontSize(11)
Text('数据报告').fontSize(11).fontColor('#FFFFFF')
}
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.backgroundColor('rgba(255,255,255,0.2)').borderRadius(D.rSm)
.onClick(() => { promptAction.showToast({ message: '数据报告' }); })
}.width('100%').padding({ left: D.pad, right: D.pad, bottom: 14 })
}
.width('100%').linearGradient({ angle: 135, colors: [[C.primary, 0.0], [C.accent, 1.0]] })
}
2.1 通栏无圆角的沉浸式头部
UserCard 没有任何圆角——渐变卡片通栏铺满屏幕宽度,直接从状态栏下方(padding({ top: this.safeTop + 30 }))延伸到快捷按钮行。这种 "Hero Header" 让卡片像是从屏幕边缘生长出来的,增强全屏沉浸感。
2.2 头像上的 Lv 角标
头像用 Stack({ alignContent: Alignment.BottomEnd }) 叠放两层:底层是 👤 emoji 头像(60×60、35% 半透明白底、D.rLg 圆角、白色描边),右下角叠放 Lv.4 等级角标(C.warn 橙底白字、白色描边)。等级信息直接"贴"在头像上,比单独一行文字更节省空间,也是游戏类 App 的经典手法。
2.3 白色经验条
| 属性 | 值 | 设计意图 |
|---|---|---|
| 填充色 | #FFFFFF 纯白 | 在绿色渐变上对比度最高 |
| 轨道色 | rgba(255,255,255,0.25) | 半透明白融入渐变背景 |
| 当前进度 | 74% 宽(固定 140vp 总宽) | 距 Lv.5 还需 120 经验 |
经验条同样是 Stack 双层 Column 实现(与订单页进度条同款模式),高度 4vp。升级提示行"距 Lv.5 还需 120 经验"中,数字用 11vp 白色加粗、说明文字用 9vp 70% 白,形成小字号层级。
三、StatRow —— 四维彩色统计 + 负 Margin 重叠
@Builder
StatRow() {
Row() {
ForEach(this.stats, (s: StatItem, idx: number) => {
Column({ space: 3 }) {
Text(s.value).fontSize(18).fontWeight(FontWeight.Bold).fontColor(s.color)
Text(s.label).fontSize(10).fontColor(C.textDim)
}.layoutWeight(1)
if (idx < this.stats.length - 1) { Divider().vertical(true).height(32).color(C.stroke) }
}, (s: StatItem) => s.label)
}
.width('92%').height(76).backgroundColor(C.card).borderRadius(D.rMd)
.border({ width: 1, color: C.stroke })
.justifyContent(FlexAlign.SpaceAround).alignItems(VerticalAlign.Center).margin({ top: -28 })
}
3.1 负 Margin 重叠效果
.margin({ top: -28 }) 让白色统计卡片向上侵入渐变 UserCard 区域 28vp,形成 卡片重叠 视觉效果。优势:
- 打破单调垂直流,创造层次感和深度感
- 强化关联性:统计数据物理上"属于"用户
- 节省纵向空间
配合 width('92%')(两侧各留 4% 露出渐变底色)与 C.stroke 细边框,白色卡片仿佛"浮"在渐变背景之上。
3.2 四维色彩编码
| 维度 | 颜色 | 业务含义 |
|---|---|---|
| 历史订单 28 | 绿色 C.primary | 品牌主色,整体规模 |
| 进行中 3 | 亮绿 C.accent | 进行态 |
| 满意度 98% | 绿色 C.ok | 正向 |
| 连续天数 15 | 琥珀 C.warn | 忠诚度激励 |
四个统计项由 ForEach 数据驱动渲染,项间用竖向 Divider().vertical(true).height(32) 分隔(最后一项后不画),layoutWeight(1) 四等分。
四、WeekChartCard —— 周使用量柱状图
@Builder
WeekChartCard() {
Column({ space: 12 }) {
Row() {
Text('📈 本周使用').fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text)
Blank()
Text('20 次').fontSize(13).fontWeight(FontWeight.Bold).fontColor(C.primary) // 绿色强调
}.width('100%')
Row({ space: 4 }) {
ForEach(this.weekBars, (b: WeekBar, idx: number) => {
Column({ space: 6 }) {
Column()
.width(18)
.height(Math.max(6, b.value / 5 * 80))
.borderRadius(4)
.linearGradient({
angle: 180,
colors: idx === 5 ? [[C.primary, 0.0], [C.accent, 1.0]] : [[C.primarySoft, 0.0], [C.primary, 1.0]]
})
Text(b.day).fontSize(10).fontColor(idx === 5 ? C.primary : C.textDim)
.fontWeight(idx === 5 ? FontWeight.Bold : FontWeight.Normal)
}.layoutWeight(1).alignItems(HorizontalAlign.Center)
}, (b: WeekBar) => b.day)
}.width('100%')
}
.width('100%').padding(14).backgroundColor(C.card).borderRadius(D.rMd)
.border({ width: 1, color: C.stroke })
.margin({ top: 14, left: D.pad, right: D.pad })
}
与首页版本差异:X轴标签简写(一~日)、无数值标签、周六(idx === 5)高亮。柱体高度用 Math.max(6, b.value / 5 * 80) 计算——以本周最大值 5 次为基准映射到 80vp,最小 6vp 保底;高亮柱(周六 5 次)用主色→强调色纵向渐变,其余柱用浅绿→主色渐变,周六的星期文字同步变绿加粗。这里目的是展示 使用模式趋势 而非精确数值,更简洁。
五、AchievementWall —— 成就徽章墙
@Builder
AchievementWall() {
Column({ space: 12 }) {
Row() {
Text('🏅 成就徽章').fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text)
Blank()
Text(`${this.achievements.filter(a => a.unlocked).length}/${this.achievements.length}`)
.fontSize(11).fontColor(C.primary) // 进度指示器(动态计算)
}.width('100%')
Row() {
ForEach(this.achievements, (item: Achievement) => {
Column({ space: 6 }) {
Row() { Text(item.icon).fontSize(24) }
.width(48).height(48).borderRadius(D.rMd).justifyContent(FlexAlign.Center)
.backgroundColor(item.unlocked ? C.primarySoft : C.cardSoft)
.opacity(item.unlocked ? 1 : 0.4) // 锁定态半透明
Text(item.title).fontSize(9).fontColor(item.unlocked ? C.text : C.textDim)
Text(item.desc).fontSize(7).fontColor(C.textDim).maxLines(1)
}.layoutWeight(1)
}, (item: Achievement) => item.title)
}.width('100%')
}
.width('100%').padding(14).backgroundColor(C.card).borderRadius(D.rMd)
.border({ width: 1, color: C.stroke })
.margin({ top: 14, left: D.pad, right: D.pad })
}
5.1 成就数据结构
6 枚成就,4 枚已解锁:
| 成就 | 图标 | 条件 | 状态 |
|---|---|---|---|
| 初次使用 | 🎯 | 第一次下单 | ✅ 已解锁 |
| 活跃用户 | 🔥 | 使用7天 | ✅ 已解锁 |
| 好评达人 | ⭐ | 5星好评10次 | ✅ 已解锁 |
| 省钱能手 | 💰 | 节省100元 | ✅ 已解锁 |
| 资深用户 | 👑 | 使用100天 | 🔒 未解锁 |
| 完美用户 | 💎 | 满意度100% | 🔒 未解锁 |
每枚成就包含 icon、title、unlocked、desc 四个字段。徽章图标是 emoji Text(24vp)放在 48×48、D.rMd 圆角的方块中——已解锁用浅绿底 C.primarySoft,未解锁用浅灰底 C.cardSoft。
5.2 opacity 锁定态处理
未解锁成就使用 .opacity(0.4) 降低到 40% 不透明度。选择 opacity 而非隐藏或锁图标是有意设计——用户能看到"还有什么可以达成",比神秘感更能激发行动。desc 文字清晰告知解锁条件(如 "使用100天"),且 .maxLines(1) 保证长描述不换行破坏网格。
5.3 动态进度指示器
标题右侧的 4/6 并非写死,而是 ${this.achievements.filter(a => a.unlocked).length}/${this.achievements.length} 实时计算——解锁任何一枚成就后数字自动更新。分数式进度表达比百分比更直观——用户一眼看出"还差 2 个就满了",无需心算。

六、MenuList —— 功能菜单列表
@Builder
MenuList() {
Column() {
ForEach(this.menus, (m: MenuItem, idx: number) => {
Column() {
Row({ space: 12 }) {
Row() { Text(m.emoji).fontSize(20) }
.width(36).height(36).backgroundColor(C.cardSoft).borderRadius(D.rSm).justifyContent(FlexAlign.Center)
Column({ space: 2 }) {
Text(m.title).fontSize(14).fontWeight(FontWeight.Medium).fontColor(C.text)
Text(m.desc).fontSize(11).fontColor(C.textDim)
}.alignItems(HorizontalAlign.Start).layoutWeight(1)
if (m.badge.length > 0) {
Text(m.badge).fontSize(9).fontColor('#FFFFFF')
.backgroundColor(m.badge === 'NEW' ? C.ok : C.danger)
.padding({ left: 5, right: 5, top: 1, bottom: 1 }).borderRadius(D.rSm)
}
Text('›').fontSize(22).fontColor(C.textDim)
}
.width('100%').padding(14)
.onClick(() => { promptAction.showToast({ message: m.title }); })
if (idx < this.menus.length - 1) { Divider().color(C.stroke).margin({ left: 58 }) }
}
}, (m: MenuItem) => m.title)
}
.width('100%').backgroundColor(C.card).borderRadius(D.rMd).border({ width: 1, color: C.stroke })
.margin({ top: 14, left: D.pad, right: D.pad }).clip(true)
}
菜单共 5 项:📦 历史订单、📊 消费统计、🔔 提醒设置、⭐ 我的收藏(带 NEW 角标)、⚙️ 设置。
6.1 标准三段式菜单项
| 区域 | 组件 | 内容 |
|---|---|---|
| 左侧 | emoji Text(36vp 方块,浅灰底) | 功能图标 |
| 中间 | Column(标题+描述) | 主标题 + 辅助说明 |
| 右侧 | Text('›')(22vp)+ 可选 badge | 导航指示 + 新功能标记 |
与 iOS 设置页标准一致,零学习成本。badge 字段非空时渲染小胶囊——'NEW' 用绿色 C.ok 底,其他值用红色 C.danger 底。
6.2 分割线
分割线不用 .divider() 属性,而是 条件渲染 Divider 组件:if (idx < this.menus.length - 1) 保证最后一项后不画线,.margin({ left: 58 }) 让分割线从文字起始处开始(跳过左侧图标区),视觉上"属于"文字列。外层 .clip(true) 配合圆角防止首尾分割线溢出卡片圆角。
6.3 路由跳转扩展
当前源码点击菜单项只弹 toast(promptAction.showToast({ message: m.title }))。实际产品中推荐使用 Navigation 组件的 NavPathStack 跳转(router.pushUrl 虽仍可用但官方推荐迁移至 Navigation):
this.pathStack.pushPath({
name: m.id === 1 ? 'OrderHistoryPage' : 'StatsPage',
params: { from: 'profile' }
})
菜单作为导航枢纽通常采用 pushPath 模式——用户可通过返回键回到个人中心。

七、快捷按钮 —— 玻璃拟态设计
"每日签到"与"数据报告"两个按钮直接内联在 UserCard 底部(没有抽成独立 Builder):
Row({ space: 5 }) {
Text('🎁').fontSize(11)
Text('每日签到').fontSize(11).fontColor('#FFFFFF')
}
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.backgroundColor('rgba(255,255,255,0.2)').borderRadius(D.rSm)
.onClick(() => { promptAction.showToast({ message: '签到成功 +5 经验' }); })
玻璃拟态(Glassmorphism) 效果——20% 半透明白色背景让按钮像"浮"在渐变上的薄层。emoji 图标(🎁 / 📊)+ 11vp 白字 + D.rSm 圆角,两个按钮分居左右(中间 Blank() 撑开),点击分别弹出"签到成功 +5 经验"和"数据报告" toast。

八、沉浸式全屏技术实现链路
- EntryAbility 中
setWindowLayoutFullScreen(true) - 通过
getWindowAvoidArea()计算安全区数值并写入 AppStorage - 页面通过
@StorageProp('safeTop')单向读取 - UserCard 渐变从安全区顶部开始(
padding({ top: this.safeTop + 30 })),创造无缝视觉效果
这种 "Hero Header" 设计已成为现代 App 个人中心的标准配置(网易云音乐、Keep、知乎均采用类似方案)。
页面末尾还有一个轻量的 About 组件:
@Builder
About() {
Column({ space: 4 }) {
Text('Version 1.0.0').fontSize(12).fontColor(C.textDim)
Text('HarmonyOS 6.0+ · ArkTS · API 23').fontSize(10).fontColor(C.textDim)
}
.width('100%').padding({ top: 24, bottom: 10 }).alignItems(HorizontalAlign.Center)
}
版本号与技术栈信息居中灰字展示,是个人中心页的收尾惯例。
九、成就系统的产品运营视角
核心指标:解锁率 和 留存贡献度。本例 4/6 = 67% 解锁率意味着足够沉没成本;剩余 2 枚提供短期目标。
心理学基础是 "目标梯度效应" ——越接近目标动力越强。关键在于:
- 第一个成就要容易(初次使用只需第一次下单)
- 中期成要有节奏(7天→10次好评→节省100元)
- 终极成要稀缺(100天使用、100%满意度)
十、总结
| 组件 | 技术要点 | 复杂度 |
|---|---|---|
| UserCard | 通栏渐变 + Stack头像Lv角标 + 白色经验条 + 玻璃拟态按钮 | ⭐⭐⭐⭐⭐ |
| StatRow | 四维彩色统计 + 竖向Divider + 负Margin浮动 | ⭐⭐⭐⭐ |
| WeekChartCard | 纯ArkUI柱状图 + Math.max保底高度 + 周六高亮 | ⭐⭐⭐ |
| AchievementWall | 徽章网格 + opacity锁定态 + 动态进度计算 | ⭐⭐⭐⭐ |
| MenuList | 三段式菜单 + 条件Divider + badge角标 | ⭐⭐⭐ |
| About | 版本信息收尾 | ⭐ |
负 Margin 重叠布局 和 opacity 锁定态处理 是最具价值的两个设计模式——前者以极简手段创造丰富层次,后者在"展示全部"和"制造稀缺感"间找到平衡。
更多推荐




所有评论(0)