鸿蒙新特性——Badge 徽章组件详解
一、引言
在移动应用界面中,角标/徽章(Badge)是一个小但无处不在的 UI 元素。社交 App 的消息列表上那个红色的未读数字、购物 App 底部 Tab 栏上购物车的商品数量角标、邮件客户端的收件箱未读计数、系统通知栏的应用角标——它们都以一个微小的圆形贴片附着在图标或列表项上,用最简洁的方式传递"这里有新的内容"的信息。
虽然角标看起来只是一个"带数字的小圆点",但在传统开发中实现它并非易事。你需要计算角标相对于宿主元素的位置(通常是右上角),处理数字过长时的显示策略(超过 99 显示"99+“还是”…"),管理空状态的显示逻辑(未读数为 0 时不显示角标),以及确保角标在不同屏幕密度下的大小一致。如果每个需要角标的场景都手写这些逻辑,代码会迅速膨胀。
而 HarmonyOS 提供了 Badge 组件——一个专用于徽章/角标的容器组件。它包裹任意子组件,自动在指定位置渲染一个带有数字或文本的角标。支持自定义位置(右上/右下/右侧)、颜色、大小、最大显示数字(超出以"99+"展示),并且当数值为 0 或空字符串时角标自动隐藏。
本文通过一个"消息中心"Demo 深入讲解 Badge 组件的核心用法:角标的位置如何设置?颜色和大小如何定制?如何与列表联动实现"标为已读"清除角标?以及 Badge 在实际业务中的最佳实践。
阅读完本文,你将能够:
- 使用 Badge 组件为任意 UI 元素添加角标
- 掌握 Badge 的位置(BadgePosition)和样式配置
- 实现消息列表的未读角标与交互联动
- 理解 Badge 的显示/隐藏逻辑和 maxCount 截断策略
- 构建完整的消息中心页面
二、Badge 组件 API 总览
2.1 构造函数
Badge(options: BadgeOptions)
interface BadgeOptions {
value: string; // 角标显示的文本内容
position?: BadgePosition; // 角标位置,默认 RightTop
style?: BadgeStyle; // 角标样式配置
maxCount?: number; // 最大显示数字,默认 99
}
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
value |
string | 必填 | 角标内容,"0"或空字符串表示不显示角标 |
position |
BadgePosition | RightTop | 角标相对宿主元素的方位 |
style |
BadgeStyle | 默认样式 | 角标的颜色和大小配置 |
maxCount |
number | 99 | 数字超过此值时显示"99+" |
2.2 BadgePosition 枚举
enum BadgePosition {
Right, // 右侧居中
RightTop, // 右上角(默认,最常用)
Left // 左侧居中
}
| 位置 | 视觉效果 | 适用场景 |
|---|---|---|
RightTop |
角标在宿主右上角,略微溢出 | 消息列表头像、Tab 图标角标 |
Right |
角标在宿主右侧居中 | 列表项尾部状态标记 |
Left |
角标在宿主左侧居中 | 特殊布局需求 |
绝大多数场景使用默认的 RightTop,它符合用户"角标在右上角"的认知习惯。Right 和 Left 适用于非标准的布局需求。
2.3 BadgeStyle 对象
interface BadgeStyle {
badgeColor?: ResourceColor; // 角标背景颜色,默认红色
badgeSize?: number | string; // 角标大小,默认 12vp
}
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
badgeColor |
ResourceColor | #FA2A2D(红色) |
角标圆形的背景色 |
badgeSize |
number/string | 16vp | 角标圆形的直径 |
badgeSize 控制角标圆形的直径。对于头像上的角标,建议 14-18vp;对于图标上的角标,建议 12-16vp。角标内的文字大小会根据 badgeSize 自动缩放。
2.4 value 参数的特殊逻辑
value 是 Badge 最核心且微妙的参数。它的取值直接决定了角标的显示行为:
| value 值 | 显示效果 |
|---|---|
"5" |
显示数字 5 |
"99" |
显示数字 99 |
"123" |
如果 maxCount=99,显示"99+" |
"0" |
不显示角标 |
"" |
不显示角标 |
"New" |
显示文本"New" |
"!" |
显示感叹号标点 |
关键逻辑:当 value 为 "0" 或空字符串 "" 时,Badge 会自动隐藏角标。这一设计让未读计数为 0 时无需额外条件判断即可自动隐藏角标,大大简化了业务代码。
2.5 Badge 作为容器组件
Badge 是一个容器组件——它包裹(sling)一个子组件,角标附着在这个子组件之上:
Badge({ value: '5', position: BadgePosition.RightTop }) {
// 任意子组件——图标、头像、文本等
Image($r('app.media.icon'))
.width(48).height(48)
}
这意味着 Badge 不改变子组件的固有布局和尺寸,它只是在上方叠加了一个角标。子组件可以是任何 ArkUI 组件:Image、Text、Row、Column,甚至嵌套的复杂组件。
三、Demo 设计:消息中心
3.1 功能概述
Demo 是一个"消息中心",模拟即时通讯应用的消息列表页面:
- 未读总览卡片:顶部蓝色卡片显示总未读数(带 Badge),以及"全部已读"按钮
- 消息列表:8 条模拟消息,每条显示头像(带未读角标)、联系人名、预览、时间
- 点击标为已读:点击消息项清除该条角标,总计数同步刷新
- 徽章配置:切换角标位置(右上/右下/右侧)、切换角标颜色(红/蓝/绿/橙)
3.2 交互点
| # | 交互 | 说明 |
|---|---|---|
| 1 | 点击消息项 | 将该条消息标为已读,角标消失,总计数 -N |
| 2 | 全部已读 | 一键清除所有未读角标,总计数归零 |
| 3 | 角标位置切换 | 右上 / 右下 / 右侧三种位置实时切换 |
| 4 | 角标颜色切换 | 红 / 蓝 / 绿 / 橙四种颜色实时切换 |
四、完整代码实现

