鸿蒙新特性——Badge 角标组件详解
一、引言
在移动端 UI 中,角标(Badge)是最常见的信息提示元素之一。无论是微信的消息红点、淘宝的购物车数量、抖音的未读通知,还是 App 图标上的更新提示——角标以最小的视觉空间传递最关键的状态信息。一个红色圆点或白色数字,就能让用户在 0.1 秒内感知到"有新内容需要关注"。
在传统开发中,实现角标需要手动叠加组件:用 Stack 容器将角标视图定位到目标组件的右上角,手动计算偏移量,手动处理数字格式化(99+),手动管理显示/隐藏逻辑。看似简单的角标,实际写起来却是一堆布局代码和条件判断。
HarmonyOS NEXT 提供了 Badge 组件——一个专为角标场景设计的声明式组件。开发者只需指定角标类型(数字/圆点/文本)、样式和位置,Badge 会自动完成定位、格式化和管理。本文通过一个消息通知中心 Demo 深入讲解 Badge 的核心用法。
阅读完本文,你将能够:
- 使用 Badge 实现数字角标、圆点角标和文本角标
- 掌握
BadgePosition的位置控制 - 理解
badgeColor、badgeSize、fontSize等样式参数 - 实现未读消息计数和全部已读功能
- 理解 Badge 组件在消息系统中的实际应用模式
二、Badge 组件 API 总览
2.1 构造函数
Badge(options: BadgeOptions)
Badge 通过构造函数接收配置对象,所有参数都通过 BadgeOptions 传入:
interface BadgeOptions {
value: string; // 角标显示内容,空字符串表示圆点模式
style?: BadgeStyle; // 角标样式配置
position?: BadgePosition; // 角标位置
}
2.2 BadgeStyle 样式配置
interface BadgeStyle {
badgeColor?: ResourceColor; // 角标背景颜色
badgeSize?: number; // 角标大小(圆点模式下的直径)
fontSize?: number; // 数字/文本模式下字体大小
fontColor?: ResourceColor; // 角标文字颜色
fontWeight?: number | FontWeight; // 字重
}
关键参数说明:
| 参数 | 用途 | 默认值 | 说明 |
|---|---|---|---|
badgeColor |
角标背景色 | 红色 | 数字和圆点角标的背景填充色 |
badgeSize |
圆点直径 | 6vp | 仅在 value: ''(圆点模式)时生效 |
fontSize |
文字大小 | 10vp | 影响数字角标的数字大小 |
fontColor |
文字颜色 | 白色 | 角标文字的颜色 |
2.3 BadgePosition 位置枚举
enum BadgePosition {
RightTop, // 右上角(默认)
Right, // 右侧居中
Left, // 左侧居中
LeftTop, // 左上角
LeftBottom, // 左下角
RightBottom // 右下角
}
最常用的是 BadgePosition.RightTop——大多数应用的消息角标放在头像右上角。
2.4 三种角标模式
根据 value 参数的不同,Badge 有三种显示模式:
// 数字角标 — value 为非空字符串
Badge({ value: '99+', style: { badgeColor: '#FF3B30', fontSize: 10 }, position: BadgePosition.RightTop })
// 圆点角标 — value 为空字符串
Badge({ value: '', style: { badgeColor: '#FF3B30', badgeSize: 8 }, position: BadgePosition.RightTop })
// 文本角标 — value 为非数字字符串
Badge({ value: 'NEW', style: { badgeColor: '#9C27B0', fontSize: 9 }, position: BadgePosition.RightTop })
三种模式的区分逻辑:
value为空字符串 → 圆点角标,显示为指定badgeSize的圆形色块value为非空字符串 → 内容角标,显示指定文字,背景为badgeColor色块- 数字角标是内容角标的特例——
value为数字字符串,通常配合fontSize控制大小
三、Demo 设计:消息通知中心
3.1 功能概述
Demo 是一个消息通知中心页面,模拟类微信消息列表的交互体验:
- 角标类型展示区:3 个演示卡片,分别展示数字角标、圆点角标和文本角标的效果
- 消息列表区:8 条模拟消息,每条消息根据状态显示不同类型的角标
- 未读统计:顶部实时显示未读消息总数
- 单条已读:点击消息将该条消息标记为已读
- 全部已读:一键清空所有未读状态
3.2 消息数据结构
每条消息用一个接口定义,包含标题、内容、时间、未读状态和角标信息:
interface MessageItem {
id: number;
title: string;
content: string;
time: string;
unread: boolean;
count: number; // 未读数(>0 时显示数字角标)
dot: boolean; // 是否显示圆点角标(未读但无具体数量时)
tag: string; // 消息分类标签
}
角标显示逻辑:
- 消息未读 + count > 0 → 显示数字角标(如"3"、“5”)
- 消息未读 + dot = true → 显示圆点角标(适合"有人关注你"这类无法计数的消息)
- 消息已读 → 不显示任何角标
这种设计覆盖了消息系统中两种典型的未读场景:可计数的(如"3 条回复")和不可计数的(如"有人关注了你")。
3.3 角标类型演示
页面顶部有 3 个演示卡片,用 Stack + Badge 组合展示三种角标:
@Builder
badgeDemo(label: string, col: string, mode: string) {
Column() {
Stack() {
// 56×56 的占位方块
Column()
.width(56).height(56)
.borderRadius(BorderRadius.MD)
.backgroundColor('#F2F3F5')
if (mode === 'count') {
Badge({ value: '99+', style: { badgeColor: col, fontSize: 10 }, position: BadgePosition.RightTop })
.width(56).height(56)
} else if (mode === 'dot') {
Badge({ value: '', style: { badgeColor: col, badgeSize: 8 }, position: BadgePosition.RightTop })
.width(56).height(56)
} else {
Badge({ value: 'NEW', style: { badgeColor: col, fontSize: 9 }, position: BadgePosition.RightTop })
.width(56).height(56)
}
}
Text(label)
.fontSize(11).fontColor('#9999AA')
.margin({ top: 6 })
}
.alignItems(HorizontalAlign.Center)
.width('30%')
}
关键点:Badge 必须放在 Stack 容器中才能正确定位。Badge 在 Stack 内部通过 position 参数确定相对于 Stack 的位置。Badge 的 width/height 应设置为与下方组件相同的尺寸,以确保定位基准正确。
3.4 消息列表中的条件角标
在消息列表中,每条消息的头像区域使用条件渲染来决定显示哪种角标:
Stack() {
// 头像占位区域
Column()
.width(44).height(44)
.borderRadius(BorderRadius.FULL)
.backgroundColor(this.tagColor(item.tag) + '20')
Text(item.tag)
.fontSize(11)
.fontColor(this.tagColor(item.tag))
.fontWeight(FontWeight.Bold)
// 条件角标渲染
if (item.unread && item.count > 0) {
Badge({ value: '' + item.count, style: { badgeColor: '#FF3B30', fontSize: 10 }, position: BadgePosition.RightTop })
.width(44).height(44)
} else if (item.unread && item.dot) {
Badge({ value: '', style: { badgeColor: '#FF3B30', badgeSize: 8 }, position: BadgePosition.RightTop })
.width(44).height(44)
}
}
注意细节:
'' + item.count— 将 number 转为 string,Badge 的 value 只接受 string 类型- 条件判断链
if/else if— 数字角标优先于圆点角标,count > 0 时不显示圆点 - Badge 的宽高设置为 44×44 与头像一致,让角标定位在头像区域的右上角
3.5 标记已读的不可变更新
单条标记已读使用不可变数组更新模式:
markAsRead(id: number): void {
const newList: MessageItem[] = [];
for (let i = 0; i < this.messages.length; i++) {
const m = this.messages[i];
if (m.id === id) {
newList.push({
id: m.id, title: m.title, content: m.content,
time: m.time, unread: false, count: 0,
dot: false, tag: m.tag
});
} else {
newList.push(m);
}
}
this.messages = newList;
this.countTotal();
}
在 ArkTS 中,@State 变量的更新检测基于引用比较。直接修改 this.messages[i].unread = false 不会触发 UI 刷新。必须创建新数组(包含修改后的元素副本),再将新数组赋值给 this.messages。
countTotal() 在每次标记已读后重新统计未读数——遍历所有消息,将未读消息的 count(或至少 1 条)累加到 unreadTotal。
3.6 全部已读功能
markAllRead(): void {
const newList: MessageItem[] = [];
for (let i = 0; i < this.messages.length; i++) {
const m = this.messages[i];
newList.push({
id: m.id, title: m.title, content: m.content,
time: m.time, unread: false, count: 0,
dot: false, tag: m.tag
});
}
this.messages = newList;
this.unreadTotal = 0;
}
全部已读比单条已读更简单——遍历所有消息,创建 unread/count/dot 全部置为 false/0 的副本,赋值触发刷新。由于已知结果为 0,所以直接赋 this.unreadTotal = 0 而无需调用 countTotal()。
3.7 页面结构
┌──────────────────────────────────────────┐
│ 📬 消息中心(深色标题栏) │
├──────────────────────────────────────────┤
│ 📘 Badge 组件说明卡片 │
├──────────────────────────────────────────┤
│ 未读消息 N 条 全部已读 │
├──────────────────────────────────────────┤
│ ┌────────────────────────────────────┐ │
│ │ 角标类型 │ │
│ │ ┌────────┐ ┌────────┐ ┌────────┐ │ │
│ │ │ 99+ │ │ ● │ │ NEW │ │ │
│ │ │ 数字 │ │ 圆点 │ │ 文本 │ │ │
│ │ └────────┘ └────────┘ └────────┘ │ │
│ └────────────────────────────────────┘ │
├──────────────────────────────────────────┤
│ ┌────────────────────────────────────┐ │
│ │ [系统] 系统通知 10:32 │ │
│ │ 您的账号在新设备上登录... ③ │ │
│ ├────────────────────────────────────┤ │
│ │ [互动] 评论回复 09:15 │ │
│ │ 张三回复了你的文章... ⑤ │ │
│ ├────────────────────────────────────┤ │
│ │ [互动] 点赞提醒 昨天 │ │
│ │ 你的评论获得了128个赞 ● │ │
│ ├────────────────────────────────────┤ │
│ │ ...更多消息... │ │
│ └────────────────────────────────────┘ │
└──────────────────────────────────────────┘

