ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

仓颉 ArkUI ListItemGroup 分组列表组件完全指南:从 API 详解到吸顶吸底实战

仓颉 ArkUI ListItemGroup 分组列表组件完全指南:从 API 详解到吸顶吸底实战
  • 文档
  • 知识库
  • 教程
  • OpenHarmony

【免费下载链接】CangjieCorpus

本仓库为仓颉编程语言语料库,聚焦 仓颉编程语言 核心知识体系,整合官方开发指南、API 文档、典型代码示例及语法规范等多维度资源,经结构化处理与专业标注,构建为支持 RAG(检索增强生成)技术 的高质量知识基座,适配智能编码助手、学习辅助工具等应用开发需求。 欢迎开发者基于本语料库构建仓颉语言智能应用,推动鸿蒙开发生态智能化升级!

项目地址:https://gitcode.com/Cangjie/CangjieCorpus
点击查看免费下载

分组列表(如通讯录按字母分组、课程表按星期分组、设置页按功能区块分组)是 ArkUI 应用中最常见的高频交互形态之一。本篇指南以仓颉(Cangjie)编程语言的 ArkUI 声明式框架为核心,系统讲解 ListItemGroup 组件的初始化方式、header/footer/space/style 各参数语义、分割线(divider)配置、卡片样式(ListItemGroupStyle)以及其与 List 组件的配合规则,并结合仓库中的官方示例代码,带你完整掌握吸顶(吸底)粘性标题、多分组循环渲染、折叠展开等实战方案。

组件定位:ListItemGroup 是什么

ListItemGroup 是 ArkUI 中用来展示列表 item 分组的容器组件,它把若干个逻辑上相关的 ListItem 组织为一个可滚动、可定位的分组单元。其宽度默认充满父级 List 组件,并且必须配合 List 组件使用。

从组件关系上看,三者构成清晰的层次结构:List 作为最外层容器,直接承载 ListItemGroup 或 ListItem;ListItemGroup 内部再承载 ListItem,从而构建"二维列表"。这一结构在仓库的官方指南 cj-layout-development-create-list.md 中被明确描述为"在 List 组件中使用 ListItemGroup 对项目进行分组,可以构建二维列表",典型场景包括联系人列表、课程表、设置页分组等。

使用 ListItemGroup 的核心收益在于:

  • 结构清晰:头部(header)/ 尾部(footer)可承载分组标题、统计信息等,帮助用户在大量数据中快速定位;
  • 粘性定位:配合 List 的sticky属性,可以实现分组标题吸顶、分组尾部吸底的效果;
  • 数据组织:多个结构相似的分组可交由 ForEach 循环渲染,代码高度复用。

使用前提与关键约束

ListItemGroup 是一个约束较强的容器组件,使用时必须注意以下规则(均出自官方 API 文档 cj-scroll-swipe-listgroup.md 的说明):

  1. 父组件只能是 List。它不能脱离 List 单独存在,也不能放入 Column、Row 等其他容器;
  2. 不支持设置通用属性 aspectRatio(宽高比),该属性在 ListItemGroup 上不生效,详见 通用尺寸属性;
  3. 尺寸由内容决定:
    • 当父组件 List 的listDirection为Axis.Vertical(垂直方向)时,设置通用属性height不生效。此时 ListItemGroup 的高度 = header 高度 + footer 高度 + 所有 ListItem 布局后的总高度之和;
    • 当listDirection为Axis.Horizontal(水平方向)时,设置通用属性width不生效。此时宽度 = header 宽度 + footer 宽度 + 所有 ListItem 布局后的总宽度之和;
  4. 内部 ListItem 不支持编辑、拖拽,即 ListItem 的editable属性在 ListItemGroup 内部不生效;
  5. 布局方向跟随父容器:ListItemGroup 使用direction属性设置自身布局方向不生效,其布局方向始终跟随 List 组件的布局方向。

