鸿蒙原生应用 ArkUI 实战:渐变用户卡 + 负边距统计 + 推送开关 —— 续费提醒我的页的通知中枢架构

App 55「自动续费提醒」我的页(ProfileTab),主题色 #FF6B6B 珊瑚红,4 个 Tab 分别为首页(⏰)、添加(➕)、日历(�)、我的(👤)。本篇基于 55-renewal-reminder/entry/src/main/ets/pages/ProfileTab.ets(共 114 行)逐段拆解,附 4 张实机截图。

我的页整体布局

一、整体结构:五区"用户卡 + 统计 + 开关 + 菜单 + 版本"布局

我的页是"个人中心 + 通知设置 + 工具入口"的五区布局——5 个 @Builder 块:

build() {
  Column() {
    Scroll() {
      Column() {
        this.UserCard()
        this.StatRow()
        this.PushCard()
        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)
}

5 块结构:UserCard(红色渐变用户卡)→ StatRow(负边距统计行)→ PushCard(推送通知开关)→ MenuList(四菜单)→ About(版本号)。与 App 53 我的页结构完全同构(卡/统计/开关/菜单/版本),但开关语义不同——53 是"过保提醒"、55 是"推送通知"。提醒类应用的我的页,开关卡是标配。

二、数据模型 + 状态声明

interface StatItem { value: string; label: string; }
interface MenuItem { id: number; emoji: string; title: string; }
@State pushOn: boolean = true;

private stats: StatItem[] = [
  { value: '5', label: '提醒项目' },
  { value: '¥746', label: '年度支出' },
  { value: '12', label: '历史记录' }
];

private menus: MenuItem[] = [
  { id: 1, emoji: '📋', title: '提醒规则' },
  { id: 2, emoji: '🕐', title: '历史记录' },
  { id: 3, emoji: '🏷️', title: '分类管理' },
  { id: 4, emoji: '⚙️', title: '设置' }
];

@State pushOn 默认 true(推送默认开启——提醒类应用的核心能力就是推送,默认开)。3 项统计(5 提醒项目 / ¥746 年度支出 / 12 历史记录)+ 4 项菜单(提醒规则/历史记录/分类管理/设置)。5 与 ¥746 与日历页完全一致——同一批数据的全局复用。

三、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('管理 5 个续费提醒').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]] })
}

逐点拆解:

  1. 红色渐变背景:linearGradient 从 #FF6B6B 渐变到 #FFB0B0——与首页告警卡同款渐变,品牌视觉统一。
  2. 64×64 半透明圆头像:backgroundColor('#33FFFFFF') 白色 20% 透明 + ⏰ 32 号居中——⏰ 是产品符号(与首页告警卡图标、Tab 图标一致),提醒 = 闹钟的符号联想。
  3. 身份 + 数据背书:18 号 Bold"续费管家" + 12 号 85% 透明"管理 5 个续费提醒"——5 项与日历页"5 续费项目"一致。
  4. 底部留白 44:bottom: 44 给负边距统计行让位——系列同款叠卡手法(App 52/53 均用)。

四、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 })
}

逐点拆解:

  1. 负边距悬浮:.margin({ top: -24 })——统计行上移 24 覆盖在渐变卡留白区,"卡片叠卡"层次感(系列经典手法)。
  2. 三列均分:5 提醒项目 / ¥746 年度支出 / 12 历史记录——18 号 Bold 数值 + 11 号淡灰标签。
  3. 数据一致性:5 = 日历页"5 续费项目"、¥746 = 日历页"¥746 年度总额"——三页同源(首页近期 4 项是 5 项的子集)。

五、PushCard:推送通知开关卡

PushCard() {
  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.pushOn })
      .selectedColor(C.primary)
      .onChange((v: boolean) => { this.pushOn = v; })
  }
  .width('92%').padding(16).margin({ top: 14 }).backgroundColor(C.card).borderRadius(D.rMd).border({ width: 1, color: C.stroke })
}

逐点拆解:

  1. 开关卡结构:左侧"推送通知"14 号 + "扣款前推送提醒"11 号淡灰副文案,右侧 Switch——与 App 53 我的页开关卡完全同构(文案 + layoutWeight(1) + Toggle)。
  2. 选中色主题红:.selectedColor(C.primary)——开关开启时轨道变珊瑚红,与 App 53 的绿色开关、App 54 的紫色开关形成"开关颜色 = 主题色"的系列约定。
  3. 默认开启:pushOn = true——提醒类应用的推送是核心价值,默认开(用户装这个 App 就是为了被提醒)。
  4. 双向绑定:isOn: this.pushOn + onChange 回写——@State 内存态,产品化需 Preferences 持久化 + 通知权限联动。

