鸿蒙应用开发之家庭应急物资个人中心深度实践:渐变用户卡与过期提醒开关组合机制

1、引言

场景是这样的:我们团队最近在开发一款面向家庭用户的应急物资管理应用。产品经理提出了一个极其明确的诉求——"我的"页面不仅要展示用户身份和统计数据,还必须把"过期提醒"开关放在最显眼的位置,因为应急物资的过期管理是整个应用的核心安全功能:过期的药品不能吃、过期的罐头不能吃,用户必须能在第一时间开启提醒。

在传统的移动端开发中,这种"身份卡 + 统计 + 开关 + 菜单"的多区布局,往往需要用 ScrollView 嵌套多个自定义 View,开关状态与推送调度的联动逻辑散落在 Activity 和 Service 之间,统计数据的实时聚合要手写 ObservablePattern,维护成本极高。

幸运的是,在 HarmonyOS 的 ArkUI 声明式开发范式中,@Builder 装饰器给了我们一种极其优雅的方案:将我的页拆分为五个独立的构建块(UserCard、StatRow、RemindCard、MenuList、About),开关用原生 Toggle 组件一行搞定,统计行用负边距悬浮在渐变卡上,整页代码不到 120 行。

这篇实战手记,我们将从 60-emergency-kit/entry/src/main/ets/pages/ProfileTab.ets(共 115 行)切入,不仅教你如何用 @Builder 拆解个人中心的五区结构,更重要的是,借此机会彻底拆解橙棕渐变用户卡与负边距悬浮统计行的层次叠加机制,以及过期提醒开关与检查页过期项的推送联动设计。我们不背文档,不讲空话,直接看真实场景下的解法。

2、效果展示与页面结构

在正式深入代码之前,我们先看看最终的页面表现和工程底座。直观的视觉冲击能让你瞬间明白我们在解决什么问题。

我的页是"应急身份 + 统计 + 提醒开关 + 工具入口"的五区布局,整体由 5 个 @Builder 块构成:

build() {
  Column() {
    Scroll() {
      Column() {
        this.UserCard()
        this.StatRow()
        this.RemindCard()
        this.MenuList()
        this.About()
      }
      .width('100%')
      .padding({ bottom: D.pad + this.safeBottom + 20 })
    }
    .layoutWeight(1).scrollBar(BarState.Off).align(Alignment.Top)
  }
  .width('100%').height('100%').backgroundColor(C.bg)
}

这个结构的设计初衷是明确划分"身份、数据、能力、入口、版权"的职责边界:

  1. UserCard — 橙棕渐变用户卡(应急管家身份 + “未雨绸缪,安心生活”)
  2. StatRow — 负边距三列统计行(32 物资/15 采购/75% 准备度,上移 24 悬浮)
  3. RemindCard — 过期提醒开关卡(物资临期时提醒补充,默认开启)
  4. MenuList — 四工具菜单(采购记录/库存设置/过期提醒/设置)
  5. About — 底部版本信息(家庭应急物资 Version 1.0.0)

我的页回答的核心问题是"我是谁、家里备了多少、怎么管提醒、去哪配置"——用户卡亮身份、统计行给总量、开关卡提供核心能力(过期提醒)、菜单给工具入口,一屏完成"身份认知 → 数据总览 → 能力配置 → 工具直达"的完整链路。

我的页整体布局

3、UserCard:橙棕渐变应急管家卡
UserCard() {
  Row({ space: 14 }) {
    Row() { Text('🎒').fontSize(32) }
    .width(64).height(64).backgroundColor('#33FFFFFF').borderRadius(32).justifyContent(FlexAlign.Center)
    Column({ space: 5 }) {
      Text('应急管家').fontSize(18).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')
      Text('未雨绸缪,安心生活').fontSize(12).fontColor('#FFFFFF').opacity(0.85)
    }.alignItems(HorizontalAlign.Start).layoutWeight(1)
  }
  .width('100%')
  .padding({ top: this.safeTop + 30, left: D.pad, right: D.pad, bottom: 44 })
  .linearGradient({ angle: 135, colors: [[C.primary, 0.0], [C.accent, 1.0]] })
}

这段代码中有几处极其精妙的设计:

第一,橙棕渐变的品牌统一linearGradient({ angle: 135, colors: [[C.primary, 0.0], [C.accent, 1.0]] })——从 #D35400 渐变到 #E88050,与首页准备度卡、检查页告警卡同款渐变。用户卡不是独立组件,而是品牌视觉体系的一环——用户看到橙棕渐变就联想到"应急物资",这是色彩品牌化的设计目标。

