写点什么

鸿蒙 List 组件解析:从基础列表到高性能界面开发指南

作者:谢道韫
  • 2025-06-28
    广东
  • 本文字数:9764 字

    阅读完需:约 32 分钟

一、引言:列表布局 —— 鸿蒙应用的数据展示中枢

在鸿蒙应用开发体系中,列表布局是处理结构化数据展示的核心场景。从新闻资讯的信息流、电商平台的商品陈列到任务管理的待办事项,几乎所有中大型应用都依赖高效的列表组件实现数据可视化。鸿蒙提供的 List、ListItem、ListItemGroup 三件套组件,通过标准化的接口设计与分层架构,构建了一套完整的列表解决方案。本文将系统解析这三个组件的核心机制、进阶用法与工程实践,帮助开发者掌握高性能列表开发的鸿蒙范式。

二、核心组件架构与协作机制

2.1 组件层级与职责划分

鸿蒙列表体系采用三层架构设计:

  • List:列表容器组件,负责整体布局控制、滚动管理与性能优化

  • ListItem:列表项原子单元,承载具体数据展示与交互逻辑

  • ListItemGroup:列表分组组件,实现数据逻辑分组与吸顶吸底效果

组件层级关系示意图:

List├─ ListItemGroup(分组容器)│  ├─ ListItem(列表项1)│  ├─ ListItem(列表项2)├─ ListItem(独立列表项)
复制代码

2.2 核心技术优势

  • 标准化交互模型:内置滑动删除、选中状态、编辑模式等通用交互

  • 高性能渲染引擎:支持懒加载、预渲染与虚拟列表优化

  • 语义化分组能力:通过 ListItemGroup 实现数据分层与视觉分组

  • 多端自适应:自动适配手机、平板、车机等不同设备的屏幕特性

三、List 组件:列表布局的总控制器

3.1 基础接口与布局控制

// xxx.etsimport { ListDataSource } from './ListDataSource';  @Entry@Componentstruct 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.etsexport 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.etsimport { ListDataSource } from './ListDataSource';  @Entry@Componentstruct 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.etsexport 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@Componentstruct 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@Componentstruct 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.etsexport 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.etsimport { TimeTable, ProjectsDataSource, TimeTableDataSource } from './ListDataSource';@Entry@Componentstruct 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@Componentstruct 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@Componentstruct 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 版本是否≥92. 确保 actionAreaDistance < ListItem 宽度选中状态不同步	使用双向绑定 .selected ($isSelected),避免直接操作状态变量列表项点击穿透	在最外层容器添加 .onClick (() => {}) 消耗点击事件
复制代码

八、总结:三层架构构建高效列表体系

鸿蒙 List 组件体系通过 List、ListItem、ListItemGroup 的三层架构,为开发者提供了完整的列表解决方案:

  1. List 容器:负责整体布局控制、滚动管理与性能优化,是列表的总控制器

  2. ListItem 单元:承载数据展示与交互逻辑,是列表的原子组件

  3. ListItemGroup 分组:实现数据逻辑分组与吸顶效果,提升复杂列表的信息层级

在实际开发中,建议遵循以下最佳实践:

  • 长列表优先使用 LazyForEach + 虚拟列表模式

  • 复杂数据采用 ListItemGroup 进行语义化分组

  • 交互操作通过组件内置 API 实现,避免自定义事件系统

  • 多端适配结合 DeviceType 与条件编译实现

随着鸿蒙生态向全场景拓展,列表组件将持续进化,未来版本可能加入 AI 驱动的布局优化、3D 滚动效果等创新功能。建议开发者从基础案例入手,结合 DevEco Studio 的实时预览与性能调试工具,逐步掌握列表开发的核心技巧,为用户打造流畅、高效的数据浏览体验

用户头像

谢道韫

关注

还未添加个人签名 2025-05-05 加入

还未添加个人简介

评论

发布
暂无评论
鸿蒙 List 组件解析:从基础列表到高性能界面开发指南_谢道韫_InfoQ写作社区