理解这些约束有助于避免"设置了 width/height 却看不到效果"这类典型问题。此外,在 List 组件文档 中还有一个与分组相关的索引计算规则:ListItemGroup 作为一个整体计算一个索引值,ListItemGroup 内部的 ListItem 不计算索引值。这意味着onScrollIndex、scrollToIndex等以索引为单位的操作,会把一个分组视为一个单元。

导入模块

ListItemGroup 属于 ArkUI 声明式组件,使用前需导入 ArkUI 模块:

import kit.ArkUI.*

子组件

ListItemGroup 的子组件仅支持 ListItem。这与 List 组件"仅支持 ListItem、ListItemGroup 子组件"的约束相呼应——List 负责承上,ListItemGroup 负责分组,ListItem 负责具体条目。

创建组件:init 方法详解

init(?CustomBuilder, ?CustomBuilder, ?Length, ?ListItemGroupStyle, () -> Unit)

创建 ListItemGroup 组件的完整签名如下:

public init( header!: ?CustomBuilder = None, footer!: ?CustomBuilder = None, space!: ?Length = None, style!: ?ListItemGroupStyle = Option.None, child!: () -> Unit )
  • 系统能力:SystemCapability.ArkUI.ArkUI.Full
  • 起始版本:22

各参数语义如下表:

参数名类型必填默认值说明
header?CustomBuilder否None命名参数。设置 ListItemGroup 头部组件,通常承载分组标题。
footer?CustomBuilder否None命名参数。设置 ListItemGroup 尾部组件,通常承载分组统计信息。
space?Length否None命名参数。列表项间距。只作用于 ListItem 与 ListItem 之间,不作用于 header 与 ListItem、footer 与 ListItem 之间。
style?ListItemGroupStyle否Option.None命名参数。设置列表卡片样式。
child() -> Unit是-声明容器子组件(即各 ListItem)。

其中header、footer的类型CustomBuilder本质是一个() -> Unit的构建函数(在 cj-common-types.md 中定义为public type CustomBuilder = () -> Unit),因此可以用{ => ... }构建函数语法传入;当头部组件需要携带数据时,可使用bind(this.itemHead, this)(title)形式将参数绑定到 @Builder 方法上。space的类型Length是一个接口,Float64、Int64、AppResource均实现了该接口,因此可直接传入数值或带单位的值(如10、8.vp)。

一个最小化的分组创建示意(来自仓库开发指南 cj-layout-development-create-list.md):

