前言
游戏里经常见到这种提示——「最高分 NEW 标」「未读消息红点」「活动倒计时角标」。这些挂在组件右上角的小标记,HarmonyOS 提供了Badge组件一行搞定,不用自己用Stack叠红点。
本篇以「猫猫大作战」主菜单最高分徽章加 NEW 标为锚点,把 Badge 的基本用法、位置与样式定制、与 Stack 手搓的取舍三大要点讲透。
提示:本系列不讲 ArkTS 基础语法与环境搭建,假设你已跟完第 1–25 篇。本篇是布局进阶的第六篇。
一、场景拆解:最高分 NEW 角标
回顾「猫猫大作战」主菜单最高分行(第 8 篇):
// 来源:entry/src/main/ets/pages/Index.ets MainMenuView() if (this.highScore > 0) { Row() { Text('🏆 最高分: ').fontSize(16).fontColor('#F1C40F') Text(this.highScore.toString()).fontSize(20).fontWeight(FontWeight.Bold).fontColor('#F1C40F') }.margin({ bottom: 32 }) }现在想加个「NEW」角标——用户首次破纪录时显示,提示「这是新纪录」。用 Badge 包住整个 Row:
Badge({ count: 0, position: BadgePosition.RightTop, style: { fontSize: 10, badgeSize: 28, badgeColor: '#E74C3C' } }) { // 被标记的内容 Row() { Text('🏆 最高分: ...'); } }关键经验:Badge 是「容器型」组件——它包裹目标内容,在指定位置渲染角标。
二、Badge 的两种模式
2.1 数字角标(count)
Badge({ count: 5, // 显示数字 5 position: BadgePosition.RightTop, // 右上角 style: { fontSize: 10, badgeSize: 28, badgeColor: '#E74C3C' } }) { Image($r('app.media.msg')) // 被标记的消息图标 }count 行为:
| count 值 | 显示 |
|---|---|
| 0 | 不显示角标 |
| 1–99 | 显示数字 |
| ≥ 100 | 显示「99+」 |
2.2 文本角标(value)
Badge({ value: 'NEW', // 显示文本「NEW」 position: BadgePosition.RightTop, style: { fontSize: 10, badgeSize: 28, badgeColor: '#E74C3C' } }) { Row() { Text('🏆 最高分: 99') } }value 行为:
| value 值 | 显示 |
|---|---|
| 空字符串 | 不显示角标 |
| ‘NEW’ | 显示「NEW」 |
| ‘HOT’ | 显示「HOT」 |
实战经验:数字用 count,文本用 value——不能同时设,同时设以 value 为准。
2.3 position 位置
position: BadgePosition.RightTop // 右上(最常用) position: BadgePosition.Right // 右侧垂直居中| 位置 | 适用 |
|---|---|
| RightTop | 图标右上红点、NEW 角标 |
| Right | 列表项右侧「未读 5」标记 |
三、用 Badge 改造最高分行
3.1 加 NEW 角标
@Builder MainMenuView() { Column() { Spacer().height('15%') Text('🐱').fontSize(72).margin({ bottom: 8 }) Text('猫猫大作战').fontSize(36).fontWeight(FontWeight.Bold).fontColor('#2C3E50').margin({ bottom: 8 }) Text('合并进化 · 策略消除').fontSize(16).fontColor('#95A5A6').margin({ bottom: 48 }) // 最高分(Badge 包裹,加 NEW 角标) if (this.highScore > 0) { Badge({ value: this.isNewRecord ? 'NEW' : '', // 新纪录显示 NEW position: BadgePosition.RightTop, style: { fontSize: 10, badgeSize: 28, badgeColor: '#E74C3C', textColor: '#FFFFFF' } }) { Row() { Text('🏆 最高分: ').fontSize(16).fontColor('#F1C40F') Text(this.highScore.toString()).fontSize(20).fontWeight(FontWeight.Bold).fontColor('#F1C40F') } .margin({ bottom: 32 }) } } // 开始游戏按钮 Button('开始游戏') .width('70%').height(56) .fontSize(20).fontWeight(FontWeight.Bold) .fontColor('#FFFFFF').backgroundColor('#2ECC71') .borderRadius(28) .shadow({ radius: 8, color: 'rgba(46, 204, 113, 0.4)', offsetY: 4 }) .onClick(() => { this.startGame(); }) Spacer().height(24) // 规则面板(第 25 篇) Scroll() { /* ... */ }.height(180) Spacer() } .width('100%').height('100%') .linearGradient({ direction: GradientDirection.Bottom, colors: [['#E8F4F8', 0.0], ['#D6EEF5', 0.5], ['#C9E8F2', 1.0]] }) .alignItems(HorizontalAlign.Center) }3.2 isNewRecord 状态控制
@State isNewRecord: boolean = false; endGame() { this.gameState = GameState.GAME_OVER; if (this.score > this.highScore) { this.highScore = this.score; this.isNewRecord = true; // 标记新纪录 } this.clearTimers(); } // 用户回到主菜单后,3 秒自动隐藏 NEW 角标 startGame() { if (this.isNewRecord) { setTimeout(() => { this.isNewRecord = false; }, 3000); } /* ... */ }执行流程:
- 破纪录 →
isNewRecord = true→ Badge 显示「NEW」。 - 回主菜单看到「🏆 最高分: 99 [NEW]」。
- 3 秒后
isNewRecord = false→ Badge value 变空 → 角标消失。
四、style 样式定制
4.1 完整 style 参数
style: { fontSize: 10, // 角标文字字号 badgeSize: 28, // 角标直径(圆形) badgeColor: '#E74C3C', // 角标背景色 textColor: '#FFFFFF', // 角标文字色 borderColor: '#FFFFFF', // 角标边框色 borderWidth: 1 // 角标边框宽 }| 参数 | 作用 | 默认 |
|---|---|---|
| fontSize | 文字字号 | 10 |
| badgeSize | 角标圆形直径 | 16 |
| badgeColor | 背景色 | 红色 |
| textColor | 文字色 | 白色 |
| borderColor | 边框色 | 无 |
| borderWidth | 边框宽 | 0 |
4.2 不同场景的 style 配置
| 场景 | badgeSize | badgeColor | fontSize |
|---|---|---|---|
| 未读消息红点 | 8 | #E74C3C | 8 |
| 未读数字角标 | 20 | #E74C3C | 12 |
| NEW 角标 | 28 | #E74C3C | 10 |
| HOT 角标 | 32 | #F39C12 | 11 |
| 倒计时角标 | 36 | #2ECC71 | 10 |
实战经验:角标文字 ≤ 3 字符用 badgeSize 28,4+ 字符用 32+——否则文字溢出角标圆形。
五、Badge vs Stack 手搓红点
5.1 Stack 手搓红点
Stack({ alignContent: Alignment.TopEnd }) { Image($r('app.media.msg')) Text('5') .fontSize(10).fontColor('#FFFFFF') .backgroundColor('#E74C3C') .borderRadius(10) .padding(2) .margin({ top: -4, right: -4 }) } .width(40).height(40)5.2 Badge 简化版
Badge({ count: 5, position: BadgePosition.RightTop, style: { fontSize: 10, badgeSize: 20, badgeColor: '#E74C3C' } }) { Image($r('app.media.msg')) } .width(40).height(40)5.3 取舍
| 维度 | Badge | Stack 手搓 |
|---|---|---|
| 代码量 | 少 | 多 |
| 可定制性 | 中(固定 style 参数) | 高(任意属性) |
| 99+ 自动转换 | ✅ | ❌ 要自己写 |
| 复杂动效(呼吸/弹跳) | ❌ | ✅ |
实战经验:标准角标用 Badge——99+ 转换、位置对齐都内置;要动效或异形角标用 Stack 手搓。
六、踩坑提示
6.1 Badge 包裹的组件没设尺寸
// ❌ 错误:Row 没 width,Badge 角标位置漂 Badge({ value: 'NEW', position: BadgePosition.RightTop }) { Row() { Text('...') } // wrap_content,位置不定 } // ✅ 正确:给被包裹组件设尺寸,或用 padding 固定 Badge({ value: 'NEW', position: BadgePosition.RightTop }) { Row() { Text('...') }.width(120) }6.2 count 与 value 同时设
// ⚠️ 同时设,value 优先,count 被忽略 Badge({ count: 5, value: 'NEW' }) { /* ... */ } // 显示「NEW」,不显示 56.3 Badge 嵌套 Badge
// ❌ 错误:嵌套 Badge,角标重叠混乱 Badge({ count: 1 }) { Badge({ count: 2 }) { Image() } } // ✅ 正确:只包一层 Badge({ count: 2 }) { Image() }七、性能与最佳实践
- 标准角标用 Badge——99+ 自动转换、位置对齐内置。
- 数字用 count,文本用 value——别同时设。
- 被包裹组件设固定尺寸——避免角标位置漂移。
- 要动效或异形角标用 Stack 手搓——Badge 不支持自定义动效。
- 角标文字 ≤ 3 字符用 badgeSize 28——否则文字溢出。
总结
本篇我们从 Badge 角标切入,掌握了两种模式(count 数字 / value 文本)、position 位置与 style 样式定制、与 Stack 手搓的取舍三大要点,并给出了最高分 NEW 角标完整代码。核心要点:Badge 是容器型组件;count 自动 99+;要动效用 Stack 手搓。
下一篇我们将拆解 Image 资源引用——$r 与 $rawfile 的区别。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- 「猫猫大作战」项目源码:本仓库
entry/src/main/ets/pages/Index.ets - Badge 角标组件官方指南
- BadgePosition 位置枚举官方参考
- ArkUI 信息标记最佳实践
- 开源鸿蒙跨平台社区
- HarmonyOS 开发者官方文档首页
- 系列索引:本仓库
articles/INDEX.md