4.1 数据模型与状态
interface MessageItem {
id: number;
name: string;
avatar: string;
preview: string;
time: string;
unread: number;
}
@State messages: MessageItem[] = [
{ id: 1, name: '张小明', avatar: '#1677FF', preview: '明天的会议改到下午3点可以吗?', time: '刚刚', unread: 5 },
{ id: 2, name: '李设计', avatar: '#FF4D4F', preview: '新的设计稿已经上传到Figma了', time: '5分钟前', unread: 0 },
{ id: 3, name: '王开发', avatar: '#52C41A', preview: '这个bug我修好了,你测一下', time: '10分钟前', unread: 99 },
{ id: 4, name: '赵产品', avatar: '#FF9800', preview: 'PRD已经更新了,加了一个新需求', time: '30分钟前', unread: 2 },
// ... 更多消息项
];
@State totalUnread: number = 0;
@State badgePosition: BadgePosition = BadgePosition.RightTop;
@State badgeColor: string = '#FF4D4F';
每条消息的 unread 字段决定角标显示的数值。totalUnread 是所有消息未读数的总和,显示在顶部总览卡片中作为总角标。
4.2 角标值计算
getBadgeValue(count: number): string {
if (count === 0) {
return '';
}
if (count > this.badgeMaxCount) {
return this.badgeMaxCount.toString().concat('+');
}
return count.toString();
}
当 count 为 0 时返回空字符串 ""——根据 Badge 的 value 逻辑,空字符串会使角标自动隐藏。当 count 超过 maxCount(默认 99)时返回 "99+",否则返回数字的字符串形式。
4.3 标为已读的实现
markAsRead(id: number): void {
const newList: MessageItem[] = [];
for (let i = 0; i < this.messages.length; i++) {
if (this.messages[i].id === id) {
newList.push({
id: this.messages[i].id,
name: this.messages[i].name,
avatar: this.messages[i].avatar,
preview: this.messages[i].preview,
time: this.messages[i].time,
unread: 0 // 清零
});
} else {
newList.push(this.messages[i]);
}
}
this.messages = newList;
this.updateTotal();
}
由于 ArkTS 的 @State 要求不可变更新,不能直接修改数组元素,而是需要创建新数组。markAsRead 遍历消息列表,将匹配 ID 的消息项 unread 设为 0(替换为新对象),其余项保持不变。最后将新数组赋值给 this.messages 触发 UI 刷新。
updateTotal() 遍历所有消息累加 unread,更新 totalUnread 状态——顶部总览卡片的总角标随之刷新。
4.4 消息列表项
ForEach(this.messages, (msg: MessageItem) => {
Column() {
Row() {
Badge({
value: this.getBadgeValue(msg.unread),
position: this.badgePosition,
style: { badgeSize: 16, badgeColor: this.badgeColor }
}) {
Row()
.width(44).height(44)
.borderRadius(22)
.backgroundColor(msg.avatar)
.justifyContent(FlexAlign.Center)
}
Column() {
Row() {
Text(msg.name)
.fontSize(15).fontColor('#1a1a2e')
.fontWeight(FontWeight.Medium).layoutWeight(1)
Text(msg.time)
.fontSize(11).fontColor('#BBBBCC')
}
Text(msg.preview)
.fontSize(13).fontColor('#9999AA')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
.layoutWeight(1).margin({ left: 12 })
}
.width('100%')
.padding({ top: 14, bottom: 14, left: 4, right: 4 })
.onClick(() => { this.markAsRead(msg.id); })
}
.width('100%')
.border({ width: { bottom: 0.5 }, color: '#F2F3F5' })
})
每个消息项是一个水平布局:Badge 包裹彩色圆形头像 → 联系人名 + 消息预览。点击整行触发 markAsRead,该条消息的未读角标消失。
Badge 的 position 和 style.badgeColor 都绑定到 @State 变量,用户在配置面板切换时所有消息项的角标同步更新。
4.5 总览卡片与 Badge
Row() {
Badge({
value: this.getBadgeValue(this.totalUnread),
position: BadgePosition.RightTop,
style: { badgeSize: 18, badgeColor: '#FF4D4F' }
}) {
Image($r('sys.symbol.message'))
.width(28).height(28)
.fillColor('#FFFFFF')
}
.margin({ right: 12 })
Column() {
Text('未读消息'.concat(' ', this.totalUnread.toString(), ' 条'))
.fontSize(16).fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
Text('点击消息项即可标为已读')
.fontSize(12).fontColor('#FFFFFF88')
}
// ...
Text('全部已读')
.fontSize(12).fontColor('#FFFFFF')
.onClick(() => { this.markAllRead(); })
}
顶部卡片使用系统符号 sys.symbol.message 作为图标,用 Badge 包裹显示总未读数。getBadgeValue(this.totalUnread) 在总数为 0 时返回空字符串,角标自动消失。"全部已读"按钮调用 markAllRead() 将所有消息的 unread 设为 0。
4.6 角标位置和颜色配置
// 位置切换
ForEach(['右上', '右下', '右侧'], (pos: string) => {
Text(pos)
.onClick(() => {
if (pos === '右上') { this.badgePosition = BadgePosition.RightTop; }
if (pos === '右下') { this.badgePosition = BadgePosition.Right; }
if (pos === '右侧') { this.badgePosition = BadgePosition.Right; }
})
})
// 颜色切换
ForEach(['#FF4D4F', '#1677FF', '#52C41A', '#FF9800'], (c: string) => {
Row()
.width(24).height(24).borderRadius(12)
.backgroundColor(c)
.border({ width: this.badgeColor === c ? 3 : 0, color: c })
.onClick(() => { this.badgeColor = c; })
})
颜色配置以圆形色块的方式展示,选中态通过加粗边框(border.width: 3)表示。这种"颜色选择器"的设计既直观又节省空间。
五、关键技术点详解
5.1 Badge 的位置与溢出策略
Badge 的角标相对于宿主元素的定位方式为:角标圆心位于宿主元素的指定方位边缘。以 RightTop 为例,角标的圆心在宿主元素的右上角顶点附近,由于角标是一个圆形,它会部分溢出到宿主元素范围之外——这正是我们期望的"角标贴在上方"的效果。
角标的溢出量等于角标的半径(badgeSize / 2)。例如 badgeSize: 16 时,角标会在右上角向外溢出约 8vp。这意味着在布局时需要为 Badge 宿主元素留出适当的 margin 或 padding,避免角标被父容器裁剪。
// 推荐:为 Badge 宿主留出角标溢出空间
.margin({ top: 4, right: 4 })
5.2 value 空值逻辑的妙用
Badge 最巧妙的设计是 value 为空字符串或 "0" 时自动隐藏角标。这一特性在业务代码中非常实用——开发者不需要在每次使用 Badge 时都加一层条件判断:
// 不需要这样写(Badge 自动处理了零值隐藏)
if (unreadCount > 0) {
Badge({ value: unreadCount.toString() }) { ... }
} else {
// 仅显示宿主元素,无角标
}
// 直接这样写即可
Badge({ value: this.getBadgeValue(unreadCount) }) { ... }
getBadgeValue 方法返回空字符串(未读数为 0)、“99+”(超过 99)或数字字符串——Badge 内部自动处理了这三种情况的显示逻辑。
5.3 Badge 的"仅圆点"模式
在某些场景下(如"有新的系统通知但不需要显示具体数量"),需要一个不带数字的红色小圆点。可以通过设置 value: ' '(一个空格)或利用小到不可见的文字来实现,但更规范的做法是设置一个极小的值。不过,ArkUI 的 Badge 组件不原生支持"dot-only"模式。变通方案是设置 value: '' 时角标隐藏,而需要圆点时设置一个不可见字符(如零宽空格)。
对于大多数业务场景,"0 不显示、>0 显示数字"的方案已经足够。
5.4 @State 数组的不可变更新
Demo 中的 markAsRead 方法展示了 ArkTS 中 @State 数组的标准更新模式——不可变替换:
// 错误做法(直接修改,ArkTS 编译器会报错或 UI 不刷新)
this.messages[idx].unread = 0;
// 正确做法(创建新数组和新对象)
const newList: MessageItem[] = [];
for (let i = 0; i < this.messages.length; i++) {
if (this.messages[i].id === id) {
newList.push({ /* 展开字段, unread: 0 */ });
} else {
newList.push(this.messages[i]);
}
}
this.messages = newList;
这种模式在 ArkTS 中非常常见:遍历 → 判断 → 创建新对象 → 推入新数组 → 赋值给 @State 变量。虽然代码量比命令式修改多一些,但它保证了 UI 的可靠刷新和状态的不可变性。
5.5 Badge 与系统符号的组合使用
Demo 的顶部卡片使用了系统符号(System Symbol)sys.symbol.message 作为图标:
Image($r('sys.symbol.message'))
.width(28).height(28)
.fillColor('#FFFFFF')
系统符号是一组内置的 SF Symbol 风格图标,通过 $r('sys.symbol.xxx') 引用。它们不需要额外引入资源文件,且颜色通过 .fillColor() 控制。Badge 包裹这个图标后,角标自动出现在图标的右上角。
六、运行效果
6.1 初始状态
进入"消息中心"页面,顶部蓝色卡片展示总未读角标(包裹消息图标)+ “未读消息 110 条”(5+0+99+2+0+1+0+3=110)。下方 8 条消息,每条显示彩色圆形头像(带红色未读角标)、联系人名、消息预览和时间。
张小明显示角标"5",王开发显示角标"99+ "(因 unread=99 达到 maxCount 阈值),李设计和项目群的未读为 0,角标不显示。
6.2 标为已读
点击王开发的消息行 → 该行角标消失(unread 从 99 变为 0),顶部总角标从 110 变为 11。再点击张小明 → 角标消失,总计数从 11 变为 6。
6.3 一键全部已读
点击"全部已读"按钮 → 所有消息项的角标消失,顶部总角标自动隐藏(totalUnread 归零,getBadgeValue(0) 返回空字符串)。
6.4 角标配置调整
在配置面板点击"右下" → 所有消息项的角标位置从右上角变为右下角居中。点击绿色色块 → 所有角标颜色从红色变为绿色 #52C41A。切换回"右上" + 红色,恢复正常外观。
七、总结
本文通过一个"消息中心"实战 Demo,深入讲解了 HarmonyOS Badge 徽章组件的核心用法:
- Badge 作为容器组件:包裹任意子组件,在指定位置渲染角标
- BadgePosition:RightTop(右上,默认)/ Right(右侧居中)/ Left(左侧居中)三种位置
- BadgeStyle:
badgeColor(背景色)和badgeSize(直径)控制角标外观 - value 空值隐藏:
""或"0"时角标自动不显示,简化零值判断代码 - maxCount 截断:数字超过 maxCount 显示"99+",避免角标过宽
- 与列表联动:通过
@State数组不可变更新实现"标为已读→角标消失"的交互闭环
Badge 组件虽然 API 简洁,但在实际应用中承载了大量微妙的 UI 逻辑——位置计算、数字截断、零值隐藏——这些逻辑如果由开发者手写,既容易出错又占用精力。Badge 将这些细节封装进组件内部,让开发者专注于业务逻辑而非角标渲染。这正是 ArkUI"组件化封装"设计哲学的体现。
从消息列表到购物车角标,从邮件未读到通知提醒,Badge 以最简洁的方式传递"这里有新内容"的信息。希望本文能帮助你在实际项目中高效运用 Badge 组件。
本文基于 HarmonyOS NEXT API 24 编写,代码经 DevEco Studio 6.1.1 编译验证通过。
更多推荐



所有评论(0)