消息通知是现代应用中最常见的交互模式之一。无论是社交应用的新消息提醒、电商平台的订单状态更新,还是协作工具的审批通知——"未读计数"这种小小红色徽标几乎无处不在。HarmonyOS NEXT 提供了 Badge 组件,专门用于在 UI 元素上叠加未读计数、状态标识等小型提示,无需手动绘制就能获得统一、美观的徽标效果。

然而,Badge 组件本身只是一个"显示层"——它解决了"怎么展示"的问题,但没有解决"怎么管理"的问题。真实的业务场景需要完整的状态管理:通知列表、已读/未读切换、分类筛选、批量操作等。本文将通过一个完整的"消息通知中心"实战案例,深入讲解 Badge 组件的用法,并在此基础上构建一套实用的通知管理系统。

关键词:HarmonyOS、ArkUI、Badge、徽标、未读计数、消息通知

一、Badge 组件基础

1.1 API 概述

Badge 是 ArkUI 提供的徽标组件,通过 .badge() 属性链式调用设置到任意组件上。它支持两种模式:

  • 数字徽标badge({ count: number }) — 显示具体未读数量
  • 文字徽标badge({ value: string }) — 显示自定义文字(如 “新”、“热门”、“99+”)
  • 位置控制badge({ position: BadgePosition }) — 控制徽标位于宿主组件的哪个方位

数字徽标在 count 超过 99 时自动显示 “99+”,这是 Badge 组件内置的行为,无需开发者手动处理。

文字徽标适合不需要具体数量的场景,比如标记一个功能为"新"、一个活动为"热门"、一个审批流为"待处理"等。

1.2 基本用法

最简单的 Badge 用法——在一个图标右上角显示未读数:

Badge({
  count: 5,
  position: BadgePosition.RightTop
}) {
  Image($r('app.media.message_icon'))
    .width(32).height(32)
}

Badge 作为容器组件包裹目标元素,徽标自动出现在元素右上角。count 为 0 时徽标自动隐藏——这个默认行为在真实业务中很实用,不需要写 if (count > 0) 的条件渲染。

1.3 Badge 的位置控制

BadgePosition 支持六种位置:

位置 说明
RightTop 右上角(默认,最常用)
Right 右侧居中
RightBottom 右下角
LeftTop 左上角
Left 左侧居中
LeftBottom 左下角

对于消息通知场景,右上角是约定俗成的位置,用户已经形成了视觉习惯。其他位置较少使用,但在特定场景下(如左侧标签栏)有意义。

1.4 Badge 的局限性

在实际项目中使用 Badge 组件时,我们遇到了几个限制:

  1. 样式不可定制:Badge 的背景色固定为红色、文字颜色固定为白色,无法通过属性修改。字号也固定,无法调整。
  2. 只能包在组件外层:Badge 必须作为容器组件使用,不能在已有的复杂布局中"插入"一个徽标。
  3. count 为 string 类型的数字:如果需要显示 “99+” 这种格式,在 count 值较小时(如 5)仍只显示 “5”,不会自动添加 “+”。

这些限制使得 Badge 适合简单场景(如底部 Tab 的未读计数),但在复杂场景(如通知列表中的可变大小徽标)中,手动用 Text + borderRadius 构建徽标反而更灵活。

因此,本文的实战案例采用混合策略:简单的 Tab 类场景用 Badge 组件,通知列表中的复杂徽标用手动构建的红色圆形 Text。

二、消息通知中心的设计

2.1 功能规划

一个实用的消息通知中心通常包含以下功能:

  1. 通知列表:展示消息的图标、标题、摘要、时间和未读状态
  2. 未读标识:未读消息显示红色徽标和高亮标题
  3. 点击已读:点击某条通知将其标记为已读
  4. 全部已读:一键将所有通知标记为已读
  5. 分类筛选:按通知类别(系统通知、消息通知、活动通知等)过滤
  6. 清除已读:删除所有已读消息,保持列表清爽
  7. 统计面板:展示未读数、总数、当前筛选数

