鸿蒙 ArkUI 列表项圆形角标组件:设计与实现全解析

鸿蒙 ArkUI 列表项圆形角标组件:设计与实现全解析
一、引言
在移动端应用中,角标(Badge) 是最常见的 UI 元素——邮件未读计数、消息通知提示、待办汇总,都以角标传递信息密度。好的角标能在不占用额外空间的前提下,清晰传达数据状态,引导用户操作。
本文以 「带红色圆形角标的列表项组件」 为切入点,从设计思路、技术选型、代码实现到最佳实践,剖析如何在鸿蒙 ArkTS 中实现类似 Flutter ListTile + Container + BoxDecoration 效果的 UI。
二、角标设计原则
2.1 视觉规范
- 形状:圆形。数字超过 9 时自适应宽度变为胶囊形。
- 颜色:红色(#FF3B30),源于 iOS 角标设计语言,认知成本最低。
- 字号:11px,白色加粗,极小尺寸下清晰可读。
- 位置:列表项右侧居中。
- 零值隐藏:未读数为 0 时角标完全隐藏。
2.2 交互设计
- 点击清除:点击列表项后该条未读数归零。
- 批量操作:底部"全部标记为已读"按钮,一键清除所有未读。
三、Flutter 与 ArkUI 布局对比
| 维度 | Flutter | ArkUI |
|---|---|---|
| 语言 | Dart | ArkTS |
| 状态管理 | StatefulWidget + setState |
@State 装饰器 |
| 列表组件 | ListView / ListTile |
List + ListItem |
| 容器 | Container + BoxDecoration |
Stack + 链式调用 |
| 圆形装饰 | BoxDecoration(shape: BoxShape.circle) |
Circle / borderRadius |
组件映射
Flutter ListTile → 鸿蒙 ArkUI
leading: CircleAvatar → Stack { Circle + Text }
title: Text → Text (发送者)
subtitle: Text → Text (摘要)
trailing: Container(BoxDecoration) → Text + backgroundColor + borderRadius
onTap → onClick
四、项目结构
a13/entry/src/main/ets/pages/
├── Index.ets ← 入口
└── BadgeList.ets ← 角标列表组件 ★
核心文件 BadgeList.ets 约 210 行,包含数据模型、辅助函数和 UI 构建逻辑。
五、数据模型与辅助函数
5.1 消息数据接口
interface MessageItem {
sender: string; // 发送者
content: string; // 摘要
time: string; // 时间
unread: number; // 未读数
}
5.2 状态管理
@Component
export struct BadgeList {
@State messages: MessageItem[] = [ ... ];
}
@State 是 ArkUI 核心装饰器。变量变化时框架自动重新渲染依赖该变量的 UI。
5.3 模拟数据
| 发送者 | 未读数 | 场景说明 |
|---|---|---|
| 张工 | 3 | 常规工作消息 |
| 系统通知 | 12 | 两位数角标 |
| 团队邮件 | 1 | 单数字角标 |
| GitHub | 7 | 开发协作通知 |
| 日历提醒 | 0 | 零值 → 隐藏 |
| Jenkins CI | 99 | 边界值 → “99+” |
5.4 辅助函数
// 取首字
function getInitial(name: string): string {
return name.length > 0 ? name.charAt(0) : '?';
}
// 数字格式化:超过99显示"99+"
function formatCount(count: number): string {
if (count > 99) return '99+';
return count.toString();
}
// 动态宽度:1位=18px, 2位=28px, 99+=36px
function badgeWidth(count: number): number {
if (count > 99) return 36;
if (count >= 10) return 28;
return 18;
}
六、核心 UI 组件实现
6.1 圆形头像
Stack() {
Circle().width(40).height(40)
.fill(this.avatarColors[(index ?? 0) % this.avatarColors.length])
Text(getInitial(item.sender))
.fontColor(Color.White).fontSize(16).fontWeight(FontWeight.Bold)
}
.width(40).height(40)
使用 Stack 叠加 Circle + Text,无需图片资源。背景色板含 6 种颜色,取模分配:
private avatarColors: string[] = [
'#3478F6', '#FF9500', '#34C759',
'#AF52DE', '#FF453A', '#5AC8FA',
];
6.2 红色圆形角标
Flutter 实现:
Container(
decoration: BoxDecoration(color: Colors.red, shape: BoxShape.circle),
child: Text('3', style: TextStyle(color: Colors.white)),
)
ArkUI 等效:
if (item.unread > 0) {
Text(formatCount(item.unread))
.fontColor(Color.White).fontSize(11)
.fontWeight(FontWeight.Medium).textAlign(TextAlign.Center)
.backgroundColor('#FF3B30')
.borderRadius(9)
.width(badgeWidth(item.unread)).height(18)
.padding({ left: 5, right: 5 })
}
关键点:
borderRadius(9)为高度 18px 的一半,确保正圆或胶囊形if (item.unread > 0)条件渲染,零值时角标不参与布局
6.3 列表项整体布局
ListItem() {
Row() {
Row() { // 左侧:头像 + 文本
Stack() { Circle() + Text() } // 圆形头像
Column() { // 文本列
Row() { Text + Text } // 发送者 + 时间
Text(content) // 摘要
}
}
if (item.unread > 0) // 右侧:角标
Text(formatCount(item.unread)) ...
}
.alignItems(VerticalAlign.Center)
.padding({ top: 12, bottom: 12, left: 16, right: 16 })
}
6.4 列表分隔线
List().divider({
strokeWidth: 0.5, color: '#F0F0F0',
startMargin: 72, endMargin: 0
})
使用 List.divider 统一管理,startMargin: 72 从头像右侧开始绘制。
七、交互逻辑实现
7.1 单项点击 — 清除未读
.onClick(() => {
if (index !== undefined) {
this.messages[index].unread = 0;
this.messages = this.messages.slice(); // 触发 @State 刷新
}
})
为什么需要 slice()?@State 通过 引用比较 检测数组变化。直接修改元素属性不会改变数组引用,slice() 创建新数组重新赋值后框架才能正确触发 UI 刷新。
7.2 批量操作 — 全部标记已读
this.messages = this.messages.map((item: MessageItem): MessageItem => ({
sender: item.sender, content: item.content,
time: item.time, unread: 0
}));
map 返回新数组,引用变化触发更新。注意 ArkTS 严格模式限制:不能使用展开运算符(...item),必须显式声明每个字段。
7.3 列表键值优化
ForEach(this.messages, (item, index) => { ... },
(item: MessageItem) => item.sender + item.time)
ForEach 第三个参数为键值生成器,帮助框架对比元素变化,避免重渲染整个列表。
八、ArkTS 严格模式常见问题
8.1 对象字面量类型推断
// 错误
.textOverflow({ overflow: TextOverflow.Ellipsis })
// 正确
.textOverflow({ overflow: TextOverflow.Ellipsis } as TextOverflowOptions)
8.2 展开运算符限制
// 错误
this.messages = [...this.messages];
// 正确
this.messages = this.messages.slice();
8.3 Color 类静态方法
// 错误:Color.fromArgb(255, 52, 120, 246)
// 正确:使用字符串 '#3478F6'
8.4 overlay 方法签名
// 错误:Circle().overlay({ builder: () => Text('A') })
// 正确:Stack() { Circle() ...; Text('A') ...; }
九、性能优化
9.1 列表懒加载
List 组件只渲染可视区域内的 ListItem,滚动时动态回收和创建节点,上千条数据也不会一次性创建所有 UI 节点。
9.2 增量更新
@State 更新时框架会:
- 对比新旧数组引用
- 遍历键值列表识别变化元素
- 仅对变化项重新构建
- 在
ListItem内部进一步对比子组件树,最小化实际更新
十、最佳实践与扩展
10.1 组件拆分建议
BadgeList/
├── BadgeList.ets ← 页面容器
├── MessageItem.ets ← 单项组件
├── BadgeWidget.ets ← 可复用的角标组件
├── CircleAvatar.ets ← 可复用的圆形头像
└── MessageModel.ets ← 数据模型
10.2 角标组件复用
@Component
export struct BadgeWidget {
@Prop count: number = 0;
build() {
if (this.count > 0) {
Text(formatCount(this.count))
.fontColor(Color.White).fontSize(11)
.backgroundColor('#FF3B30').borderRadius(9)
.width(badgeWidth(this.count)).height(18)
.padding({ left: 5, right: 5 })
}
}
}
10.3 动画增强
Text(formatCount(item.unread))
.transition(TransitionEffect.scale({})
.combine(TransitionEffect.opacity({})))
.animation({ duration: 200, curve: Curve.EaseInOut })
10.4 主题色 & 无障碍
@Prop accentColor: string = '#FF3B30';
.accessibilityText(`${item.unread} 条未读消息`)
十一、Flutter 开发者迁移对照
| Flutter | ArkUI |
|---|---|
setState() |
@State + 变量重新赋值 |
StatelessWidget |
无 @State 的 @Component struct |
StatefulWidget |
有 @State 的 @Component struct |
Container |
Container 或 Stack |
EdgeInsets.all(16) |
{ top:16, bottom:16, left:16, right:16 } |
BorderRadius.circular(9) |
borderRadius(9) |
ListView.builder() |
List() { ForEach() { ListItem() } } |
GestureDetector.onTap |
.onClick() |
十二、总结
本文实现了一个鸿蒙 ArkUI 下的 带红色圆形角标的列表项组件,涵盖以下核心知识点:
- UI 布局:
List+ListItem+Row+Column+Stack组合 - 圆形角标:
Text+backgroundColor+borderRadius轻量级实现 - 状态管理:
@State装饰器原理与数组更新正确姿势 - 条件渲染:
if语句实现角标零值隐藏 - ArkTS 严格模式:常见编译错误及解决方案
- 性能优化:列表懒加载与增量更新机制
最终效果
┌───────────────────────────────────────────────┐
│ 消息中心 │
├───────────────────────────────────────────────┤
│ [蓝] 张工 09:45 [3] │
│ 项目进度更新:鸿蒙模块已完成测试… │
├───────────────────────────────────────────────┤
│ [橙] 系统通知 昨天 [12] │
│ 您的账号安全验证已通过… │
├───────────────────────────────────────────────┤
│ [绿] 团队邮件 昨天 [1] │
│ 本周五下午3点召开Q2技术评审会议… │
├───────────────────────────────────────────────┤
│ [天蓝] Jenkins CI 周二 [99+] │
│ 主分支构建失败:单元测试覆盖率未达标 │
├───────────────────────────────────────────────┤
│ 全部标记为已读 │
└───────────────────────────────────────────────┘
适用场景
邮件客户端、即时通讯、系统通知中心、社交应用、工作协同(Jira/GitHub 通知)等。
角标虽小,但作为用户与信息之间的第一道桥梁,其设计质量直接影响着应用的用户体验。希望本文能为鸿蒙开发者提供有价值的参考。
项目地址:entry/src/main/ets/pages/BadgeList.ets
技术栈:HarmonyOS ArkUI + ArkTS + hvigor
更多推荐




所有评论(0)