鸿蒙原生应用实战:用 ArkUI 搭志愿服务首页 —— 沉浸式 HeroCard 与项目推荐

App 17「志愿服务」首页(HomeTab),主题色 #EF4444 红色,4 个 Tab 分别为首页(🤝)、活动(📋)、记录(🏅)、我的(👤)。首页采用沉浸式渐变 HeroCard(智能管理 + 3 栏统计)+ 快捷功能入口 + 本周数据可点击条形图 + 横向分类筛选 + 推荐项目列表(含进度条与 HOT/NEW/精选 标签)。本篇基于 17-volunteer/entry/src/main/ets/pages/HomeTab.ets(约 193 行)逐段拆解,附 4 张实机截图。

一、整体结构:5 个 @Builder 的内容流

HomeTab 的结构与 App 13/14/15/16 高度同源——都是 5 个 @Builder 块拼出"信息流首页":

build() {
  Column() {
    Scroll() {
      Column({ space: 14 }) {
        this.HeroCard()
        this.QuickActionRow()
        this.WeekChartCard()
        this.CategoryBar()
        this.ItemList()
        Blank().height(this.safeBottom + 20)
      }
      .width('100%').padding({ left: D.pad, right: D.pad })
    }
    .layoutWeight(1).scrollBar(BarState.Off).align(Alignment.Top)
  }
  .width('100%').height('100%').backgroundColor(C.bg)
}

5 个块按"头部 → 工具 → 数据 → 筛选 → 列表"五段叙事

  1. HeroCard — 红色渐变欢迎 + 3 栏统计
  2. QuickActionRow — 4 个圆形入口(搜索/收藏/统计/消息)
  3. WeekChartCard — 本周数据可点击条形图
  4. CategoryBar — 横向分类筛选(全部/推荐/热门/最新)
  5. ItemList — 3 条推荐项目(含进度条与标签)

"沉浸式顶部"设计:HeroCard 的 padding({ top: this.safeTop + 20 }) 让红色渐变延伸到状态栏背后,形成"顶部品牌色块"——这是本系列一直沿用的沉浸式模式。

Blank().height(this.safeBottom + 20) 底部留白:让最后一条卡片不贴到 Tab 栏,加 20vp 视觉缓冲。

志愿服务首页首屏 · 红色渐变HeroCard+智能管理+3栏统计+快捷入口+本周数据

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

配图

二、HeroCard:红色渐变 + 智能管理 + 3 栏统计

HeroCard 是顶部品牌区——红色(C.primary)渐变到浅红(C.accent)形成"热情公益"的视觉调性:

@Builder
HeroCard() {
  Column({ space: 12 }) {
    Row({ space: 12 }) {
      Text('🏠').fontSize(24)
      Column({ space: 4 }) {
        Text('智能管理').fontSize(20).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
        Text('高效便捷 · 智能推荐').fontSize(12).fontColor('rgba(255,255,255,0.85)')
      }.alignItems(HorizontalAlign.Start).layoutWeight(1)
    }.width('100%')

    Row() {
      ForEach(this.stats, (s: StatItem, idx: number) => {
        Column({ space: 2 }) {
          Text(s.value).fontSize(20).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
          Text(s.label).fontSize(10).fontColor('rgba(255,255,255,0.8)')
        }.alignItems(HorizontalAlign.Start).layoutWeight(1)
        if (idx < this.stats.length - 1) {
          Column().width(1).height(28).backgroundColor('rgba(255,255,255,0.25)')
        }
      }, (s: StatItem) => s.label)
    }.width('100%')
  }
  .width('100%').padding(18).padding({ top: this.safeTop + 20 })
  .borderRadius({ bottomLeft: D.rLg, bottomRight: D.rLg })
  .linearGradient({ angle: 135, colors: [[C.primary, 0.0], [C.accent, 1.0]] })
}

两个 padding 写法

.width('100%').padding(18).padding({ top: this.safeTop + 20 })

padding(18).padding({ top: this.safeTop + 20 }) 是 ArkUI 里"叠加 padding"的写法——先给四面 18vp,再单独把顶部覆盖为 safeTop + 20。这样左右下三边距统一为 18,顶部则跟随状态栏高度自适应。比 padding({ top: safeTop+20, left: 18, right: 18, bottom: 18 }) 这种"四值全写"简洁。

borderRadius({ bottomLeft: D.rLg, bottomRight: D.rLg }) 只圆角底部:让顶部与状态栏无缝拼接(沉浸式),底部则形成大圆角与下方白色卡片做软分隔。"上不留白、下大圆角"是沉浸式 HeroCard 的标准做法