第二,半透明圆头像的层次处理backgroundColor('#33FFFFFF')——白色 20% 透明圆角 32 头像底,🎒 32 号居中。“🎒”(背包)即产品符号(与 Tab 图标一致),应急背包的意象。半透明白底在橙棕渐变上形成"同色系弱对比",头像区与卡背景融为一体又不失辨识。

第三,身份与情感副文案的组合。18 号 Bold 白字"应急管家"(角色名)+ 12 号 85% 白字"未雨绸缪,安心生活"——"价值主张式"副文案(非数据背书)。"未雨绸缪"是应急管理的核心理念(灾前准备 > 灾后应对),“安心生活"是准备带来的结果,八个字概括产品的"预防哲学”。

第四,底部大留白的悬浮预留bottom: 44 + safeTop + 30——顶部避让状态栏、底部大留白 44 给负边距统计行让位,这是系列个人中心"卡叠卡"层次感的标准手法。

4、StatRow:负边距三列统计行
StatRow() {
  Row() {
    ForEach(this.stats, (s: StatItem) => {
      Column({ space: 4 }) {
        Text(s.value).fontSize(18).fontWeight(FontWeight.Bold).fontColor(C.text)
        Text(s.label).fontSize(11).fontColor(C.textDim)
      }.layoutWeight(1)
    }, (s: StatItem) => s.label)
  }
  .width('92%').padding({ top: 14, bottom: 14 })
  .backgroundColor(C.card).borderRadius(D.rMd).border({ width: 1, color: C.stroke }).margin({ top: -24 })
}

这段代码中有几处极其精妙的设计:

第一,负边距悬浮的层次叠加.margin({ top: -24 })——统计行上移 24 覆盖在渐变卡留白区,"卡片叠卡"层次感。白底统计卡悬浮在橙棕渐变卡底部,视觉上形成"渐变卡 → 白卡"的层次过渡,是系列个人中心的经典手法。

第二,三列均分的维度设计layoutWeight(1) 三列——32(物资总数)/ 15(采购记录)/ 75%(准备度),“存量 + 行动 + 水平"三个维度(家里有多少物资、采购过几次、准备水平如何),比纯存量统计立体,用户从三列就能回答"物资够不够、维护得勤不勤、整体水平如何”。

第三,数据一致性的双展示。75% = 首页环形进度准备度——首页环形呈现、我的页统计列出,两个页面同一指标双展示。32 = 物资页全部物品的库存总和、15 = 采购次数累计,统计与物资页/检查页同源。

5、RemindCard:过期提醒开关卡
RemindCard() {
  Row() {
    Column({ space: 2 }) {
      Text('过期提醒').fontSize(14).fontColor(C.text)
      Text('物资临期时提醒补充').fontSize(11).fontColor(C.textDim)
    }.alignItems(HorizontalAlign.Start).layoutWeight(1)
    Toggle({ type: ToggleType.Switch, isOn: this.expiryRemind })
      .selectedColor(C.primary)
      .onChange((v: boolean) => { this.expiryRemind = v; })
  }
  .width('92%').padding(16).margin({ top: 14 }).backgroundColor(C.card).borderRadius(D.rMd).border({ width: 1, color: C.stroke })
}

这段代码中有几处极其精妙的设计:

第一,过期提醒开关的核心定位。左侧"过期提醒"14 号 + "物资临期时提醒补充"11 号淡灰副文案,右侧 Toggle({ type: ToggleType.Switch, isOn: this.expiryRemind })——开关卡是"物资效期提醒"的总闸。这是我的页的核心功能,不是装饰性开关。

第二,选中色主橙棕的品牌统一.selectedColor(C.primary)——开关开启时轨道变 #D35400 橙棕,与渐变卡、分类选中态同色系,品牌视觉贯穿到开关组件。

第三,默认开启的安全优先逻辑expiryRemind = true——物资过期是应急准备的大忌(过期的饮用水/药品在紧急时不能用),默认开启过期提醒符合"应急安全默认优先"的逻辑。与 App 53/55/59 的提醒开关默认开启一致——安全类应用的提醒默认开。

第四,副文案的动作导向。“物资临期时提醒补充”——提醒的动作是"补充"(临期提醒 → 去补新),与检查页"去补充"按钮呼应。开关开启后检查页的过期/临期项触发推送提醒,"检查页发现 + 开关决定推送"的配合让物资效期管理既被动可查又主动可推。

