一、引言:列表布局 —— 鸿蒙应用的数据展示中枢
在鸿蒙应用开发体系中,列表布局是处理结构化数据展示的核心场景。从新闻资讯的信息流、电商平台的商品陈列到任务管理的待办事项,几乎所有中大型应用都依赖高效的列表组件实现数据可视化。鸿蒙提供的 List、ListItem、ListItemGroup 三件套组件,通过标准化的接口设计与分层架构,构建了一套完整的列表解决方案。本文将系统解析这三个组件的核心机制、进阶用法与工程实践,帮助开发者掌握高性能列表开发的鸿蒙范式。
二、核心组件架构与协作机制
2.1 组件层级与职责划分
鸿蒙列表体系采用三层架构设计:
List:列表容器组件,负责整体布局控制、滚动管理与性能优化
ListItem:列表项原子单元,承载具体数据展示与交互逻辑
ListItemGroup:列表分组组件,实现数据逻辑分组与吸顶吸底效果
组件层级关系示意图:
List ├─ ListItemGroup(分组容器) │ ├─ ListItem(列表项1) │ ├─ ListItem(列表项2) ├─ ListItem(独立列表项)
2.2 核心技术优势
标准化交互模型:内置滑动删除、选中状态、编辑模式等通用交互
高性能渲染引擎:支持懒加载、预渲染与虚拟列表优化
语义化分组能力:通过 ListItemGroup 实现数据分层与视觉分组
多端自适应:自动适配手机、平板、车机等不同设备的屏幕特性
三、List 组件:列表布局的总控制器
3.1 基础接口与布局控制
// xxx.ets import { ListDataSource } from './ListDataSource'; @Entry @Component struct ListLanesExample { arr: ListDataSource = new ListDataSource([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]); @State alignListItem: ListItemAlign = ListItemAlign.Start; build() { Column() { List({ space: 20, initialIndex: 0 }) { LazyForEach(this.arr, (item: string) => { ListItem() { Text('' + item) .width('100%') .height(100) .fontSize(16) .textAlign(TextAlign.Center) .borderRadius(10) .backgroundColor(0xFFFFFF) } .border({ width: 2, color: Color.Green }) }, (item: string) => item) } .height(300) .width('90%') .friction(0.6) .border({ width: 3, color: Color.Red }) .lanes({ minLength: 40, maxLength: 40 }) .alignListItem(this.alignListItem) .scrollBar(BarState.Off) Button('点击更改alignListItem:' + this.alignListItem).onClick(() => { if (this.alignListItem == ListItemAlign.Start) { this.alignListItem = ListItemAlign.Center; } else if (this.alignListItem == ListItemAlign.Center) { this.alignListItem = ListItemAlign.End; } else { this.alignListItem = ListItemAlign.Start; } }) }.width('100%').height('100%').backgroundColor(0xDCDCDC).padding({ top: 5 }) } }
3.2 滚动事件与交互控制
// ListDataSource.ets export class ListDataSource implements IDataSource { private list: number[] = []; private listeners: DataChangeListener[] = []; constructor(list: number[]) { this.list = list; } totalCount(): number { return this.list.length; } getData(index: number): number { return this.list[index]; } registerDataChangeListener(listener: DataChangeListener): void { if (this.listeners.indexOf(listener) < 0) { this.listeners.push(listener); } } unregisterDataChangeListener(listener: DataChangeListener): void { const pos = this.listeners.indexOf(listener); if (pos >= 0) { this.listeners.splice(pos, 1); } } // 通知控制器数据删除 notifyDataDelete(index: number): void { this.listeners.forEach(listener => { listener.onDataDelete(index); }); } // 通知控制器添加数据 notifyDataAdd(index: number): void { this.listeners.forEach(listener => { listener.onDataAdd(index); }); } // 在指定索引位置删除一个元素 public deleteItem(index: number): void { this.list.splice(index, 1); this.notifyDataDelete(index); } // 在指定索引位置插入一个元素 public insertItem(index: number, data: number): void { this.list.splice(index, 0, data); this.notifyDataAdd(index); } }
// xxx.ets import { ListDataSource } from './ListDataSource'; @Entry @Component struct ListExample { private arr: ListDataSource = new ListDataSource([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); build() { Column() { List({ space: 20, initialIndex: 0 }) { LazyForEach(this.arr, (item: number) => { ListItem() { Text('' + item) .width('100%').height(100).fontSize(16) .textAlign(TextAlign.Center).borderRadius(10).backgroundColor(0xFFFFFF) } }, (item: string) => item) } .listDirection(Axis.Vertical) // 排列方向 .scrollBar(BarState.Off) .friction(0.6) .divider({ strokeWidth: 2, color: 0xFFFFFF, startMargin: 20, endMargin: 20 }) // 每行之间的分界线 .edgeEffect(EdgeEffect.Spring) // 边缘效果设置为Spring .onScrollIndex((firstIndex: number, lastIndex: number, centerIndex: number) => { console.info('first' + firstIndex); console.info('last' + lastIndex); console.info('center' + centerIndex); }) .onScrollVisibleContentChange((start: VisibleListContentInfo, end: VisibleListContentInfo) => { console.info(' start index: ' + start.index + ' start item group area: ' + start.itemGroupArea + ' start index in group: ' + start.itemIndexInGroup); console.info(' end index: ' + end.index + ' end item group area: ' + end.itemGroupArea + ' end index in group: ' + end.itemIndexInGroup); }) .onDidScroll((scrollOffset: number, scrollState: ScrollState) => { console.info(`onScroll scrollState = ScrollState` + scrollState + `, scrollOffset = ` + scrollOffset); }) .width('90%') } .width('100%') .height('100%') .backgroundColor(0xDCDCDC) .padding({ top: 5 }) } }
3.3 性能优化属性
属性名称 类型 功能描述 cachedCount number 预加载相邻项数量,默认值 5,提升滚动流畅度 itemSize number 固定列表项高度,避免动态计算布局开销 layoutWeight number 弹性布局权重,配合 ListItem 使用 useVirtualized boolean 启用虚拟列表模式,仅渲染可见区域(API 10+)
四、ListItem 组件:列表项的原子实现单元
4.1 基础结构与样式配置
// xxx.ets export class ListDataSource implements IDataSource { private list: number[] = []; constructor(list: number[]) { this.list = list; } totalCount(): number { return this.list.length; } getData(index: number): number { return this.list[index]; } registerDataChangeListener(listener: DataChangeListener): void { } unregisterDataChangeListener(listener: DataChangeListener): void { } } @Entry @Component struct ListItemExample { private arr: ListDataSource = new ListDataSource([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); build() { Column() { List({ space: 20, initialIndex: 0 }) { LazyForEach(this.arr, (item: number) => { ListItem() { Text('' + item) .width('100%') .height(100) .fontSize(16) .textAlign(TextAlign.Center) .borderRadius(10) .backgroundColor(0xFFFFFF) } }, (item: string) => item) }.width('90%') .scrollBar(BarState.Off) }.width('100%').height('100%').backgroundColor(0xDCDCDC).padding({ top: 5 }) } }
4.2 交互能力实现
ListItem() .selectable(true) // 可选中状态 .selected($$this.isSelected) // 双向绑定选中状态 .onSelect((selected: boolean) => { // 选中状态变更回调 console.log(`选中状态: ${selected}`); }) .swipeAction({ // 滑动操作 end: { // 向右滑动显示 builder: () => Row() } })
4.3 卡片样式优化(API 10+)
// xxx.ets @Entry @Component struct ListItemExample3 { build() { Column() { List({ space: '4vp', initialIndex: 0 }) { ListItemGroup({ style: ListItemGroupStyle.CARD }) { ForEach([ListItemStyle.CARD, ListItemStyle.CARD, ListItemStyle.NONE], (itemStyle: number, index?: number) => { ListItem({ style: itemStyle }) { Text('' + index) .width('100%') .textAlign(TextAlign.Center) } }) } ForEach([ListItemStyle.CARD, ListItemStyle.CARD, ListItemStyle.NONE], (itemStyle: number, index?: number) => { ListItem({ style: itemStyle }) { Text('' + index) .width('100%') .textAlign(TextAlign.Center) } }) } .width('100%') .multiSelectable(true) .backgroundColor(0xDCDCDC) } .width('100%') .padding({ top: 5 }) } }
五、ListItemGroup 组件:列表的逻辑分组器
5.1 分组结构与吸顶效果
// ListDataSource.ets export class TimeTableDataSource implements IDataSource { private list: TimeTable[] = []; private listeners: DataChangeListener[] = []; constructor(list: TimeTable[]) { this.list = list; } totalCount(): number { return this.list.length; } getData(index: number): TimeTable { return this.list[index]; } registerDataChangeListener(listener: DataChangeListener): void { if (this.listeners.indexOf(listener) < 0) { this.listeners.push(listener); } } unregisterDataChangeListener(listener: DataChangeListener): void { const pos = this.listeners.indexOf(listener); if (pos >= 0) { this.listeners.splice(pos, 1); } } // 通知控制器数据变化 notifyDataChange(index: number): void { this.listeners.forEach(listener => { listener.onDataChange(index); }); } // 修改第一个元素 public change1stItem(temp: TimeTable): void { this.list[0] = temp; this.notifyDataChange(0); } } export class ProjectsDataSource implements IDataSource { private list: string[] = []; constructor(list: string[]) { this.list = list; } totalCount(): number { return this.list.length; } getData(index: number): string { return this.list[index]; } registerDataChangeListener(listener: DataChangeListener): void { } unregisterDataChangeListener(listener: DataChangeListener): void { } } export interface TimeTable { title: string; projects: string[]; }
// xxx.ets import { TimeTable, ProjectsDataSource, TimeTableDataSource } from './ListDataSource'; @Entry @Component struct ListItemGroupExample { itemGroupArray: TimeTableDataSource = new TimeTableDataSource([]); aboutToAppear(): void { let timeTable: TimeTable[] = [ { title: '星期一', projects: ['语文', '数学', '英语'] }, { title: '星期二', projects: ['物理', '化学', '生物'] }, { title: '星期三', projects: ['历史', '地理', '政治'] }, { title: '星期四', projects: ['美术', '音乐', '体育'] } ]; this.itemGroupArray = new TimeTableDataSource(timeTable); } @Builder itemHead(text: string) { Text(text) .fontSize(20) .backgroundColor(0xAABBCC) .width('100%') .padding(10) } @Builder itemFoot(num: number) { Text('共' + num + '节课') .fontSize(16) .backgroundColor(0xAABBCC) .width('100%') .padding(5) } build() { Column() { List({ space: 20 }) { LazyForEach(this.itemGroupArray, (item: TimeTable) => { ListItemGroup({ header: this.itemHead(item.title), footer: this.itemFoot(item.projects.length) }) { LazyForEach(new ProjectsDataSource(item.projects), (project: string) => { ListItem() { Text(project) .width('100%') .height(100) .fontSize(20) .textAlign(TextAlign.Center) .backgroundColor(0xFFFFFF) } }, (item: string) => item) } .divider({ strokeWidth: 1, color: Color.Blue }) // 每行之间的分界线 }) } .width('90%') .sticky(StickyStyle.Header | StickyStyle.Footer) .scrollBar(BarState.Off) }.width('100%').height('100%').backgroundColor(0xDCDCDC).padding({ top: 5 }) } }
5.2 分组布局规则
垂直布局:ListItemGroup 高度由内容自动计算,禁止显式设置 height
水平布局:宽度由内容自动计算,禁止显式设置 width
性能优化:分组内 ListItem 共享滚动上下文,减少内存占用
交互限制:分组头部 / 尾部不支持滑动操作,仅内容区支持
六、实战案例:从基础到复杂的列表开发
6.1 新闻资讯垂直列表
@Entry @Component struct NewsFeed { // 使用类替代接口定义数据模型 @State newsData: NewsItem[] = generateNews(20) // 生成模拟数据 private dataSource: NewsDataSource = new NewsDataSource(this.newsData) build() { List({ space: 12 }) { // 使用正确的LazyForEach语法 LazyForEach(this.dataSource, (item: NewsItem) => { ListItem() { Column() { Image(item.image) .width('100%') .height(180) .objectFit(ImageFit.Cover) .borderRadius(4) Text(item.title) .fontSize(16) .fontWeight(FontWeight.Medium) .margin({ top: 8, bottom: 4 }) .maxLines(2) .textOverflow({ overflow: TextOverflow.Ellipsis }) Text(item.summary) .fontSize(14) .fontColor('#666666') .lineHeight(20) .maxLines(2) .textOverflow({ overflow: TextOverflow.Ellipsis }) } .padding(16) .backgroundColor('#FFFFFF') .borderRadius(8) } }, (item: NewsItem) => item.id) // 唯一键 } .width('100%') .height('100%') .onReachEnd(() => this.loadMoreNews()) // 滚动到底部加载更多 .cachedCount(8) // 预加载8项 .divider({ strokeWidth: 0.5, color: '#EEEEEE' }) // 添加分割线 } // 加载更多数据 private loadMoreNews() { const newItems = generateNews(10) this.newsData = this.newsData.concat(newItems) this.dataSource.updateData(this.newsData) } } // 实现LazyForEach所需的数据源 class NewsDataSource implements IDataSource { private data: NewsItem[] = [] private listeners: DataChangeListener[] = [] constructor(data: NewsItem[]) { this.data = data } // 更新数据源 updateData(newData: NewsItem[]) { this.data = newData this.notifyDataReload() } // 通知数据变化 private notifyDataReload() { this.listeners.forEach(listener => listener.onDataReloaded()) } totalCount(): number { return this.data.length } getData(index: number): NewsItem { return this.data[index] } registerDataChangeListener(listener: DataChangeListener): void { this.listeners.push(listener) } unregisterDataChangeListener(listener: DataChangeListener): void { const index = this.listeners.indexOf(listener) if (index !== -1) { this.listeners.splice(index, 1) } } } // 新闻数据模型 class NewsItem { id: string = '' title: string = '' summary: string = '' image: Resource = $r('app.media.default_news') // 默认图片资源 } // 模拟数据生成函数 function generateNews(count: number): NewsItem[] { const result: NewsItem[] = [] for (let i = 0; i < count; i++) { result.push({ id: `news_${Date.now()}_${i}`, title: `新闻标题 ${i + 1}`, summary: `这是新闻摘要内容,展示了ArkTS新闻列表的实现方式...`, image: $r('app.media.news_image') // 实际项目中替换为真实资源 }) } return result }
6.2 任务管理分组列表
@Entry @Component struct TaskManager { // 任务分组数据模型 @State tasks: TaskGroup[] = [ { title: '今日待办', items: [ { id: '1', title: '完成工作报告', completed: false }, { id: '2', title: '准备会议材料', completed: false } ] }, { title: '已完成', items: [ { id: '3', title: '晨跑锻炼', completed: true }, { id: '4', title: '回复邮件', completed: true } ] } ] // 更新任务状态 private updateTaskStatus(groupIndex: number, itemIndex: number, checked: boolean) { this.tasks[groupIndex].items[itemIndex].completed = checked // 创建新数组触发UI更新 this.tasks = [...this.tasks] } // 分组头部构建器 @Builder groupHeaderBuilder(title: string) { Text(title) .fontSize(18) .fontWeight(FontWeight.Bold) .padding({ top: 20, bottom: 12, left: 16 }) .width('100%') .backgroundColor('#f5f5f5') } build() { List({ space: 8 }) { ForEach(this.tasks, (group: TaskGroup, groupIndex: number) => { ListItemGroup({ header: this.groupHeaderBuilder(group.title) }) { ForEach(group.items, (task: TaskItem, itemIndex: number) => { ListItem() { Row() { Checkbox() .onChange((checked: boolean) => { this.updateTaskStatus(groupIndex, itemIndex, checked) }) Text(task.title) .margin({ left: 8 }) .fontSize(16) } .padding(16) .width('100%') } .borderRadius(8) .margin({ bottom: 8 }) .backgroundColor('#FFFFFF') }, (task: TaskItem) => task.id) } }, (group: TaskGroup) => group.title) } .width('100%') .height('100%') .divider({ strokeWidth: 0 }) // 隐藏分割线 .listDirection(Axis.Vertical) } } // 数据模型定义 class TaskGroup { title: string = '' items: TaskItem[] = [] } class TaskItem { id: string = '' title: string = '' completed: boolean = false }
七、工程实践最佳指南
7.1 性能优化策略
虚拟列表实现
List() { // 虚拟列表不需要子组件 } .width('100%') .height('100%') .cachedCount(10) // 预加载项数 .onScrollIndex((start, end) => { // 滚动事件处理(可选) console.log(`当前可见项: ${start}-${end}`) })
长列表优化组合
List() .cachedCount(10) // 预加载10项
7.2 兼容性处理方案
API 分级适配
#if (API >= 9) List() .editMode(true) .onItemDelete((index) => { // 新API编辑逻辑 }) #else List() .onClick((index) => { // 旧API模拟编辑逻辑 }) #endif
多端布局适配
List() .listDirection( DeviceType.isTablet() ? Axis.Horizontal : Axis.Vertical ) .then((list) => { if (DeviceType.isPhone()) { list.itemSize(80) } else { list.itemSize(100) } })
7.3 常见问题解决方案
问题场景 解决方案 列表滚动卡顿 1. 启用虚拟列表模式 .useVirtualized (true) 2. 设置固定项高度 .itemSize (80) 分组头部不吸顶 确认 .sticky (StickyStyle.Header) 已设置,且 List 为垂直布局 滑动删除无响应 1. 检查 API 版本是否≥9 2. 确保 actionAreaDistance < ListItem 宽度 选中状态不同步 使用双向绑定 .selected ($isSelected),避免直接操作状态变量 列表项点击穿透 在最外层容器添加 .onClick (() => {}) 消耗点击事件
八、总结:三层架构构建高效列表体系
鸿蒙 List 组件体系通过 List、ListItem、ListItemGroup 的三层架构,为开发者提供了完整的列表解决方案:
List 容器:负责整体布局控制、滚动管理与性能优化,是列表的总控制器
ListItem 单元:承载数据展示与交互逻辑,是列表的原子组件
ListItemGroup 分组:实现数据逻辑分组与吸顶效果,提升复杂列表的信息层级
在实际开发中,建议遵循以下最佳实践:
长列表优先使用 LazyForEach + 虚拟列表模式
复杂数据采用 ListItemGroup 进行语义化分组
交互操作通过组件内置 API 实现,避免自定义事件系统
多端适配结合 DeviceType 与条件编译实现
随着鸿蒙生态向全场景拓展,列表组件将持续进化,未来版本可能加入 AI 驱动的布局优化、3D 滚动效果等创新功能。建议开发者从基础案例入手,结合 DevEco Studio 的实时预览与性能调试工具,逐步掌握列表开发的核心技巧,为用户打造流畅、高效的数据浏览体验
共同學習,寫下你的評論
評論加載中...
作者其他優質文章