四、Badge 组件的最佳实践
4.1 数字角标的阈值处理
数字角标的常见需求是超过一定数量显示"99+"。这个逻辑需要开发者在传入 value 前自行处理:
// 推荐:在数据层处理阈值
function formatBadgeValue(count: number): string {
if (count > 99) return '99+';
if (count > 0) return '' + count;
return '';
}
// 使用时
Badge({ value: formatBadgeValue(item.count), style: { badgeColor: '#FF3B30', fontSize: 10 }, position: BadgePosition.RightTop })
Badge 组件本身不处理数字阈值——它原样显示传入的 value 字符串。如果 count 是 128,而你传入了 '128',Badge 就会显示三位数字,角标变得过宽。应在数据层做阈值截断。
4.2 圆点角标与数字角标的选择
两种角标各有适用场景:
- 数字角标:适合可量化的未读通知——“3 条评论”、“5 个赞”、“2 条私信”。数字给用户具体的预期,告诉用户"有多少内容需要处理"。
- 圆点角标:适合不可量化的通知——“有人关注了你”、“账号在新设备登录”。这类事件只有"有"和"无"两种状态,用圆点表示更合适。
在 Demo 中,两种角标通过条件判断共同存在:count > 0 时用数字角标,count === 0 && dot 时用圆点角标。
4.3 Badge 与 Stack 的配合
Badge 必须放在 Stack 容器中才能正常工作。Badge 的定位是相对于 Stack 的边界进行的:
Stack() {
// 被标注的目标组件
Image($r('app.media.avatar'))
.width(44).height(44)
// 角标组件
Badge({ value: '3', style: { badgeColor: '#FF3B30', fontSize: 10 }, position: BadgePosition.RightTop })
.width(44).height(44)
}
Badge 的 width/height 应与目标组件相同——这确保 Badge 的定位基准与目标组件对齐。如果 Badge 不设置宽高,它的定位基准是 Stack 的完整区域,可能导致角标偏移。
4.4 条件渲染与角标
角标的显示/隐藏应通过 ArkUI 的条件渲染语法(if/else)而非 visibility 属性实现:
// 推荐:条件渲染
if (item.unread) {
Badge({ value: '' + item.count, style: { badgeColor: '#FF3B30', fontSize: 10 }, position: BadgePosition.RightTop })
.width(44).height(44)
}
// 不推荐:visibility 控制
Badge({ value: '' + item.count, style: { badgeColor: '#FF3B30', fontSize: 10 }, position: BadgePosition.RightTop })
.width(44).height(44)
.visibility(item.unread ? Visibility.Visible : Visibility.Hidden)
条件渲染在角标不存在时不创建组件节点,比 visibility 隐藏更节省渲染资源。此外,条件渲染让角标的显示逻辑更直观——在代码中一眼就能看出"什么时候显示角标"。
4.5 角标与无障碍
对于消息通知类页面,角标的可访问性设计值得考虑:
- 数字角标的 value 应使用清晰的数字,避免仅用图标或纯色圆点
- 配合朗读文本:Badge 本身不提供无障碍语义,但可以在外层 Stack 添加
.accessibilityText()描述 - 颜色不是唯一的区分方式:圆点和数字角标用不同的形状(圆点 vs 胶囊形)区分类型
4.6 性能优化
如果消息列表很长(50+ 条),每条消息都有条件角标渲染,需要注意:
- 使用
ForEach的 key 函数:Demo 中ForEach(this.messages, ..., item => '' + item.id)的 key 函数帮助框架最小化 DOM 更新 - 避免内联对象:Badge 的 style 对象如果每次都重新创建,会产生不必要的 diff。但 ArkUI 框架在组件层面已做优化,大多数场景不需要额外处理
- 已读/未读切换:标记已读时创建新的数组副本 → 赋值给 @State → 框架 diff 后发现部分消息的角标条件变化 → 只更新变化的部分。这是标准且高效的 ArkUI 渲染模式
五、完整代码结构
BadgePage (~270 行)
├── 数据模型
│ └── interface MessageItem — 消息数据结构
├── 状态变量
│ ├── @State messages: MessageItem[] — 8 条模拟消息
│ └── @State unreadTotal: number — 未读消息总数
├── 业务方法
│ ├── countTotal() — 统计未读数量
│ ├── markAsRead(id) — 标记单条已读
│ └── markAllRead() — 标记全部已读
├── 辅助方法
│ └── tagColor(tag) — 消息分类颜色映射
├── 视图
│ ├── 标题栏 — 📬 消息中心
│ ├── 说明卡片 — Badge 组件介绍
│ ├── 状态栏 — 未读计数 + 全部已读按钮
│ ├── 角标类型演示区 — 数字/圆点/文本三种角标
│ └── 消息列表 — 8 条消息 + 条件角标
│ ├── 头像区 Stack — 标签 + Badge
│ └── 内容区 Column — 标题 + 时间 + 正文
└── @Builder badgeDemo() — 角标演示卡片
六、总结
本文通过一个消息通知中心 Demo 深入讲解了 HarmonyOS NEXT 中的 Badge 角标组件。Badge 将传统的手动 Stack 叠加 + 手动定位方案封装为声明式组件,通过 value 参数区分三种角标模式,通过 BadgePosition 控制位置,通过 BadgeStyle 配置样式。
核心要点回顾:
-
三种角标模式:数字角标(
value: '99+')、圆点角标(value: '')、文本角标(value: 'NEW'),分别适用于可量化未读数、不可量化通知和状态标签三种场景。 -
Badge 依赖 Stack:Badge 必须放在 Stack 容器中,通过
BadgePosition确定在 Stack 中的位置。Badge 的宽高应与目标组件一致,确保定位基准对齐。 -
条件渲染控制显示:使用 ArkUI 的
if/else语法控制角标的显示/隐藏,比 visibility 控制更高效且代码意图更清晰。 -
不可变更新模式:标记已读时必须创建新数组副本再赋值,因为
@State基于引用比较检测变化。 -
数字阈值自行处理:Badge 不自动截断大数字(如显示 99+),开发者在传入 value 前自行格式化。
Badge 是消息通知体系中的核心视觉元素——它用最少的空间传递最关键的信息。在 HarmonyOS NEXT 中,Badge 组件让这个看似简单的 UI 元素变得真正简单:一行声明替代了手动布局、手动定位和手动格式化的十几行代码。
七、扩展思考
Badge 解决了基础的角标展示需求,但在实际项目中,角标的使用还有更多变化:
动态角标更新:本文 Demo 中角标通过 @State 数组驱动,标记已读后重建数组触发更新。在真实应用中,角标数据可能来自推送通知、消息同步或 WebSocket 实时更新——每次数据到达时更新 @State 数组即可,角标自动跟随刷新。
自定义角标形状:Badge 默认是圆形/胶囊形背景。如果需要三角形角标、带状角标或特殊形状角标,Badge 组件本身不支持自定义形状——需要回到手动 Stack + 自定义绘制方案。
组合角标:某些场景需要在同一位置显示多个角标(如同时显示数字和 NEW 标签)。单一 Badge 组件只能显示一个角标内容——多角标场景需要嵌套多个 Badge(不推荐,会重叠)或回到自定义布局。
角标动画:Badge 组件本身不提供显示/隐藏动画。如果需要角标弹入弹出效果(如微信的红点出现动画),需要在父组件层面使用 .transition() 或 .animation() 实现。
桌面角标:Android/iOS 桌面图标角标通过系统 API 设置(NotificationManager),与 Badge 组件无关。Badge 只用于应用内 UI 角标展示。
理解 Badge 的定位是正确使用它的关键:它是应用内 UI 级角标组件,解决了"在任意组件右上角显示数字/圆点/文本标记"的通用需求,但它不是系统级桌面角标的替代方案,也不支持自定义形状和动画。
通过本文的 Demo ——消息通知中心,你将 Badge 的三种模式和条件渲染模式应用到实践中,构建了一个完整的消息列表界面。这个页面可以作为任何需要消息中心的应用的起点模板。
更多推荐




所有评论(0)