鸿蒙原生ArkTS布局之ListItemGroup分组列表通讯录实现

发布时间:2026/6/26 15:51:27
鸿蒙原生ArkTS布局之ListItemGroup分组列表通讯录实现 鸿蒙原生 ArkTS 布局方式之 List 分组Group带标题的分组列表 —— 通讯录风格实现详解一、引言在移动端应用开发中分组列表Section List是最常见的 UI 模式之一。从通讯录到商品分类页从设置菜单到好友列表分组列表几乎无处不在。其核心特征是将数据按首字母、时间或类别等维度分组展示每一组拥有一个可吸顶的标题头帮助用户快速定位。HarmonyOS NEXT 的原生 UI 框架ArkUI提供了声明式的 ArkTS 语言布局体系。其中List容器配合ListItemGroup子容器构成了实现分组列表的标准方案官方将其归纳为「List 分组Group带标题的分组列表」是 ArkTS 六大基础布局之一。本文将围绕一个通讯录风格的分组列表示例从数据模型、界面布局到完整代码逐层剖析这一布局的实现原理。二、HarmonyOS NEXT 与 ArkTS 布局体系概述2.1 从 Java 到 ArkTS 的演进HarmonyOS 的应用开发语言经历了从 Java/JS 双框架1.0–2.0到 eTS3.0再到 HarmonyOS NEXTAPI 11全面采用ArkTS的演进。ArkTS 是 TypeScript 的超集增加了Component / Entry / Builder / State等装饰器体系实现声明式 数据驱动的 UI 开发范式。2.2 六大基础布局方式ArkUI 官方归纳六种核心布局覆盖绝大多数应用场景线性布局Row/Column、层叠布局Stack、弹性布局Flex、相对布局RelativeContainer、滚动布局Scroll、列表布局List和网格布局Grid/GridItem。本文聚焦的正是列表布局的高阶用法——带标题的分组列表ListItemGroup。2.3 为什么选择 ListItemGroup在 iOS 开发中UITableView通过section概念天然支持分组在 Android 中RecyclerView配合ItemDecoration可以实现分组效果。而在 ArkUI 中ListItemGroup就是实现分组语义的第一等公民。与直接使用多个List拼接或用Divider手动模拟分组相比ListItemGroup的优势语义明确代码结构天然反映List ListItemGroup ListItem层级标题吸顶通过sticky属性一键开启无需手动计算滚动偏移性能优化继承List的懒加载能力不会一次性渲染所有分组交互丰富支持侧滑删除swipeAction、拖拽排序等高级交互。三、示例应用概览3.1 最终效果模拟手机通讯录顶部标题栏「通讯录」列表按首字母分组★ 置顶 A~Z每组上方有灰色标题栏滚动时标题吸顶固定。每个联系人条目包含圆形头像首字随机色、姓名、手机号、拨号图标。点击联系人弹出 Toast 提示。3.2 数据模型interfaceContactInfo{name:string;phone:string;}interfaceGroupData{title:string;contacts:ContactInfo[];}整个列表的数据源是一个GroupData[]数组。本示例定义了10 个分组、28 个联系人涵盖 ★置顶、A、B、C、D、G、H、L、W、Z 等字母模拟真实通讯录中「并非每个字母都有联系人」的情况。3.3 组件树结构Column ← 页面根容器 ├── Row ← 顶部标题栏 └── List ← 滚动列表sticky header ├── ListItemGroup (A) ← 分组 Aheader: A │ ├── ListItem ← 联系人「阿杰」 │ ├── ListItem ← 联系人「艾米」 │ └── ListItem ← 联系人「安琪拉」 ├── ListItemGroup (B) ← 分组 B └── ListItemGroup (Z) ← 分组 Z这个层级清晰映射了「列表 → 多个分组 → 每个分组下多个条目」的数据关系。四、核心代码逐段解析4.1 导入语句与数据接口import{promptAction}fromkit.ArkUI;interfaceContactInfo{name:string;phone:string;}interfaceGroupData{title:string;contacts:ContactInfo[];}kit.ArkUI是 HarmonyOS NEXT 的统一 SDK 包入口替代了旧版本的多个分散导入如ohos.promptAction。promptAction提供了 Toast 弹窗等轻量交互能力。ContactInfo和GroupData两个接口是数据驱动的基石。在真实工程中这些接口通常由后端 API 返回的数据结构定义或由数据库 ORM 映射而来。4.2 State 数据源与 Builder 头像StategroupDataList:GroupData[][/* 10 个分组、28 个联系人 */];BuildercontactAvatar(name:string){Text(name.charAt(0)).fontSize(18).fontColor(Color.White).fontWeight(FontWeight.Bold).width(44).height(44).borderRadius(22).backgroundColor(this.getAvatarColor(name)).textAlign(TextAlign.Center)}State装饰器是 ArkTS 状态管理的核心——被State修饰的变量变化时框架自动重渲染相关组件。Builder装饰的方法是一个可复用的 UI 片段构建器支持参数传递。此处构建了 44×44 的圆形头像首字白色粗体居中背景色根据姓名 Unicode 哈希取模从 10 种预设颜色中选取。4.3 Build 方法核心布局逻辑build(){Column(){Row(){Text(通讯录)...}// 顶部标题List({space:0}){ForEach(this.groupDataList,(groupItem){ListItemGroup({header:this.groupHeaderBuilder(groupItem.title)}){ForEach(groupItem.contacts,(contact){ListItem(){Row(){this.contactAvatar(contact.name)Column(){Text(contact.name)...Text(contact.phone)...}Image($r(app.media.startIcon))...}}})}})}.sticky(StickyStyle.Header)// 吸顶效果}}核心是双重 ForEach 循环外层遍历groupDataList生成ListItemGroup内层遍历groupItem.contacts生成ListItem。ForEach的第三个参数是键值生成函数用于框架追踪列表项变化。4.4 吸顶属性与交互List({space:0}).sticky(StickyStyle.Header)StickyStyle枚举有三个值None不吸顶、Header标题吸顶、Footer底部吸底。设置Header后ArkUI 自动处理所有ListItemGroupheader 的位置计算。点击交互使用promptAction.showToast实现 Toast 提示onContactClick(contact:ContactInfo):void{promptAction.showToast({message:拨打电话${contact.name}(${contact.phone}),duration:2000});}五、ListItemGroup 的关键属性详解5.1 header 属性header是ListItemGroup最重要的属性它接收一个Builder或自定义构建器用于渲染分组的标题头。ListItemGroup({header:this.groupHeaderBuilder(groupItem.title)}){// 分组内的列表项}groupHeaderBuilder的实现36px 高、浅灰背景的文字区域5.2 divider 属性.divider({strokeWidth:0,startMargin:0,endMargin:0})divider控制分组之间的分隔线样式。本示例中隐藏了组间分割线而每个ListItem内部通过.border设置 0.5 像素的底部分割线形成「组内条目有分割线、组间无额外分割线」的效果。5.3 children插槽内容ListItemGroup的花括号内容只能放置ListItem组件或ForEach/LazyForEach生成的ListItem。六、BorderOptions 在 API 24 中的正确使用在 API 24 中BorderOptions接口的定义发生了变化——不能再将bottom、left等边属性直接放在BorderOptions顶层而需嵌套在width和color内部// ❌ 编译错误bottom does not exist in type BorderOptions.border({bottom:{width:0.5,color:#E8E8E8}})// ✅ 正确写法.border({width:{bottom:0.5},color:{bottom:#E8E8E8}})BorderOptions的类型定义简化interfaceBorderOptions{width?:EdgeWidths|Dimension;// EdgeWidths { left?, right?, top?, bottom? }color?:EdgeColors|ResourceColor;// EdgeColors { left?, right?, top?, bottom? }style?:EdgeStyles|BorderStyle;radius?:BorderRadiuses|Dimension;}设置单边边框用{ width: { bottom: 1 }, color: { bottom: #ccc } }设置四边统一边框用{ width: 1, color: #ccc }设置各边不同边框用{ width: { left: 1, right: 2 }, color: { left: red, right: blue } }。七、性能考量ForEach 与 LazyForEach本示例使用ForEach它会一次性渲染所有 ListItem。数据量较少时没有问题。当数据量达数百或数千时应使用LazyForEach替代List(){LazyForEach(this.dataSource,(item:GroupData){ListItemGroup({header:...}){LazyForEach(item.contacts,(contact:ContactInfo){ListItem(){...}},(contact)contact.phone)}},(item)item.title)}.sticky(StickyStyle.Header)LazyForEach需配合IDataSource接口使用按需创建、回收复用大幅降低内存占用。八、扩展建议字母索引条使用AlphabetIndexer组件配合ListController.scrollToIndex实现右侧字母快速跳转侧滑操作ListItem的swipeAction属性支持添加侧滑删除/置顶菜单数据持久化实际工程中建议使用LazyForEach 数据源接口从RelationalStore或云端获取数据九、完整代码清单以下为ContactGroupList.ets的完整代码已包含详尽的中文注释完整文件位于entry/src/main/ets/pages/ContactGroupList.ets。import{promptAction}fromkit.ArkUI;interfaceContactInfo{name:string;phone:string;}interfaceGroupData{title:string;contacts:ContactInfo[];}EntryComponentstruct ContactGroupList{StategroupDataList:GroupData[][{title:★,contacts:[{name:张三,phone:138****1234},{name:李四,phone:139****5678}]},{title:A,contacts:[{name:阿杰,phone:136****1111},{name:艾米,phone:137****2222},{name:安琪拉,phone:135****3333}]},{title:B,contacts:[{name:白杨,phone:150****4444},{name:本杰明,phone:151****5555}]},{title:C,contacts:[{name:陈晨,phone:152****6666},{name:程菲,phone:153****7777},{name:柴远,phone:155****8888}]},{title:D,contacts:[{name:邓超,phone:156****9999},{name:董洁,phone:157****0000}]},{title:G,contacts:[{name:高天,phone:158****1111},{name:郭靖,phone:159****2222}]},{title:H,contacts:[{name:韩梅梅,phone:170****3333},{name:何炅,phone:171****4444}]},{title:L,contacts:[{name:李华,phone:172****5555},{name:刘洋,phone:173****6666},{name:林娜,phone:174****7777}]},{title:W,contacts:[{name:王伟,phone:175****8888},{name:吴芳,phone:176****9999},{name:魏明,phone:177****0000}]},{title:Z,contacts:[{name:张宇,phone:178****1111},{name:赵敏,phone:179****2222},{name:周杰,phone:180****3333}]}];onContactClick(contact:ContactInfo):void{promptAction.showToast({message:拨打电话${contact.name}(${contact.phone}),duration:2000});}BuildercontactAvatar(name:string){Text(name.charAt(0)).fontSize(18).fontColor(Color.White).fontWeight(FontWeight.Bold).width(44).height(44).borderRadius(22).backgroundColor(this.getAvatarColor(name)).textAlign(TextAlign.Center)}getAvatarColor(name:string):ResourceColor{constcolors:ResourceColor[][#FF6B81,#5B8FF9,#F6BD16,#E8684A,#2FC25B,#9F7EEA,#F4606C,#20B2AA,#FF7F50,#9370DB];letindex0;for(leti0;iname.length;i)index(indexname.charCodeAt(i))%colors.length;returncolors[index];}build(){Column(){Row(){Text(通讯录).fontSize(24).fontWeight(FontWeight.Bold).fontColor(#333333)}.width(100%).padding({left:16,top:12,bottom:8}).backgroundColor(#F7F7F7)List({space:0}){ForEach(this.groupDataList,(groupItem:GroupData){ListItemGroup({header:this.groupHeaderBuilder(groupItem.title)}){ForEach(groupItem.contacts,(contact:ContactInfo){ListItem(){Row(){this.contactAvatar(contact.name)Column(){Text(contact.name).fontSize(16).fontColor(#333333).fontWeight(FontWeight.Medium)Text(contact.phone).fontSize(13).fontColor(#999999).margin({top:3})}.alignItems(HorizontalAlign.Start).layoutWeight(1).margin({left:12})Image($r(app.media.startIcon)).width(24).height(24).objectFit(ImageFit.Contain).opacity(0.4)}.width(100%).height(64).padding({left:16,right:16}).alignItems(VerticalAlign.Center).onClick((){this.onContactClick(contact);})}.border({width:{bottom:0.5},color:{bottom:#E8E8E8}})},(contact:ContactInfo)contact.phone)}.divider({strokeWidth:0,startMargin:0,endMargin:0})},(groupItem:GroupData)groupItem.title)}.sticky(StickyStyle.Header).width(100%).height(100%).backgroundColor(#FFFFFF).edgeEffect(EdgeEffect.Spring)}.width(100%).height(100%).backgroundColor(#F7F7F7)}BuildergroupHeaderBuilder(title:string){Text(title).fontSize(15).fontColor(#666666).fontWeight(FontWeight.Bold).width(100%).height(36).padding({left:16}).backgroundColor(#F0F0F0).textAlign(TextAlign.Start)}}十、总结本文围绕 HarmonyOS NEXT 的List 分组ListItemGroup布局方式以一个完整的通讯录风格应用为例介绍了数据模型设计用ContactInfo和GroupData两层接口组织分组数据组件层级结构List → ListItemGroup → ListItem的三层树形关系核心属性用法header分组标题、sticky吸顶、divider分组分隔线交互与反馈onClick事件与showToast的组合使用API 注意事项BorderOptions在 API 24 中的类型变化性能优化LazyForEach用于大数据量分组列表。掌握ListListItemGroup组合就掌握了 HarmonyOS NEXT 应用中 80% 以上的列表页场景。从通讯录到商品分类从好友列表到信息流这种布局模式将是 ArkTS 开发工具箱中使用频率最高的利器。本文代码基于 HarmonyOS NEXT API 24 编译验证。示例工程完整源码可在项目entry/src/main/ets/pages/ContactGroupList.ets路径下找到。