6、MenuList:四工具菜单列表
MenuList() {
  Column() {
    ForEach(this.menus, (m: MenuItem, idx: number) => {
      Row({ space: 12 }) {
        Text(m.emoji).fontSize(18)
        Text(m.title).fontSize(14).fontColor(C.text).layoutWeight(1)
        Text('>').fontSize(14).fontColor(C.textDim)
      }
      .width('100%').padding({ left: 14, right: 14, top: 14, bottom: 14 })
      .onClick(() => { promptAction.showToast({ message: m.title }); })
      if (idx < this.menus.length - 1) { Divider().color(C.stroke).strokeWidth(0.5) }
    }, (m: MenuItem) => m.title)
  }
  .width('92%').margin({ top: 14 }).backgroundColor(C.card).borderRadius(D.rMd).border({ width: 1, color: C.stroke })
}

4 个菜单项(22-27 行):

  • 🛒 采购记录 — 查看物资采购历史
  • 📦 库存设置 — 管理物资建议库存与分类
  • ⏰ 过期提醒 — 管理过期提醒规则
  • ⚙️ 设置 — 应用偏好配置

这段代码中有几处极其精妙的设计:

第一,菜单顺序的递进逻辑。采购记录(动作历史)→ 库存设置(配置)→ 过期提醒(提醒)→ 设置(偏好)——从"记录"到"配置"的递进(先看买过什么、再设建议量、再管提醒、最后偏好),符合"先了解后配置"的使用逻辑。其中"库存设置"与"过期提醒"是应急场景的核心配置,放在中间突出。

第二,0.5 细 Divider 的条件渲染idx < length - 1 判断后插入——最后一项不画线,整宽细线分隔。strokeWidth(0.5) 极细线保证视觉轻量,不抢内容焦点。

第三,菜单与统计的对应关系。🛒采购记录(统计 15 采购记录)、📦库存设置(物资页分类/建议量)、⏰过期提醒(RemindCard 的进阶)——菜单是统计与开关的进阶管理入口,点击后跳对应功能页。

7、About:底部版本信息
About() {
  Column({ space: 4 }) {
    Text('家庭应急物资').fontSize(12).fontColor(C.textDim)
    Text('Version 1.0.0').fontSize(11).fontColor(C.textDim).opacity(0.7)
  }.width('100%').margin({ top: 24 })
}

12 号淡灰"家庭应急物资" + 11 号 70% 透明"Version 1.0.0"——应用名 + 版本号(与 AppScope/app.json5 的 versionName “1.0.0” 一致),个人中心标准收尾。这里用应用全名"家庭应急物资"(首页标题用简写"应急物资"),正式信息用全名是命名的一致性与完整性平衡。

点击过期提醒菜单

8、跨页数据自洽:个人中心的全局闭环

我的页不是孤岛——它与首页、物资页、检查页形成四页数据闭环:

  • 我的页"32 物资总数" ↔ 物资页"8 种物品库存"——32 是全部物资的库存总和,物资明细 → 总量统计(产品化后动态求和)。
  • 我的页"75% 准备度" ↔ 首页"环形进度 75%"——数值完全一致,首页环形呈现、我的页统计列出,同一指标双展示。
  • 我的页"过期提醒"开关 ↔ 检查页"感冒药已过期/午餐肉临期"——开关开启后检查页的过期/临期项触发推送提醒,开关是过期检查的推送闸门
  • 我的页"15 采购记录" ↔ 检查页"去补充"按钮——每次点"去补充"完成采购后记入采购记录,去补充动作 → 采购流水
  • 菜单"库存设置" ↔ 物资页分类/数量——库存设置菜单配置"建议库存量",物资页按建议量判定充足/偏少,建议量配置 → 状态判定

这套闭环的核心是统一数据源——四页共享同一份物资数据模型,我的页是"提醒配置与统计总览"的中枢。

点击库存设置菜单

9、多维度方案选型对比:个人中心的五种风格

在实际的架构研讨中,有同学曾经发出过极其尖锐的质疑:“我的页为什么要放开关?开关不是应该放在设置页吗?”

为了彻底解答这个选型问题,我们将系列中个人中心页对比:

维度 App 60 应急管家 App 59 健康管家 App 58 保养管家 App 55 续费管家
核心开关 过期提醒 保险提醒 保险提醒 推送提醒
统计维度 物资/采购/准备度 药品/成员/健康度 里程/保养/保险 续费/支出/节省
特色菜单 采购记录/库存设置 家庭成员 呼叫救援 续费日历
主题色 橙棕 #D35400 #E74C3C #008080 #FF6B6B