2.2 数据结构设计

通知对象的核心字段:

interface NoticeItem {
  id: number;       // 唯一标识
  title: string;    // 通知标题
  desc: string;     // 通知摘要
  time: string;     // 发布时间(相对时间)
  icon: string;     // 图标(emoji)
  color: string;    // 图标背景色
  unread: number;   // 未读计数(0 或 N)
  category: string; // 分类:系统 / 消息 / 活动
}

unread 字段使用数字类型而非布尔值,原因是某些聚合通知可能有多个未读项(如"3 条新评论"),数字可以承载更丰富的语义。当 unread 为 0 时等同"已读",大于 0 时等同"未读"。

2.3 三类通知的设计

样本数据覆盖三个常见类别:

系统通知:系统更新、存储空间、安全提醒——这类通知通常由系统自动触发,用户无法回复,适合只读展示。

标题 描述 类别
系统更新通知 HarmonyOS NEXT 6.1.1 版本已发布 系统
存储空间提醒 云存储空间已使用 85% 系统
安全提醒 检测到新设备登录 系统

消息通知:评论、关注、团队邀请——这类是社交/协作类通知,通常可点击跳转到具体内容。

标题 描述 类别
新评论 张三评论了你的文章 消息
团队邀请 李四邀请你加入团队 消息
新关注 王五关注了你的专栏 消息

活动通知:促销活动、直播预告——这类是营销/运营类通知,时效性强,通常有明确的开始/结束时间。

标题 描述 类别
限时优惠活动 618 大促,全场低至 5 折 活动
直播预告 今晚 8 点:ArkUI 深度解析 活动

2.4 状态管理设计

整个页面的状态由三个核心变量驱动:

@State notices: NoticeItem[] = [];   // 通知列表(唯一数据源)
@State activeCat: string = '全部';    // 当前筛选分类

所有变化(已读、删除、筛选)都通过对 notices 数组进行不可变更新来实现。遵循我们之前总结的 ArkUI 状态管理原则:永远创建新数组和新对象引用,而非原地修改。
在这里插入图片描述

三、核心功能实现

3.1 分类筛选

分类筛选是通知管理的核心功能。我们用一组横向滚动的胶囊按钮实现:

private categories: string[] = ['全部', '系统', '消息', '活动'];

getFiltered(): NoticeItem[] {
  if (this.activeCat === '全部') {
    return this.notices;
  }
  let result: NoticeItem[] = [];
  for (let i = 0; i < this.notices.length; i++) {
    if (this.notices[i].category === this.activeCat) {
      result.push(this.notices[i]);
    }
  }
  return result;
}

筛选逻辑很简单:选中"全部"时返回原数组,否则遍历过滤。这个方法的返回值是一个动态计算出的数组——它不缓存、不额外存储,完全由 activeCatnotices 驱动。ArkUI 的响应式系统会在依赖变化时自动重新渲染使用 getFiltered() 的 UI。

每个分类胶囊的构建:

ForEach(this.categories, (cat: string) => {
  Row() {
    Text(cat)
      .fontSize(13)
      .fontColor(this.activeCat === cat ? '#FFFFFF' : '#666677')
      .fontWeight(this.activeCat === cat ? FontWeight.Bold : FontWeight.Normal)

    if (this.getCatUnread(cat) > 0) {
      Text(' '.concat(this.getCatUnread(cat).toString()))
        .fontSize(10)
        .fontColor(this.activeCat === cat ? '#FFFFFFAA' : '#9999AA')
    }
  }
  .padding({ top: 6, bottom: 6, left: 12, right: 12 })
  .borderRadius(14)
  .backgroundColor(this.activeCat === cat ? '#1677FF' : '#F2F3F5')
  .margin({ right: 8 })
  .onClick(() => { this.activeCat = cat; })
}, (cat: string) => cat)