3 栏统计128 / 32 / 86% 对应"总数 / 今日 / 完成率"——"量 → 速 → 质"三角

  • 总数 128 ——累计活动总量
  • 今日 32 ——当日新增
  • 完成率 86% ——完成质量

rgba(255,255,255,0.25) 25% 不透明白色作为分隔竖线,在红色背景上柔和不刺眼。if (idx < this.stats.length - 1) 防止最后一条也加分隔线。

HeroCard 与 App 13 同源:都是"图标 + 标题 + 副标题 + 3 栏统计 + 135° 渐变"——同一套模板,换主题色(紫→红)+ 换标题文案(音乐歌单→智能管理)

三、QuickActionRow:4 个圆形入口

QuickActionRow 是 4 个常用功能(搜索/收藏/统计/消息)的快捷入口:

@Builder
QuickActionRow() {
  Row() {
    ForEach(this.actions, (item: QuickAction) => {
      Column({ space: 6 }) {
        Row() { Text(item.icon).fontSize(22) }
          .width(44).height(44).backgroundColor(C.cardSoft).borderRadius(D.rSm).justifyContent(FlexAlign.Center)
        Text(item.title).fontSize(10).fontColor(C.textSub)
      }
      .layoutWeight(1)
      .onClick(() => { promptAction.showToast({ message: item.title }); })
    }, (item: QuickAction) => item.title)
  }
  .width('100%').padding(12).backgroundColor(C.card).borderRadius(D.rMd)
  .border({ width: 1, color: C.stroke }).justifyContent(FlexAlign.SpaceAround)
}

44×44 圆角图标块borderRadius(D.rSm) 10vp 圆角,非正圆)+ 下方 10sp 文字。justifyContent(FlexAlign.SpaceAround) 让 4 个入口之间留出等距空白。promptAction.showToast({ message: item.title }) 是"轻反馈"——点击立刻弹"搜索""收藏"等文字,给用户"我收到了点击"的反馈。

与 App 13 几乎完全同源——只有 actions 数组的内容略不同(App 13 是 4 个 app 内功能,App 17 是"搜索/收藏/统计/消息")。同一套"圆形图标+文字"骨架换数据

四、WeekChartCard:可点击的 7 天条形图

WeekChartCard 是 7 天数据条形图,点击柱子切换高亮(与 App 13 相同的"可交互周图"):

@Builder
WeekChartCard() {
  Column({ space: 12 }) {
    Row() {
      Text('📊 本周数据').fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text)
      Blank()
      Text('30 次').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 / 7 * 80))
            .borderRadius(4)
            .linearGradient({
              angle: 180,
              colors: idx === this.selectedBar
                ? [[C.primary, 0.0], [C.accent, 1.0]]
                : [[C.primarySoft, 0.0], [C.primary, 1.0]]
            })
          Text(b.day).fontSize(10)
            .fontColor(idx === this.selectedBar ? C.primary : C.textDim)
            .fontWeight(idx === this.selectedBar ? FontWeight.Bold : FontWeight.Normal)
        }.layoutWeight(1).alignItems(HorizontalAlign.Center)
        .onClick(() => { this.selectedBar = idx; })
      }, (b: WeekBar) => b.day)
    }.width('100%')
  }
  .width('100%').padding(14).backgroundColor(C.card).borderRadius(D.rMd)
  .border({ width: 1, color: C.stroke })
}

柱子高度 = b.value / 7 * 80:把 value 归一化到 80vp 上限。Math.max(6, ...) 兜底最低 6vp 防止柱子消失。选中柱子用主色渐变(红→浅红),未选用浅红→红(逆向)——视觉上"激活"更亮。

@State selectedBar: number = 4 初始选中"五"(idx=4,value=4)——**"周五数据刚好居中"**是 demo 的默认值巧思(既能演示选中态,又不显眼)。

点击 this.selectedBar = idx; 切换高亮——@State 一变,整张卡的渐变色和文字重渲染。"点击即反馈"的响应式范式

30 次汇总:3+5+2+6+4+7+3 = 30——"汇总 = 各柱之和"完全成立(App 13 同样是 30 次 = 3+5+2+6+4+7+3 也成立,App 15/16 有口径不一致的坑,App 17 与 App 13 一样严格自洽)。

"30 次"的语义是"本周完成 30 次志愿活动"——"次数"是志愿服务的核心量化指标(比"时长"更直观)。"30 次"= 每位志愿者每天 4-5 次活动是合理的活跃水平。

五、CategoryBar:横向分类筛选

CategoryBar 是 4 个分类(全部/推荐/热门/最新)的胶囊标签,横滑滚动