List() { ListItemGroup(header: {=> bind(this.itemHead, this)("a")}) { => // 循环渲染分组A的ListItem } ListItemGroup(header: {=> bind(this.itemHead, this)("b")}) { => // 循环渲染分组B的ListItem } }

通用属性与通用事件

  • 通用属性:全部支持,唯一的例外是不支持设置通用属性aspectRatio(见 尺寸属性文档 中的 aspectRatio 说明)。
  • 通用事件:全部支持。

组件属性:divider 分割线

func divider(Option<ListDividerOptions>)

public func divider(value: Option<ListDividerOptions>): This
  • 功能:设置 ListItem 分割线样式,默认无分割线。
  • 系统能力:SystemCapability.ArkUI.ArkUI.Full
  • 起始版本:22
参数名类型必填默认值说明
valueOption<ListDividerOptions>是-ListItem 分割线样式。设置为Option.None时表示无分割线。

需要特别注意的是:strokeWidth、startMargin 和 endMargin 不支持设置百分比。分割线也可以设置在 List 层(List 的divider属性),二者均复用ListDividerOptions类型;分组级 divider 与列表级 divider 的差异在于作用范围——前者作用于分组内部的 ListItem 之间,后者作用于整个列表的列表项之间。

基础类型定义

class ListDividerOptions

public class ListDividerOptions { public var strokeWidth: ?Length public var color: ?ResourceColor public var startMargin: ?Length public var endMargin: ?Length public init( strokeWidth!: ?Length, color!: ?ResourceColor = None, startMargin!: ?Length = None, endMargin!: ?Length = None ) }
  • 功能:定义 ListItem 分割线样式。
  • 系统能力:SystemCapability.ArkUI.ArkUI.Full
  • 起始版本:22

各成员说明:

成员类型读写能力说明
strokeWidth?Length可读写分割线的线宽(必填参数)。
color?ResourceColor可读写分割线的颜色,默认 None。
startMargin?Length可读写分割线距离列表侧边起始端的距离,默认 None。
endMargin?Length可读写分割线距离列表侧边结束端的距离,默认 None。

构造方法init(?Length, ?ResourceColor, ?Length, ?Length)中strokeWidth必填,其余三个参数均为可选的命名参数。典型用法(来自联系人分组示例):

ListItemGroup(header: { => bind(this.itemHead, this)(itemGroup.title)}) { // ... } .divider(ListDividerOptions(strokeWidth: 1, color: Color(0X08000000), startMargin: 48, endMargin: 48))

枚举 ListItemGroupStyle(卡片样式)

public enum ListItemGroupStyle <: Equatable<ListItemGroupStyle> { | None | Card | ... }
  • 功能:设置 List 组件卡片样式。
  • 父类型:Equatable<ListItemGroupStyle>
枚举值功能
None无样式。
Card显示默认卡片样式。

该枚举实现了==/!=运算符重载,可对两个枚举值进行相等性比较:

public operator func ==(other: ListItemGroupStyle): Bool public operator func !=(other: ListItemGroupStyle): Bool

两者均接收一个ListItemGroupStyle参数,返回Bool:相等返回 true(不相等返回 false),反之亦然。Card样式常用于"信息清单"类分组,例如设置页中每个功能区块以卡片呈现,具体用法可参考仓库中"折叠与展开"示例(见下文实战章节)。

实战一:通过 sticky 实现吸顶 / 吸底

原文档示例使用ListItemGroup构建了一张课程表:按星期(Monday ~ Thursday)分组,每个分组内列出当天课程。其核心是利用 List 的sticky属性(值为 StickyStyle)实现 Header 吸顶和 Footer 吸底:

package ohos_app_cangjie_entry import kit.ArkUI.* import ohos.arkui.state_macro_manage.* class TimeTable { let title: String let projects: Array<String> public init(title:String,projects: Array<String>){ this.title = title this.projects = projects } } @Entry @Component class EntryView { let timeTable = [ TimeTable("Monday", ["Chinese", "Math", "English"]), TimeTable("Tuesday", ["Physics", "Chemistry", "Biology"]), TimeTable("Wednesday", ["History", "Geography", "Politics"]), TimeTable("Thursday", ["Art", "Music", "PE"])] @Builder func itemHead(text:String) { Text(text) .fontSize(20) .backgroundColor(0xAABBCC) .width(100.percent) .padding(10) } @Builder func itemFoot(num:Int64) { Text("Total ${num} classes") .fontSize(16) .backgroundColor(0xAABBCC) .width(100.percent) .padding(5) } func build() { Column() { List(space: 20) { ForEach(this.timeTable, itemGeneratorFunc: {item:TimeTable ,_:Int64 => ListItemGroup(header:{=>bind(this.itemHead,this)(item.title)},footer:{=>bind(this.itemFoot,this)(item.projects.size)}){ ForEach(item.projects,itemGeneratorFunc: {project:String,_:Int64=> ListItem(){ Text(project) .width(100.percent) .height(100) .fontSize(20) .textAlign(TextAlign.Center) .backgroundColor(0xFFFFFF) }}) } }) } } .height(800.vp) .backgroundColor(Color(0XD3D3D3)) } }

代码要点拆解:

  1. 外层List(space: 20)设置列表项间距;ForEach遍历timeTable数组,对每个分组生成一个ListItemGroup;
  2. header: {=> bind(this.itemHead, this)(item.title)}将 @Builder 方法itemHead绑定到当前组件实例并传入title,渲染分组标题(星期名称);footer: {=> bind(this.itemFoot, this)(item.projects.size)}渲染分组尾部,显示课程总数;
  3. 分组内部再用ForEach遍历projects生成ListItem,每个条目以白底文本呈现;
  4. 由于space只作用于 ListItem 之间,header/footer 与 ListItem 之间不会插入间距,分组标题与内容天然贴合。

若要实现"吸顶/吸底",需要结合 List 的sticky属性。StickyStyle枚举(见 cj-common-types.md)提供三种取值:None(header 不吸顶、footer 不吸底)、Header(header 吸顶)、Footer(footer 吸底)。例如设置.sticky(StickyStyle.Header)后,滚动列表时当前分组的 header 会固定在屏幕顶部,直到下一个分组的 header 将其顶替。

实战二:多分组循环渲染与粘性标题(联系人列表)

当多个分组结构相似时,可把分组数据组装成数组后用 ForEach 循环渲染,代码更简洁。仓库开发指南 cj-layout-development-create-list.md 中的联系人示例展示了完整的"分组 + 吸顶 + 分割线"组合用法:

@Entry @Component public class EntryView { private var contactsGroups : Array<ContactGroup> = [ ContactGroup('A', [Contact('艾佳', @r(app.media.startIcon)),Contact('安安', @r(app.media.startIcon)),Contact('Angela', @r(app.media.startIcon))]), ContactGroup('B', [Contact('白叶', @r(app.media.startIcon)),Contact('伯明', @r(app.media.startIcon))]) ] @Builder func itemHead(text: String) { // 列表分组的头部组件,对应联系人分组A、B等位置的组件 Text(text) .fontSize(20) .backgroundColor(0xfff1f3f5) .width(100.percent) .padding(5) } func build() { List() { // 循环渲染ListItemGroup,contactsGroups为多个分组联系人contacts和标题title的数据集合 ForEach(this.contactsGroups, itemGeneratorFunc: { itemGroup: ContactGroup, _: Int64 => ListItemGroup(header: { => bind(this.itemHead, this)(itemGroup.title)}) { this.footertest(itemGroup) } .divider(ListDividerOptions(strokeWidth: 1, color: Color(0X08000000), startMargin: 48, endMargin: 48)) }, keyGeneratorFunc: {item: ContactGroup, idx: Int64 => idx.toString()} ) } .backgroundColor(Color(0X08000000)) .divider(ListDividerOptions(strokeWidth: 1, color: Color(0X08000000), startMargin: 48, endMargin: 48)) .sticky(StickyStyle.Header) // 设置吸顶,实现粘性标题效果 } }

该示例同时展示了三个层次的配置技巧:

  • 分组级分割线:.divider(...)挂在 ListItemGroup 上,作用于组内 ListItem 之间,startMargin: 48, endMargin: 48使分割线左右留白,视觉上不贯穿整行;
  • 列表级分割线:List 也设置了同样的divider,用于分组之间的分隔;
  • 粘性标题:.sticky(StickyStyle.Header)让分组标题在滚动时吸顶,形成"粘性标题"效果——这在字母索引类列表中极大方便用户在大量信息中定位。

若需要吸底效果,可通过footer参数初始化 ListItemGroup 的底部组件,并将sticky设置为StickyStyle.Footer。StickyStyle中Header与Footer可以组合使用,同时实现"头吸顶、尾吸底"。

实战三:折叠与展开的分组卡片

ListItemGroup 也常用于"信息清单"折叠展开场景。仓库开发指南 cj-layout-development-create-list.md 的"折叠与展开"章节展示了ListItemGroupStyle.Card的实际用法:

ListItemGroup(header: {=> bind(this.ListItemGroupHeader, this)(itemGroup)}, footer: {=>}, space: 0, style: ListItemGroupStyle.Card) { if (this.expandedItems[itemGroup.index] == 180.0) { ForEach(itemGroup.children, itemGeneratorFunc: { item: ItemInfo, _: Int64 => ListItem() { Row() { Text(item.name) Blank() // ... 图标与箭头 } .width(100.percent) }.width(100.percent) }) } }

其实现思路是:

  1. 定义带children字段的分组数据结构(ItemGroupInfo <: ItemInfo);
  2. 用@State维护每个分组的展开状态(如旋转角度数组expandedItems);
  3. 在ListItemGroup的 header 上通过onClick改变展开状态,并通过animation/animateTo为箭头旋转和内容出现添加动效;
  4. 组内if条件成立时才渲染ForEach中的 ListItem,实现内容的折叠与展开。

style: ListItemGroupStyle.Card在此场景下为每个分组提供默认卡片外观,使分组间边界清晰,视觉上与设置页、隐私清单等"信息清单"型页面高度契合。

实战四:分组列表的懒加载与缓存优化

当分组数量庞大(如超长通讯录)时,建议使用数据懒加载(LazyForEach)替代 ForEach 一次性渲染,以降低启动耗时。List 的cachedCount属性可配合设置列表项缓存数量,减少滑动白块。针对分组列表,官方指南 cj-layout-development-create-list.md 明确了两条缓存规则:

  • 若懒加载用于ListItem:单列模式下在显示区前后各缓存cachedCount个 ListItem;多列模式下各缓存cachedCount × 列数个 ListItem;
  • 若懒加载用于ListItemGroup:无论单列还是多列模式,都在 List 显示的 ListItemGroup 前后各缓存cachedCount个ListItemGroup。

使用示例:

List() { // ... }.cachedCount(3)

需要注意cachedCount的增加会同步增大 UI 的 CPU、内存开销,应根据实际列表长度与设备性能综合取舍。同时,使用懒加载时,除显示区域及前后缓存的列表项外,其余列表项会被销毁,因此不要在列表项中持有跨组共享的外部状态。相关数据懒加载的详细说明可参考 cj-rendering-control-lazyforeach.md,ForEach 循环渲染的用法可参考 cj-rendering-control-foreach.md。

总结

ListItemGroup 是 ArkUI 中构建分组列表的核心组件,与 List、ListItem 共同构成"容器—分组—条目"三层结构。掌握它需要抓住几个关键点:

  • 强约束:父组件只能是 List,尺寸跟随内容与父容器方向,不支持 aspectRatio,组内 ListItem 不支持编辑拖拽;
  • 参数语义:header/footer承载分组首尾,space只作用于 ListItem 之间,style控制卡片外观;
  • 分割线:通过divider(ListDividerOptions(...))精细控制线宽、颜色与左右留白(不支持百分比);
  • 吸顶吸底:借助 List 的sticky(StickyStyle.Header/Footer)与 header/footer 组合实现粘性标题;
  • 性能优化:大数据量分组列表优先 LazyForEach +cachedCount,并对 ListItemGroup 以组为单位缓存。

将上述 API 与实战模式组合使用,即可高效实现通讯录、课程表、设置页、折叠信息清单等绝大多数分组列表界面。更多关联内容可继续阅读 List 组件文档、ListItem 组件文档 以及 列表开发总览。

  • 文档
  • 知识库
  • 教程
  • OpenHarmony

【免费下载链接】CangjieCorpus

本仓库为仓颉编程语言语料库,聚焦 仓颉编程语言 核心知识体系,整合官方开发指南、API 文档、典型代码示例及语法规范等多维度资源,经结构化处理与专业标注,构建为支持 RAG(检索增强生成)技术 的高质量知识基座,适配智能编码助手、学习辅助工具等应用开发需求。 欢迎开发者基于本语料库构建仓颉语言智能应用,推动鸿蒙开发生态智能化升级!

项目地址:https://gitcode.com/Cangjie/CangjieCorpus
点击查看免费下载

相关推荐

上一篇:2026年iOS越狱终极指南:从新手到专家的完整教程
下一篇:TranslucentTB启动失败终极指南:深度解析Microsoft.UI.Xaml.2.8缺失问题与专业解决方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进