60 与 55/59 的结构高度一致(卡/统计/开关/菜单/版本),差异化在**“过期提醒 + 采购"的场景**——开关管"物资临期提醒”(应急物资不能过期)、菜单含"采购记录/库存设置"(物资的采购与配置闭环),统计的"75% 准备度"是应急场景独有的指标(其他应用无"准备度"概念)。情感副文案"未雨绸缪,安心生活"传达应急场景的"预防意识",是系列中"安全预防"主题的个人中心。

点击设置菜单

10、避坑指南:个人中心的四个防翻车细节

在将这套看起来优雅的个人中心设计推向真实生产环境时,我们踩过了几个深浅不一的暗坑。以下是内部复盘后沉淀的防翻车铁律:

第一,统计值必须动态化。32/15/75% 都是写死的——产品化由数据源实时聚合(物资总数 = 全部库存求和 items.reduce((sum, it) => sum + it.count, 0)、采购记录 = 采购流水条数、准备度 = 状态计算函数),物资页加减库存后统计自动更新。如果写死,物资页改了数量但我的页统计不变,用户会以为应用坏了。

第二,过期提醒与推送必须联动expiryRemind 只改内存态——产品化需联动通知权限与推送调度(开启后注册"效期扫描",每日检查物资有效期,临期/过期项推送"XX 将于 N 天后过期"),开关状态 + 推送能力两层同步。关闭则停止扫描推送(但仍可在检查页被动查看)。

第三,准备度计算函数必须复用。75% 在首页环形进度与我的页统计两处——产品化应抽象"计算准备度"函数(按物资充足度与关键性加权),首页与我的页调用同一函数,保证两页数值一致。当前写死两处,若手动改一处另一处不同步,用户会看到"首页 75%、我的页 80%"的矛盾。

第四,采购记录的数据结构必须设计。菜单"采购记录"是占位——产品化记录每次采购(时间/物资/数量/金额),与检查页"去补充"联动(点去补充 → 生成采购记录 → 库存更新),采购记录是"库存增减的审计"。

11、常见问题 FAQ

问:统计行的"32 物资总数"是怎么算的?

答:演示数据设定全部物资库存合计 32 件(产品化后由物资页所有物品的库存数量动态求和)——“物资总数"是家庭应急物资的总量指标,让用户知道"家里备了多少件应急物资”,与检查页的"库存不足"对照(总量 vs 缺口)。

问:75% 准备度和首页的环形进度是什么关系?

答:完全同一个指标——首页用环形进度可视化呈现(白环 + 75%),我的页用统计数字列出(75% 准备度),两处数据相同、呈现不同(环 vs 数字)。产品化后由同一"准备度计算函数"生成,物资补足后两处同步上升。

问:过期提醒开关和检查页的"已过期/即将过期"是什么关系?

答:检查页的过期/临期项是"数据"(哪些物资快过期),过期提醒开关是"推送"(要不要提醒)——开关开启后,检查页的过期/临期项会触发推送提醒(“感冒药已过期,请补充”),开关关闭则只在检查页被动查看(不主动推送)。"检查页发现 + 开关决定推送"的配合,让物资效期管理既被动可查又主动可推。

问:为什么用户卡叫"应急管家"?

答:延续"角色化命名"传统——“应急管家"是"管理家庭应急事务的角色”,比"用户/我的"更有服务感;副文案"未雨绸缪,安心生活"是角色的价值主张(提前准备 = 安心),角色名 + 价值主张的组合是个人中心页的身份范式。

12、总结

纵观本篇对 60-emergency-kit 我的页(ProfileTab,115 行)的逐块拆解,@Builder 五区拆解范式绝对不是大家在走马观花浏览 ArkUI 文档时所以为的"仅仅只是把代码拆成几个函数"那么简单。

我的页用五个 @Builder 块完成了"身份 + 数据 + 能力 + 入口 + 版权"的完整呈现:UserCard 亮出身份(应急管家 + 未雨绸缪)、StatRow 展示数据(32 物资/15 采购/75% 准备度)、RemindCard 提供核心能力(过期提醒开关)、MenuList 给出工具入口(四菜单)、About 收尾版本号。整页的视觉语言围绕"应急管家"展开——橙棕渐变是品牌签名、负边距悬浮是层次手法、过期开关是核心能力、四菜单是工具直达。

从演示到产品,我的页的进化方向清晰:统计动态聚合、推送联动调度、准备度函数复用、采购记录审计。过期提醒开关是本页的核心,让应急物资的效期管理"被动可查、主动可推"。

Logo

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

更多推荐