HarmonyOS 校园应用系列之鸿蒙 ArkUI 实战:校园打印我的页 —— 负 Margin 重叠与三种可视化组合技

一、页面定位与技术复杂度

个人中心页(ProfileTab,底部 Tab 为 "我的")是本应用中 代码量最大(226 行)、组件种类最多(6 个 Builder)、视觉层次最丰富 的页面。它是一个微型的 "数据仪表盘 + 游戏化激励中心 + 功能导航枢纽"

技术复杂度指标:

  • 6 个独立 Builder 组件(UserCard、StatRow、WeekChartCard、AchievementWall、MenuList、About)
  • 3 种数据可视化形式(经验进度条、柱状图、成就网格)
  • 多种交互模式(点击事件、条件渲染、动态样式、负 Margin 重叠布局)

应用效果图

统计我的页首屏

项目源码开源:https://gitee.com/codenestFlow/HarmonyOSHub

配图

二、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。


统计页滚动 - 底部菜单

八、沉浸式全屏技术实现链路

  1. EntryAbility 中 setWindowLayoutFullScreen(true)
  2. 通过 getWindowAvoidArea() 计算安全区数值并写入 AppStorage
  3. 页面通过 @StorageProp('safeTop') 单向读取
  4. 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 枚提供短期目标。

心理学基础是 "目标梯度效应" ——越接近目标动力越强。关键在于:

  1. 第一个成就要容易(初次使用只需第一次下单)
  2. 中期成要有节奏(7天→10次好评→节省100元)
  3. 终极成要稀缺(100天使用、100%满意度)

十、总结

组件技术要点复杂度
UserCard通栏渐变 + Stack头像Lv角标 + 白色经验条 + 玻璃拟态按钮⭐⭐⭐⭐⭐
StatRow四维彩色统计 + 竖向Divider + 负Margin浮动⭐⭐⭐⭐
WeekChartCard纯ArkUI柱状图 + Math.max保底高度 + 周六高亮⭐⭐⭐
AchievementWall徽章网格 + opacity锁定态 + 动态进度计算⭐⭐⭐⭐
MenuList三段式菜单 + 条件Divider + badge角标⭐⭐⭐
About版本信息收尾

负 Margin 重叠布局opacity 锁定态处理 是最具价值的两个设计模式——前者以极简手段创造丰富层次,后者在"展示全部"和"制造稀缺感"间找到平衡。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