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

发布于:2025-06-28 ⋅ 阅读:(19) ⋅ 点赞:(0)

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

在鸿蒙应用开发体系中,列表布局是处理结构化数据展示的核心场景。从新闻资讯的信息流、电商平台的商品陈列到任务管理的待办事项,几乎所有中大型应用都依赖高效的列表组件实现数据可视化。鸿蒙提供的 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 的三层架构,为开发者提供了完整的列表解决方案:

  1. List 容器:负责整体布局控制、滚动管理与性能优化,是列表的总控制器
  2. ListItem 单元:承载数据展示与交互逻辑,是列表的原子组件
  3. ListItemGroup 分组:实现数据逻辑分组与吸顶效果,提升复杂列表的信息层级

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

  • 长列表优先使用 LazyForEach + 虚拟列表模式
  • 复杂数据采用 ListItemGroup 进行语义化分组
  • 交互操作通过组件内置 API 实现,避免自定义事件系统
  • 多端适配结合 DeviceType 与条件编译实现

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


网站公告

今日签到

点亮在社区的每一天
去签到