一、引言在移动应用界面中角标/徽章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}参数类型默认值说明valuestring必填角标内容0或空字符串表示不显示角标positionBadgePositionRightTop角标相对宿主元素的方位styleBadgeStyle默认样式角标的颜色和大小配置maxCountnumber99数字超过此值时显示992.2 BadgePosition 枚举enumBadgePosition{Right,// 右侧居中RightTop,// 右上角默认最常用Left// 左侧居中}位置视觉效果适用场景RightTop角标在宿主右上角略微溢出消息列表头像、Tab 图标角标Right角标在宿主右侧居中列表项尾部状态标记Left角标在宿主左侧居中特殊布局需求绝大多数场景使用默认的RightTop它符合用户角标在右上角的认知习惯。Right和Left适用于非标准的布局需求。2.3 BadgeStyle 对象interfaceBadgeStyle{badgeColor?:ResourceColor;// 角标背景颜色默认红色badgeSize?:number|string;// 角标大小默认 12vp}属性类型默认值说明badgeColorResourceColor#FA2A2D红色角标圆形的背景色badgeSizenumber/string16vp角标圆形的直径badgeSize控制角标圆形的直径。对于头像上的角标建议 14-18vp对于图标上的角标建议 12-16vp。角标内的文字大小会根据badgeSize自动缩放。2.4 value 参数的特殊逻辑value是 Badge 最核心且微妙的参数。它的取值直接决定了角标的显示行为value 值显示效果5显示数字 599显示数字 99123如果 maxCount99显示990不显示角标不显示角标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点击消息项将该条消息标为已读角标消失总计数 -N2全部已读一键清除所有未读角标总计数归零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:number0;StatebadgePosition:BadgePositionBadgePosition.RightTop;StatebadgeColor:string#FF4D4F;每条消息的unread字段决定角标显示的数值。totalUnread是所有消息未读数的总和显示在顶部总览卡片中作为总角标。4.2 角标值计算getBadgeValue(count:number):string{if(count0){return;}if(countthis.badgeMaxCount){returnthis.badgeMaxCount.toString().concat();}returncount.toString();}当count为 0 时返回空字符串——根据 Badge 的 value 逻辑空字符串会使角标自动隐藏。当count超过maxCount默认 99时返回99否则返回数字的字符串形式。4.3 标为已读的实现markAsRead(id:number):void{constnewList:MessageItem[][];for(leti0;ithis.messages.length;i){if(this.messages[i].idid){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.messagesnewList;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 总览卡片与 BadgeRow(){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.badgePositionBadgePosition.RightTop;}if(pos右下){this.badgePositionBadgePosition.Right;}if(pos右侧){this.badgePositionBadgePosition.Right;}})})// 颜色切换ForEach([#FF4D4F,#1677FF,#52C41A,#FF9800],(c:string){Row().width(24).height(24).borderRadius(12).backgroundColor(c).border({width:this.badgeColorc?3:0,color:c}).onClick((){this.badgeColorc;})})颜色配置以圆形色块的方式展示选中态通过加粗边框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(unreadCount0){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].unread0;// 正确做法创建新数组和新对象constnewList:MessageItem[][];for(leti0;ithis.messages.length;i){if(this.messages[i].idid){newList.push({/* 展开字段, unread: 0 */});}else{newList.push(this.messages[i]);}}this.messagesnewList;这种模式在 ArkTS 中非常常见遍历 → 判断 → 创建新对象 → 推入新数组 → 赋值给State变量。虽然代码量比命令式修改多一些但它保证了 UI 的可靠刷新和状态的不可变性。5.5 Badge 与系统符号的组合使用Demo 的顶部卡片使用了系统符号System Symbolsys.symbol.message作为图标Image($r(sys.symbol.message)).width(28).height(28).fillColor(#FFFFFF)系统符号是一组内置的 SF Symbol 风格图标通过$r(sys.symbol.xxx)引用。它们不需要额外引入资源文件且颜色通过.fillColor()控制。Badge 包裹这个图标后角标自动出现在图标的右上角。六、运行效果6.1 初始状态进入消息中心页面顶部蓝色卡片展示总未读角标包裹消息图标 “未读消息 110 条”509920103110。下方 8 条消息每条显示彩色圆形头像带红色未读角标、联系人名、消息预览和时间。张小明显示角标5王开发显示角标99 因 unread99 达到 maxCount 阈值李设计和项目群的未读为 0角标不显示。6.2 标为已读点击王开发的消息行 → 该行角标消失unread 从 99 变为 0顶部总角标从 110 变为 11。再点击张小明 → 角标消失总计数从 11 变为 6。6.3 一键全部已读点击全部已读按钮 → 所有消息项的角标消失顶部总角标自动隐藏totalUnread归零getBadgeValue(0)返回空字符串。6.4 角标配置调整在配置面板点击右下 → 所有消息项的角标位置从右上角变为右下角居中。点击绿色色块 → 所有角标颜色从红色变为绿色#52C41A。切换回右上 红色恢复正常外观。七、总结本文通过一个消息中心实战 Demo深入讲解了 HarmonyOS Badge 徽章组件的核心用法Badge 作为容器组件包裹任意子组件在指定位置渲染角标BadgePositionRightTop右上默认/ Right右侧居中/ Left左侧居中三种位置BadgeStylebadgeColor背景色和badgeSize直径控制角标外观value 空值隐藏或0时角标自动不显示简化零值判断代码maxCount 截断数字超过 maxCount 显示99避免角标过宽与列表联动通过State数组不可变更新实现标为已读→角标消失的交互闭环Badge 组件虽然 API 简洁但在实际应用中承载了大量微妙的 UI 逻辑——位置计算、数字截断、零值隐藏——这些逻辑如果由开发者手写既容易出错又占用精力。Badge 将这些细节封装进组件内部让开发者专注于业务逻辑而非角标渲染。这正是 ArkUI组件化封装设计哲学的体现。从消息列表到购物车角标从邮件未读到通知提醒Badge 以最简洁的方式传递这里有新内容的信息。希望本文能帮助你在实际项目中高效运用 Badge 组件。本文基于 HarmonyOS NEXT API 24 编写代码经 DevEco Studio 6.1.1 编译验证通过。