@Builder
CategoryBar() {
  Scroll() {
    Row({ space: 8 }) {
      ForEach(this.cats, (cat: string, idx: number) => {
        Text(cat).fontSize(13)
          .fontColor(this.activeCat === idx ? '#FFFFFF' : C.textSub)
          .backgroundColor(this.activeCat === idx ? C.primary : C.card)
          .borderRadius(D.rSm).border({ width: 1, color: this.activeCat === idx ? C.primary : C.stroke })
          .padding({ left: 14, right: 14, top: 8, bottom: 8 })
          .onClick(() => { this.activeCat = idx; })
      }, (cat: string) => cat)
    }
  }
  .scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off).width('100%')
}

选中态三件套(背景/文字/边框)——同系列标准做法:

  • 选中:主色背景(红)+ 白字 + 主色描边
  • 未选:白底 + 灰字 + 浅灰描边

Scroll 包裹让 4 个分类可横滑(4 个装得下,但 Scroll 让未来加分类不会破版)。"分类条 + 横滑"是移动端"分类筛选"的标准组件

onClick(() => { this.activeCat = idx; }) 切换状态——demo 没有联动 ItemList 过滤(与 App 13 一样是"假筛选"),但状态机已就绪,真实项目只需加 filteredItems() 即可联动。

六、ItemList:3 条推荐项目(含标签+进度条)

ItemList 是首页的"内容主体"——3 条推荐项目,每条都是"emoji + 标题 + 标签 + 描述 + 进度条":

@Builder
ItemList() {
  Column({ space: 10 }) {
    Row() {
      Text('推荐列表').fontSize(16).fontWeight(FontWeight.Bold).fontColor(C.text)
      Blank()
      Text('查看全部 ›').fontSize(11).fontColor(C.primary)
    }.width('100%')

    ForEach(this.items, (item: ListItem) => {
      Column({ space: 10 }) {
        Row({ space: 12 }) {
          Row() { Text(item.emoji).fontSize(28) }
            .width(56).height(56).backgroundColor(C.cardSoft).borderRadius(D.rMd).justifyContent(FlexAlign.Center)
          Column({ space: 4 }) {
            Row() {
              Text(item.title).fontSize(15).fontWeight(FontWeight.Bold).fontColor(C.text).layoutWeight(1)
              Text(item.tag).fontSize(9).fontColor('#FFFFFF')
                .backgroundColor(item.tag === 'HOT' ? C.danger : item.tag === 'NEW' ? C.ok : C.accent)
                .padding({ left: 5, right: 5, top: 2, bottom: 2 }).borderRadius(D.rSm)
            }.width('100%')
            Text(item.desc).fontSize(12).fontColor(C.textDim)
          }.alignItems(HorizontalAlign.Start).layoutWeight(1)
        }.width('100%')

        Row() {
          Text('进度').fontSize(10).fontColor(C.textDim)
          Stack({ alignContent: Alignment.Start }) {
            Column().width('100%').height(5).backgroundColor(C.cardSoft).borderRadius(3)
            Column().width(`${item.progress}%`).height(5).borderRadius(3).backgroundColor(C.primary)
          }.layoutWeight(1).margin({ left: 8, right: 8 })
          Text(`${item.progress}%`).fontSize(10).fontColor(C.primary).fontWeight(FontWeight.Bold)
        }.width('100%')
      }
      .width('100%').padding(12).backgroundColor(C.card).borderRadius(D.rMd)
      .border({ width: 1, color: C.stroke })
      .onClick(() => { promptAction.showToast({ message: item.title }); })
    }, (item: ListItem) => item.id.toString())
  }.width('100%')
}

6.1 三种标签的语义色彩