每个胶囊包含两个信息维度:分类名称和该分类下的未读计数。选中状态和未选中状态有视觉上的对比——背景色(蓝 vs 灰)、文字色(白 vs 灰)、字重(粗 vs 常规)。未读计数在选中状态下使用半透明白色,未选中状态下使用灰色——视觉层次清晰:选中 > 未选中,文字 > 数字。

getCatUnread 方法计算每个分类下的未读总数:

getCatUnread(cat: string): number {
  if (cat === '全部') {
    return this.getTotalUnread();
  }
  let sum = 0;
  for (let i = 0; i < this.notices.length; i++) {
    if (this.notices[i].category === cat) {
      sum += this.notices[i].unread;
    }
  }
  return sum;
}

对于"全部"分类,返回全局未读总数;对于具体分类,遍历累加该分类下所有通知的 unread 值。

3.2 标记已读

点击通知将其标记为已读。这里的关键仍然是不可变更新:

markRead(id: number): void {
  let copy = this.notices.slice();
  for (let i = 0; i < copy.length; i++) {
    if (copy[i].id === id && copy[i].unread > 0) {
      copy[i] = {
        id: copy[i].id, title: copy[i].title, desc: copy[i].desc,
        time: copy[i].time, icon: copy[i].icon, color: copy[i].color,
        unread: 0, category: copy[i].category
      };
      break;
    }
  }
  this.notices = copy;
}

三步走:

  1. slice() 创建新数组引用
  2. 找到目标通知,展开所有字段创建新对象,将 unread 设为 0
  3. 将新数组赋值给 this.notices,触发 @State 更新

注意解构展开的写法:由于 ArkTS 不支持对象展开运算符 { ...obj, unread: 0 },需要逐个字段手动列出。这确实有些啰嗦,如果在有很多字段的场景下,可以写一个 copyNotice() 工厂函数来复用。

3.3 全部已读

一键标记所有通知为已读:

markAllRead(): void {
  let copy = this.notices.slice();
  let changed = false;
  for (let i = 0; i < copy.length; i++) {
    if (copy[i].unread > 0) {
      copy[i] = {
        id: copy[i].id, title: copy[i].title, desc: copy[i].desc,
        time: copy[i].time, icon: copy[i].icon, color: copy[i].color,
        unread: 0, category: copy[i].category
      };
      changed = true;
    }
  }
  if (changed) {
    this.notices = copy;
  }
}

markRead 的区别在于遍历所有通知而非找到一个就 break。同时引入了一个 changed 标记——如果所有通知原本都已读,就不触发不必要的状态更新。这是一个小优化,但在频繁操作场景下(如用户反复点击"全部已读")可以避免无意义的重渲染。

"全部已读"按钮使用半透明样式,置于标题栏右侧:

Text('全部已读')
  .fontSize(13)
  .fontColor('#FFFFFFCC')
  .padding({ top: 5, bottom: 5, left: 12, right: 12 })
  .borderRadius(12)
  .backgroundColor('#FFFFFF20')
  .onClick(() => { this.markAllRead(); })