六、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 })
}

逐点拆解:

  1. 标准列表行:18 号 emoji + 14 号标题 layoutWeight(1) + 14 号 > 箭头——系列同款范式。
  2. 条件 Divider:idx < length - 1 时才画 0.5 细线——最后一项不画线,列表底部干净。
  3. 菜单语义:📋 提醒规则(编辑提前天数/周期)、🕐 历史记录(12 条操作流水)、🏷️ 分类管理(订阅分类维护)、⚙️ 设置——4 项覆盖"规则/流水/分类/配置"四类管理职能。

点击提醒规则菜单

七、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% 透明版本号——系列标准收尾(App 52/53 同款 About 区)。

点击分类管理菜单

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

    { value: '5', label: '提醒项目' },
    { value: '¥746', label: '年度支出' },
        Text('¥746').fontSize(22).fontWeight(FontWeight.Bold).fontColor(C.primary)
        ...
        Text('5').fontSize(22).fontWeight(FontWeight.Bold).fontColor(C.accent)
  • 我的页"5 提醒项目 / ¥746 年度支出" ↔ 日历页"5 续费项目 / ¥746 年度总额"——数字完全一致,统计与日历展示同一批数据。
  • 我的页"推送通知"开关 ↔ 添加页"提前提醒 1/3/7 天"——开关是"要不要推"的总闸,添加页的天数是"提前多久推"的参数,开关 × 天数共同决定推送行为。
  • 菜单"提醒规则" ↔ 添加页 RemindCard——规则菜单是提醒设置的进阶管理页,添加页的胶囊是录入时的单次选择。
  • 菜单"历史记录 12 条" ↔ 用户操作流水——添加提醒、开关切换、查看日历各记一笔。

点击设置菜单

九、系列横向对比:开关型我的页双生花

我的页代表 App开关语义默认状态主题色
推送开关App 55 续费扣款前推送提醒开(提醒是核心价值)#FF6B6B 珊瑚红
提醒开关App 53 保修过保前 30 天提醒开#00A862 生态绿

差异点:App 53 与 55 的我的页结构完全同构(渐变卡 + 负边距统计 + 开关卡 + 四菜单 + 版本),但开关的语义层级不同——53 的开关管"要不要提醒"(业务规则),55 的开关管"要不要推送"(系统通道)。55 的开关更底层:关掉推送 = 整个 App 失声;53 关掉提醒只是不再主动提示,数据还在。同构骨架承载不同权重的开关,是系列"骨架统一、语义各异"方法论的体现。

十、开发者视角:调试与验证技巧

  1. Toggle 持久化:pushOn 是 @State 内存态——重启复位,产品化需 Preferences 存储;且推送开关必须与系统通知权限状态同步(系统关权限时 App 内开关应置灰并引导跳设置)。
  2. 负边距联动:统计行 -24 与用户卡 bottom: 44——负边距值必须小于留白值,改任一值需同步验证叠卡效果(与 App 53 同款约束)。
  3. 92% 宽度体系:StatRow/PushCard/MenuList 均 width('92%')——三卡同宽居中悬浮,与用户卡全宽形成"全宽底卡 + 悬浮子卡"的层次;新增区块需沿用 92% 保持对齐。
  4. 菜单与统计的呼应:统计行"历史记录 12"与菜单"🕐 历史记录"同义重复——统计给数量、菜单给入口,产品化时点击统计数字也可跳对应页,双入口提升可达性。

十一、演进方向

方向当前状态产品化演进
开关持久化@State 内存态Preferences 存储 + 重启恢复
权限联动无系统通知权限检测 + 未授权引导
统计点击无统计数字跳对应页(提醒项目→列表)
提醒规则页Toast 占位全局默认提醒天数 + 单项覆盖规则
推送通道无对接系统推送服务,定时任务检查扣款日

十二、一句话总结

我的页以"红色渐变用户卡 + 负边距统计 + 推送开关 + 四菜单"完成了续费提醒的个人中心。linearGradient 珊瑚红渐变用户卡(⏰ 符号 + 5 项背书)、margin({ top: -24 }) 悬浮统计行(5/¥746/12 与日历页同源)、Toggle 开关 selectedColor(C.primary) 红色选中态(默认开 + 推送语义)、0.5 细 Divider 四菜单——114 行代码,一个"亮身份 → 看总账 → 管推送 → 进工具"的个人中心页,也是"提醒类应用开关卡"的标准范式。

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

Logo

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

更多推荐