item.tag === 'HOT' ? C.danger : item.tag === 'NEW' ? C.ok : C.accent 是关键三元表达式:

  • HOTC.danger 深红(#FF5A6E)——最显眼的"热门"标签
  • NEWC.ok 绿色(#2BB673)——清新"新"标签
  • 精选C.accent 浅红(#F87171)——品牌色"精选"标签

3 色映射对应 3 种语义——"HOT"是紧迫感(红色警报)、"NEW"是新鲜感(绿色生命)、"精选"是品牌质感(主题浅色)。**"标签色 = 标签语义"**是良好的视觉设计。

3 条项目:📦 热门项目一 HOT(已参与 128 人,进度 75%)/ 🎯 推荐项目二 NEW(86 人,45%)/ ⭐ 精选项目三 精选(256 人,90%)——3 种标签都有数据展示

6.2 Stack 进度条(双通道)

Row() {
  Text('进度').fontSize(10).fontColor(C.textDim)
  Stack({ alignContent: Alignment.Start }) {
    Column().width('100%').height(5).backgroundColor(C.cardSoft).borderRadius(3)
    Column().width(`${item.progress}%`).height(5).borderRadius(3).backgroundColor(C.primary)
  }.layoutWeight(1).margin({ left: 8, right: 8 })
  Text(`${item.progress}%`).fontSize(10).fontColor(C.primary).fontWeight(FontWeight.Bold)
}

"Stack 进度条 = 背景条 + 前景条叠加"——Stack({ alignContent: Alignment.Start }) 让前景对齐到左边起点,背景 100% 宽、前景 ${item.progress}% 宽。width 用模板字符串拼接百分比,item.progress 既是宽度数字又是右侧文字一处数据两处联动

"进度"标签 + 进度条 + 百分比文字"——三处同步表达"完成度":

  • "进度" 文字(10sp 灰)
  • 进度条(5vp 高主色圆角)
  • "75%" 数字(10sp 主色加粗)

色弱用户不靠颜色就能读(靠"进度"和"75%"两处文字)——"颜色 + 文字双通道"原则

"项目 128/86/256 人参与"的数字故事:项目三(精选 256 人 + 90% 完成度)参与最多且进度最远——**"参与人数与完成进度正相关"**符合大众心理(好项目越多人参与、参与越多越容易完成)。三个数据互相印证,首页"数据自洽"。

志愿服务首页底部 · 推荐列表完整+底部Tab栏

七、内容流首页的"节奏感"设计

App 17 首页 5 块内容从上到下有明确的"节奏感"——

第 1 块 HeroCard(红色渐变):视觉冲击最强(红色 135° 渐变),承担"品牌印象"。用户在首页第 1 秒看到这块——决定"这个 App 是做什么的"。

第 2 块 QuickActionRow(4 入口):中度视觉(白色卡片 + 圆角图标),承担"工具导航"。节奏从"冲击"过渡到"实用"

第 3 块 WeekChartCard(条形图):中度视觉(白色卡片 + 数据图),承担"行为数据"。节奏从"导航"过渡到"数据"

第 4 块 CategoryBar(横向分类):轻度视觉(小胶囊标签),承担"分类筛选"。节奏从"数据"过渡到"选择"——视觉重量递减,给用户"选择更多"的心理暗示。

第 5 块 ItemList(推荐项目):中重视觉(白卡 + 大色块 + 进度条 + 标签),承担"主内容"。节奏从"选择"过渡到"消费"——这是用户最终要消费的内容

"节奏感" = 视觉重量的"强 → 中 → 中 → 轻 → 中"的起伏——不是"统一重量从头到尾"(会显得平铺直叙),也不是"忽强忽弱"(会显得杂乱)。"先冲击后实用"是设计节奏的黄金法则

对比 App 14 首页

  • App 14:HeroCard(活动)→ QuickActionRow(4 运动)→ WeeklyChart(数据)→ SectionTitle + VenueList(5 场地)→ SectionTitle + RankList(3 排行)
  • App 17:HeroCard(活动)→ QuickActionRow(4 入口)→ WeekChartCard(数据)→ CategoryBar(分类)→ ItemList(3 项目)

两者结构完全同源——"5 块节奏"是系列化首页的通用骨架

志愿服务首页下部 · 推荐项目1+2+3完整+进度条+底部Tab栏

八、@State 的克制 + 主题色的力量

HomeTab 只有 2 个 @StateactiveCat(分类选中)+ selectedBar(柱子高亮)。其他数据全部 private 不可变——"会变的只有交互态"是 ArkUI 健康状态管理的铁律

主题色 #EF4444 红色的语义:红色在中国文化里"热情、公益、爱心",是志愿服务的经典色(红十字会、志愿者红马甲)。相比 App 14 绿(运动活力)和 App 15 绿(环保),App 17 的红更"情感"——"颜色即产品调性"

与 App 13 紫色对比:App 13 紫 #8B5CF6(音乐、浪漫、二次元),App 17 红 #EF4444(志愿、热情、公益)——"同一套代码框架换主题色 + 换文案 = 完全不同的产品"。这是系列化 demo 最重要的方法论:模板复用、主题演化

志愿服务首页中段 · 4个圆形入口+本周数据条形图(周六高亮)+分类筛选+推荐项目1+2+3

九、总结

App 17 首页解析完毕。沉浸式红色 HeroCard + 可点击周图 + 横向分类 + 标签+进度条项目卡是四大亮点,配合红色志愿服务调性与 App 13 的"主题复刻"展示了一个重要方法论:同一套 UI 框架,换主题色 + 换文案 + 换数据 = 完全不同的产品。

Logo

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

更多推荐