鸿蒙新特性——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)interfaceBadgeOptions{value:string;// 角标显示的文本内容position?:BadgePosition;// 角标位置,默认 RightTopstyle?:BadgeStyle;// 角标样式配置maxCount?:number;// 最大显示数字,默认 99}| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
value | string | 必填 | 角标内容,"0"或空字符串表示不显示角标 |
position | BadgePosition | RightTop | 角标相对宿主元素的方位 |
style | BadgeStyle | 默认样式 | 角标的颜色和大小配置 |
maxCount | number | 99 | 数字超过此值时显示"99+" |
2.2 BadgePosition 枚举
enumBadgePosition{Right,// 右侧居中RightTop,// 右上角(默认,最常用)Left// 左侧居中}| 位置 | 视觉效果 | 适用场景 |
|---|---|---|
RightTop | 角标在宿主右上角,略微溢出 | 消息列表头像、Tab 图标角标 |
Right | 角标在宿主右侧居中 | 列表项尾部状态标记 |
Left | 角标在宿主左侧居中 | 特殊布局需求 |
绝大多数场景使用默认的RightTop,它符合用户"角标在右上角"的认知习惯。Right和Left适用于非标准的布局需求。
2.3 BadgeStyle 对象
interfaceBadgeStyle{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 数据模型与状态
interfaceMessageItem{id:number;name:string;avatar:string;preview:string;time:string;unread:number;}@Statemessages: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},// ... 更多消息项];@StatetotalUnread:number=0;@StatebadgePosition:BadgePosition=BadgePosition.RightTop;@StatebadgeColor:string='#FF4D4F';每条消息的unread字段决定角标显示的数值。totalUnread是所有消息未读数的总和,显示在顶部总览卡片中作为总角标。
4.2 角标值计算
getBadgeValue(count:number):string{if(count===0){return'';}if(count>this.badgeMaxCount){returnthis.badgeMaxCount.toString().concat('+');}returncount.toString();}当count为 0 时返回空字符串""——根据 Badge 的 value 逻辑,空字符串会使角标自动隐藏。当count超过maxCount(默认 99)时返回"99+",否则返回数字的字符串形式。
4.3 标为已读的实现
markAsRead(id:number):void{constnewList:MessageItem[]=[];for(leti=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;// 正确做法(创建新数组和新对象)constnewList:MessageItem[]=[];for(leti=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 编译验证通过。