标题栏使用深色背景(#1a1a2e),"全部已读"按钮使用白色半透明底色(#FFFFFF20)和半透明白色文字——保持标题栏统一的视觉风格,同时有足够的可点击区分度。

3.4 清除已读

只保留未读通知,删除所有已读项:

deleteRead(): void {
  let result: NoticeItem[] = [];
  for (let i = 0; i < this.notices.length; i++) {
    if (this.notices[i].unread > 0) {
      result.push(this.notices[i]);
    }
  }
  this.notices = result;
}

这个操作从筛选栏右侧触发,显示为红色文字"清已读"——只在当前筛选结果有通知时才显示:

if (this.getFiltered().length > 0) {
  Text('清已读')
    .fontSize(11)
    .fontColor('#FF4D4F')
    .padding({ top: 5, bottom: 5, left: 10, right: Spacing.LG })
    .onClick(() => { this.deleteRead(); })
}

注意 deleteRead 删除的是全部已读通知,而非仅当前分类筛选下的已读通知。这是因为:

  1. “清除已读"是一个全局清理操作,用户的心理模型是"清掉所有我看过的”
  2. 如果只删除当前分类的已读,用户切换到其他分类后会疑惑"为什么已读的还在"

如果产品需求确实是"只清当前分类的已读",可以在 deleteRead 中加入分类筛选逻辑。但从用户体验角度,全局清除更直观。

3.5 统计面板

通知列表上方展示三个统计数字:

Row() {
  Column() {
    Text(this.getTotalUnread().toString())
      .fontSize(28)
      .fontColor('#1677FF')
      .fontWeight(FontWeight.Bold)
    Text('未读消息').fontSize(11).fontColor('#9999AA').margin({ top: 2 })
  }
  .alignItems(HorizontalAlign.Center).layoutWeight(1)

  Column() {
    Text(this.notices.length.toString())
      .fontSize(28)
      .fontColor('#1a1a2e')
      .fontWeight(FontWeight.Bold)
    Text('全部消息').fontSize(11).fontColor('#9999AA').margin({ top: 2 })
  }
  .alignItems(HorizontalAlign.Center).layoutWeight(1)

  Column() {
    Text(this.getFiltered().length.toString())
      .fontSize(28)
      .fontColor('#00B894')
      .fontWeight(FontWeight.Bold)
    Text('当前筛选').fontSize(11).fontColor('#9999AA').margin({ top: 2 })
  }
  .alignItems(HorizontalAlign.Center).layoutWeight(1)
}

三列布局,每列 layoutWeight(1) 等分。三列使用不同的颜色——蓝色(未读数)、深色(总数)、绿色(筛选数)——颜色本身就是信息编码,帮助用户快速区分三个指标的含义。

为什么是这三项?它们回答了用户可能关心的三个问题:

  • 还有多少没看?(未读消息)→ 决定"是否需要立即处理"
  • 一共有多少?(全部消息)→ 了解"整体信息量"
  • 当前看到了多少?(当前筛选)→ 了解"筛选范围"

3.6 通知列表

通知列表是页面最重要的部分,每条通知是一个水平布局的行:

ForEach(this.getFiltered(), (item: NoticeItem) => {
  Row() {
    // 图标
    Row() {
      Text(item.icon).fontSize(22)
    }
    .width(44).height(44)
    .justifyContent(FlexAlign.Center)
    .borderRadius(12)
    .backgroundColor(item.color.concat('15'))

    // 内容
    Column() {
      Text(item.title)
        .fontSize(14)
        .fontColor('#1a1a2e')
        .fontWeight(item.unread > 0 ? FontWeight.Bold : FontWeight.Normal)
        .maxLines(1)
        .textOverflow({ overflow: TextOverflow.Ellipsis })
      Text(item.desc)
        .fontSize(12)
        .fontColor('#9999AA')
        .maxLines(1)
        .textOverflow({ overflow: TextOverflow.Ellipsis })
      Text(item.time)
        .fontSize(11)
        .fontColor('#BBBBCC')
    }
    .layoutWeight(1).margin({ left: 12 })

    // 徽标
    if (item.unread > 0) {
      Text(item.unread.toString())
        .fontSize(11).fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
        .padding({ top: 3, bottom: 3, left: 7, right: 7 })
        .borderRadius(10)
        .backgroundColor('#FF4D4F')
    }
  }
  .width('100%').padding({ top: 14, bottom: 14 })
  .borderRadius(10)
  .onClick(() => { this.markRead(item.id); })
}, (item: NoticeItem) => item.id.toString())

通知行的四个关键设计:

图标区:44x44 的圆角方块,背景色为各通知主色的 15% 透明度(item.color.concat('15'))。这个透明度值经过试验——太浅看不出颜色差异(< 10%),太深则 emoji 白色内容对比度不足(> 25%),15% 是一个合理的平衡点。

内容区:标题 + 描述 + 时间,三层纵向排列。标题在未读状态使用 Bold 字重,已读后变为 Normal——这种微小的视觉变化足够区分已读/未读而不喧宾夺主。一律使用 maxLines(1) + textOverflow 防止长文本撑破布局。

徽标区:手动构建的红色圆形 Text。为什么不用 Badge 组件?如 1.4 节所述,在复杂列表的每个行内部精确放置徽标,手动构建比 Badge 组件更灵活——可以精确控制大小、位置、padding 和内边距。这里使用的是固定样式:#FF4D4F 红色背景、白色粗体数字、圆角 10。

点击行为:整行可点击,点击后调用 markRead,徽标消失、标题由粗变细——用户获得即时的视觉反馈。

3.7 空状态处理

当筛选结果为空时,显示友好的空状态提示:

if (this.getFiltered().length === 0) {
  Text('暂无消息')
    .fontSize(14)
    .fontColor('#BBBBCC')
    .width('100%')
    .textAlign(TextAlign.Center)
    .padding({ top: 24, bottom: 24 })
}

空状态是容易被忽略但至关重要的 UX 细节。如果没有空状态处理,筛选后列表消失会让用户困惑"是 bug 了还是确实没数据?"。一个简单的"暂无消息"文字消除了这种困惑。

在实际产品中,空状态可以更丰富——配合插图、引导按钮等。但对于 Demo 级别来说,文字提示已足够。

3.8 关于 ArkUI Badge 组件的实际使用

回到 Badge 组件本身。虽然本文的通知列表徽标使用了手动构建,但 Badge 组件在更简单的场景下非常合适。例如,在底部 Tab 导航栏中给"消息"Tab 加未读徽标:

Badge({
  count: this.getTotalUnread(),
  position: BadgePosition.RightTop
}) {
  Column() {
    Image($r('sys.symbol.message'))
      .width(24).height(24)
    Text('消息')
      .fontSize(10)
  }
}

这个用法的优势是:一行代码搞定,不需要手动计算位置和样式。Badge 会自动处理:

  • count 为 0 时隐藏
  • count > 99 时显示 “99+”
  • 精确的右上角定位

在以下场景中,使用 Badge 组件优于手动构建:

  1. 图标或按钮角标(如购物车、消息 Tab)
  2. 列表项的状态标记(如审批流的"待处理")
  3. 需要统一风格的全局徽标系统

而在以下场景中,手动构建更好:

  1. 需要自定义徽标样式的(如绿色徽标表示"安全"、橙色表示"警告")
  2. 徽标需要动画效果的
  3. 徽标位置不固定的(如内联在文字中的徽标)
    在这里插入图片描述

四、交互流程

整个通知中心的操作流程如下:

4.1 初始状态

页面加载后,8 条通知全部展示。统计面板显示:未读消息 = 13(所有通知的 unread 之和),全部消息 = 8,当前筛选 = 8。分类筛选栏的"全部"胶囊高亮,旁边显示 “13”。

每条未读通知的右侧显示红色数字徽标,标题使用粗体。

4.2 点击某条通知

点击"新评论"通知,该条通知的 unread 变为 0,徽标消失,标题变为常规字重。统计面板的"未读消息"从 13 变为 10(该通知原本有 3 条未读),"全部消息"和"当前筛选"保持不变。

同时,"消息"分类胶囊的数字从原来的值减去 3,因为它所属的分类未读总数减少了。

4.3 切换分类

点击"系统"胶囊,列表只显示 3 条系统类通知。统计面板变为:未读消息 = 13(全局未读仍然不变)、全部消息 = 8(总数不变)、当前筛选 = 3。

此时如果点击"清已读",会删除全部已读消息(包括其他分类的已读项)。

4.4 全部已读

点击标题栏的"全部已读",所有通知的 unread 置为 0。所有徽标消失,所有标题变为常规字重。统计面板"未读消息"变为 0,分类胶囊全部归零。

4.5 清除已读

假设此时有 3 条已读消息和 5 条未读消息,点击"清已读"后,列表只保留 5 条未读消息。统计面板更新:全部消息 = 5,未读消息 = 对应值,当前筛选 = 对应值。

4.6 空状态

如果清除到某分类下没有任何通知,切换过去后显示"暂无消息"。

五、关键设计决策

5.1 为什么不用 Badge 组件做通知列表徽标

这是本文最值得讨论的设计决策。ArkUI 提供了 Badge 组件,理论上我们应该用它来展示通知列表中的未读计数。但在实际开发中,我们发现手动构建更合适,原因如下:

位置控制的精度:Badge 组件通过 BadgePosition 枚举控制位置,但这类位置是相对于宿主组件外层的。在一个复杂的列表行布局中,我们需要徽标准确出现在某个位置(行尾、垂直居中),Badge 组件的定位逻辑不如手动放置直观。

样式自定义:Badge 的样式(颜色、大小、圆角、字重)完全固定。真实产品中,徽标的样式可能需要根据上下文变化——比如:

  • 系统通知用蓝色徽标(而非统一的红色)
  • 紧急通知用脉冲动画的徽标
  • 低优先级通知用小号灰色徽标

这些定制需求超出了 Badge 组件的能力范围。

布局灵活性:在通知行中,徽标只是三个水平元素之一(图标 → 内容 → 徽标),与图标和内容平级排列。Badge 组件的容器模型(包裹宿主组件)在这种场景下显得"用力过猛"——我们需要的是一个普通的 Text,不是包裹整个行的 Badge。

5.2 为什么 unread 是数字而非布尔

如果只关心"已读/未读"两种状态,布尔值似乎更合适。但数字类型有几个优势:

  1. 聚合通知:一条通知可能对应多条底层消息(如"5 条新评论")。数字直接体现在 UI 上。
  2. 渐进已读:可以逐步减少 unread(如"标记部分已读"),而非二值切换。
  3. 统计友好getTotalUnread() 只需要 sum,不需要先转数字。

在当前的 Demo 中,unread 的实际使用方式是 “0 或 N”,等同于布尔值。但数据结构为未来扩展留了余地。

5.3 为什么筛选不缓存结果

getFiltered() 每次调用都重新计算,没有缓存。这种设计的好处是:

  1. 代码简单:不需要额外维护一个 filteredNotices 状态变量。
  2. 不会过期:缓存需要手动失效,每次计算的方案天然保证数据一致性。
  3. 性能足够:对于 8 条通知的列表,遍历筛选的开销可忽略不计。

如果通知数量增长到数百条,可以考虑引入缓存。但对于消息通知这种通常分页加载的数据(每页 20 条左右),每次计算完全没有性能问题。

六、与其他组件的组合使用

6.1 Badge + Tabs

Tab 切换是 Badge 最常见的搭档。配合 Tabs 组件,可以实现带未读计数的标签页:

Tabs() {
  TabContent() { /* 消息列表 */ }
    .tabBar(this.tabWithBadge('消息', this.msgUnread))
  TabContent() { /* 通知列表 */ }
    .tabBar(this.tabWithBadge('通知', this.noticeUnread))
  TabContent() { /* 我的 */ }
    .tabBar(this.tabWithBadge('我的', 0))
}

@Builder
tabWithBadge(label: string, count: number) {
  Badge({ count: count, position: BadgePosition.RightTop }) {
    Text(label).fontSize(14)
  }
}

6.2 Badge + Navigation

在侧边导航或底部导航中,Badge 用于提醒用户特定入口有新内容:

Navigation() {
  Column() {
    // 侧边菜单项
    ForEach(this.menuItems, (item: MenuItem) => {
      Badge({ count: item.unreadCount }) {
        Row() {
          Image(item.icon).width(24).height(24)
          Text(item.title)
        }
      }
    })
  }
}

6.3 手动徽标 + 列表

如本文实现,手动构建的徽标更适合复杂列表场景。结合 SwipeAction 可以实现"滑动标记已读":

ListItem() {
  // 通知行(含手动徽标)
}
.swipeAction({
  end: {
    builder: () => {
      Row() {
        Image($r('sys.symbol.message')).width(20).height(20)
        Text('已读').fontSize(12).fontColor('#FFFFFF')
      }
      .backgroundColor('#1677FF')
      .onClick(() => { this.markRead(item.id); })
    }
  }
})

七、进阶扩展方向

7.1 通知推送接入

当前 Demo 的通知数据是硬编码的。在实际应用中,通知数据来自服务端。可以通过 WebSocket 或 Push Kit 实时接收新通知,插入到 notices 数组头部:

onNewNotice(notice: NoticeItem): void {
  let copy = this.notices.slice();
  copy.unshift(notice);  // 新通知放在最前面
  this.notices = copy;
}

7.2 通知点击跳转

不同类别的通知点击后应跳转到不同的页面:

  • 评论通知 → 跳转到文章详情页
  • 团队邀请 → 跳转到团队申请页
  • 系统更新 → 跳转到设置页

可以在 NoticeItem 中增加 linkTypelinkId 字段,在 onClick 中根据类型调用不同的路由。

7.3 通知持久化

使用 PersistentStorage 或关系型数据库(RelationalStore)存储通知数据。在 aboutToAppear 中从本地加载,在 markRead / deleteRead 后同步写回。

7.4 通知分组

同类通知聚合显示,类似微信的"群聊通知"折叠。例如:

[系统] 3条新通知
  ├─ 系统更新通知
  ├─ 存储空间提醒
  └─ 安全提醒

点击分组头部展开/折叠。这需要在 NoticeItem 外层增加一个 GroupItem 层级结构。

7.5 通知音效与振动

在收到新通知时播放系统提示音或触发振动反馈:

import { vibrator } from '@kit.SensorServiceKit';

onNewNotice(notice: NoticeItem): void {
  // 更新数据
  // ...
  // 触发振动反馈
  vibrator.startVibration({
    type: 'time',
    duration: 100
  });
}

八、总结

本文通过"消息通知中心"这个实战案例,全面讲解了 Badge 徽标组件及其配套的通知管理功能。核心知识点包括:

  1. Badge 组件基础:数字徽标(count)和文字徽标(value)、六种位置控制、默认的 99+ 格式化行为
  2. Badge 的适用场景与局限:适合简单角标场景,复杂列表徽标推荐手动构建
  3. 通知数据结构设计:NoticeItem 接口、unread 数字类型的设计考量
  4. 分类筛选系统:动态计算 + 胶囊式 UI + 每分类未读计数
  5. 已读状态管理:arkRead / markAllRead / deleteRead 的不可变更新实现
  6. 统计面板:三列指标(未读 / 全部 / 筛选)的颜色语义编码
  7. 混合徽标策略:Badge 组件用于系统级角标,手动构建用于列表内徽标
  8. ArkUI 状态管理实践:slice() + 新对象引用的不可变更新模式

在实际项目中,Badge 组件和手动构建的徽标是互补而非替代关系。选择标准很简单:如果需要标准风格的角标(红点/数字),用 Badge;如果需要自定义样式或复杂布局中的徽标,手动构建。理解两者的优劣,才能在不同场景下做出正确的技术决策。

消息通知系统的复杂度不在于"展示"(那只是几个红色数字),而在于"管理"——已读/未读的状态流转、分类筛选的交互逻辑、列表的增删操作。希望本文的实战案例能为你的下一个通知系统提供参考。


Logo

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

更多推荐