在这里插入图片描述

鸿蒙 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 更新时框架会:

  1. 对比新旧数组引用
  2. 遍历键值列表识别变化元素
  3. 仅对变化项重新构建
  4. 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 ContainerStack
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 下的 带红色圆形角标的列表项组件,涵盖以下核心知识点:

  1. UI 布局List + ListItem + Row + Column + Stack 组合
  2. 圆形角标Text + backgroundColor + borderRadius 轻量级实现
  3. 状态管理@State 装饰器原理与数组更新正确姿势
  4. 条件渲染if 语句实现角标零值隐藏
  5. ArkTS 严格模式:常见编译错误及解决方案
  6. 性能优化:列表懒加载与增量更新机制

最终效果

┌───────────────────────────────────────────────┐
│  消息中心                                      │
├───────────────────────────────────────────────┤
│  [蓝] 张工                  09:45      [3]    │
│       项目进度更新:鸿蒙模块已完成测试…         │
├───────────────────────────────────────────────┤
│  [橙] 系统通知               昨天     [12]    │
│       您的账号安全验证已通过…                   │
├───────────────────────────────────────────────┤
│  [绿] 团队邮件               昨天      [1]    │
│       本周五下午3点召开Q2技术评审会议…          │
├───────────────────────────────────────────────┤
│  [天蓝] Jenkins CI           周二    [99+]   │
│        主分支构建失败:单元测试覆盖率未达标      │
├───────────────────────────────────────────────┤
│             全部标记为已读                      │
└───────────────────────────────────────────────┘

适用场景

邮件客户端、即时通讯、系统通知中心、社交应用、工作协同(Jira/GitHub 通知)等。

角标虽小,但作为用户与信息之间的第一道桥梁,其设计质量直接影响着应用的用户体验。希望本文能为鸿蒙开发者提供有价值的参考。


项目地址entry/src/main/ets/pages/BadgeList.ets

技术栈:HarmonyOS ArkUI + ArkTS + hvigor

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