【鸿蒙开发教程】HarmonyOS 实现List 列表

一、List组件概述

1.1 什么是List组件

List组件是鸿蒙开发中最常用的滚动列表容器,就像一个智能的"数据展示柜",能够高效地展示一系列相似格式的数据项,并支持滚动、选择、点击等交互操作。

核心特点

  • 垂直/水平滚动:支持上下或左右方向的滚动展示
  • 灵活的列表项:每个列表项可以包含复杂的UI结构
  • 高性能渲染:支持大数据集的高效展示与滚动
  • 丰富的交互:支持点击、长按、滑动等多种交互方式
  • 样式定制:可自定义滚动条、分割线、边缘效果等

与其他列表组件对比

表格

复制

组件 特点 适用场景
List 功能全面,支持复杂交互 联系人列表、商品列表等大多数场景
Grid 网格布局,多列展示 相册、应用图标等网格排列场景
WaterFlow 瀑布流布局,高度自适应 图片墙、资讯流等不规则布局
Swiper 轮播展示,一次显示一项 广告轮播、引导页等场景

典型应用场景

  • 联系人列表
  • 商品展示列表
  • 消息通知列表
  • 设置选项列表
  • 新闻资讯列表

1.2 List组件的重要性

在移动应用开发中,列表是最常用的UI元素之一,约80%的应用界面包含列表组件。掌握List组件的使用,能够解决大部分数据展示需求,是鸿蒙开发的基础技能。

List组件不仅能展示静态数据,还能通过状态管理实现动态更新,结合其高性能渲染机制,即使处理上万条数据也能保持流畅的滚动体验。

二、基础使用方法

2.1 创建简单列表

创建一个基础的List列表只需三个步骤:准备数据、使用List容器、定义列表项。

Step 1:准备数据

css 复制代码
// 定义数据模型
interface Contact {
  id: number;
  name: string;
  avatar: string;
  phone: string;
}

// 模拟联系人数据
@State contacts: Contact[] = [
  { id: 1, name: "张三", avatar: "avatar1", phone: "13800138000" },
  { id: 2, name: "李四", avatar: "avatar2", phone: "13900139000" },
  { id: 3, name: "王五", avatar: "avatar3", phone: "13700137000" },
  { id: 4, name: "赵六", avatar: "avatar4", phone: "13600136000" },
  { id: 5, name: "孙七", avatar: "avatar5", phone: "13500135000" }
];

Step 2:创建List组件

scss 复制代码
build() {
  Column() {
    Text("联系人列表")
      .fontSize(20)
      .margin(10)
      
    // 基本列表
    List() {
      // 循环渲染列表项
      ForEach(this.contacts, (contact: Contact) => {
        ListItem() {
          // 列表项内容
          Row() {
            // 头像
            Image($r(`app.media.${contact.avatar}`))
              .width(50)
              .height(50)
              .borderRadius(25)
              
            // 联系人信息
            Column() {
              Text(contact.name)
                .fontSize(18)
              Text(contact.phone)
                .fontSize(14)
                .fontColor("#666666")
            }
            .margin({ left: 10 })
            .alignItems(HorizontalAlign.Start)
          }
          .width("100%")
          .padding(10)
        }
      })
    }
    .width("100%")
    .height("80%")
  }
  .width("100%")
  .padding(15)
}

核心组成部分

  • List:列表容器组件,提供滚动功能
  • ListItem:列表项容器,每个列表项必须放在ListItem中
  • ForEach:循环渲染数据,将数组数据转换为列表项

2.2 基本样式设置

List组件提供了丰富的样式属性,可根据需求定制列表外观。

常用样式属性

表格

复制

属性 作用 示例值
width/height 设置列表尺寸 "100%", 300
backgroundColor 背景色 "#F5F5F5"
borderWidth/borderColor 边框样式 1, "#E5E5E5"
borderRadius 圆角 10
margin/padding 外边距/内边距 10, { left: 15 }
space 列表项间距 5
divider 分割线样式 { strokeWidth: 1, color: "#E5E5E5" }
scrollBar 滚动条显示 BarState.On, BarState.Off

样式设置示例

scss 复制代码
List() {
  // 列表项内容...
}
.width("100%")
.height(500)
.backgroundColor("#F5F5F5")
.borderRadius(10)
.padding(5)
.space(5)
.divider({
  strokeWidth: 1,
  color: "#E5E5E5",
  startMargin: 70, // 分割线左侧缩进
  endMargin: 15   // 分割线右侧缩进
})
.scrollBar(BarState.On) // 显示滚动条
.edgeEffect(EdgeEffect.Spring) // 设置边缘弹性效果

2.3 数据绑定与更新

List组件结合状态管理,可以实现数据的动态更新,当数据源变化时,列表会自动刷新。

动态更新示例

scss 复制代码
@Entry
@Component
struct DynamicList {
  @State items: string[] = ["Item 1", "Item 2", "Item 3"]
  @State newItem: string = ""
  
  build() {
    Column() {
      // 添加新项
      Row() {
        TextInput({ placeholder: "输入新项" })
          .width("70%")
          .onChange((value) => {
            this.newItem = value
          })
          
        Button("添加")
          .width("25%")
          .onClick(() => {
            if (this.newItem) {
              // 添加新数据
              this.items.push(this.newItem)
              this.newItem = ""
            }
          })
      }
      
      // 列表
      List() {
        ForEach(this.items, (item: string, index: number) => {
          ListItem() {
            Row() {
              Text(item)
                .flexGrow(1)
                
              Button("删除")
                .width(60)
                .fontSize(14)
                .backgroundColor("#F53F3F")
                .onClick(() => {
                  // 删除数据
                  this.items.splice(index, 1)
                })
            }
            .padding(10)
          }
        })
      }
      .width("100%")
      .margin({ top: 10 })
    }
    .padding(15)
    .width("100%")
  }
}

数据更新原理

  • 当使用@State装饰的数组发生变化(添加、删除、修改元素)时,ArkUI框架会自动检测变化
  • ForEach会重新渲染变化的列表项,而非全部重新渲染
  • 这种局部更新机制保证了列表的高效更新

三、高级特性详解

3.1 列表项交互

List组件支持多种交互方式,满足不同的用户操作需求。

常用交互事件

表格

复制

事件 作用 应用场景
onClick 点击事件 跳转到详情页
onLongPress 长按事件 弹出操作菜单
onSwipe 滑动事件 显示删除/编辑按钮
onScroll 滚动事件 监听滚动位置
onScrollIndex 滚动索引事件 实现字母索引

交互实现示例

javascript 复制代码
ListItem() {
  // 列表项内容...
}
.onClick(() => {
  // 点击事件:跳转到详情页
  router.pushUrl({
    url: "pages/DetailPage",
    params: { contact: contact }
  })
})
.onLongPress(() => {
  // 长按事件:显示操作菜单
  prompt.showActionMenu({
    title: "请选择操作",
    items: ["编辑", "删除", "分享"],
    success: (result) => {
      switch(result.index) {
        case 0: // 编辑
          this.editContact(contact);
          break;
        case 1: // 删除
          this.deleteContact(contact.id);
          break;
        case 2: // 分享
          this.shareContact(contact);
          break;
      }
    }
  })
})

滑动操作实现

scss 复制代码
ListItem() {
  // 列表项内容...
}
.swipeAction({
  end: this.DeleteButton(contact.id) // 右侧滑动显示删除按钮
})

// 删除按钮构建函数
@Builder
DeleteButton(id: number) {
  Button() {
    Image($r("app.media.ic_delete"))
      .width(20)
      .height(20)
  }
  .width(60)
  .height("100%")
  .backgroundColor("#F53F3F")
  .onClick(() => {
    this.deleteContact(id);
  })
}

3.2 分组列表

当数据量较大时,可以使用ListItemGroup将列表项分组展示,每组可以有独立的标题。

分组列表实现

scss 复制代码
// 分组数据模型
interface GroupItem {
  groupName: string;
  items: Contact[];
}

// 分组数据
@State groups: GroupItem[] = [
  {
    groupName: "A",
    items: [
      { id: 1, name: "阿明", avatar: "avatar1", phone: "13800138000" },
      { id: 2, name: "阿华", avatar: "avatar2", phone: "13900139000" }
    ]
  },
  {
    groupName: "B",
    items: [
      { id: 3, name: "巴金", avatar: "avatar3", phone: "13700137000" },
      { id: 4, name: "百合", avatar: "avatar4", phone: "13600136000" }
    ]
  }
];

// 分组列表UI
List() {
  ForEach(this.groups, (group: GroupItem) => {
    // 分组
    ListItemGroup({
      header: this.GroupHeader(group.groupName) // 分组标题
    }) {
      // 组内列表项
      ForEach(group.items, (contact: Contact) => {
        ListItem() {
          // 联系人项内容...
        }
      })
    }
  })
}

// 分组标题构建函数
@Builder
GroupHeader(title: string) {
  Text(title)
    .fontSize(16)
    .fontWeight(FontWeight.Bold)
    .fontColor("#666666")
    .padding({ left: 15, top: 10, bottom: 5 })
}

分组吸顶效果

设置sticky属性可以实现分组标题吸顶效果,滚动时组标题会固定在列表顶部,直到下一组到达顶部。

csharp 复制代码
ListItemGroup({
  header: this.GroupHeader(group.groupName),
  sticky: StickyStyle.Header // 吸顶效果
}) {
  // 组内列表项...
}

3.3 索引列表

对于联系人等按字母排序的列表,可以添加侧边索引栏,实现快速定位功能。

索引列表实现

less 复制代码
@Entry
@Component
struct IndexedList {
  // 索引字母
  private indexLetters: string[] = ['#', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
  
  // 当前选中的索引
  @State currentIndex: number = 0;
  
  build() {
    Stack() {
      // 联系人列表(同上分组列表)
      List() {
        // 分组列表内容...
      }
      
      // 右侧索引栏
      AlphabetIndexer({
        arrayValue: this.indexLetters,
        selected: this.currentIndex
      })
        .width(30)
        .height("90%")
        .backgroundColor("transparent")
        .selectedColor("#007AFF")
        .color("#666666")
        .selectedBackgroundColor("rgba(0,0,0,0.1)")
        .onSelect((index: number) => {
          this.currentIndex = index;
          // 根据索引滚动到对应分组
          this.scrollToGroup(this.indexLetters[index]);
        })
        .alignStyle(IndexerAlign.Right)
        .margin({ right: 5 })
    }
  }
  
  // 滚动到指定分组
  scrollToGroup(letter: string) {
    // 实现滚动逻辑...
  }
}

四、性能优化策略

4.1 懒加载(LazyForEach)

当列表数据量较大(超过100项)时,使用LazyForEach替代ForEach可以显著提升性能,它只会渲染当前可视区域的列表项。

LazyForEach使用方法

kotlin 复制代码
// 1. 实现数据源接口
class ContactDataSource implements IDataSource {
  private contacts: Contact[] = [];
  private listeners: DataChangeListener[] = [];
  
  constructor(data: Contact[]) {
    this.contacts = data;
  }
  
  // 获取数据总数
  totalCount(): number {
    return this.contacts.length;
  }
  
  // 获取指定索引的数据
  getData(index: number): Contact {
    return this.contacts[index];
  }
  
  // 注册数据变化监听器
  registerDataChangeListener(listener: DataChangeListener): void {
    if (!this.listeners.includes(listener)) {
      this.listeners.push(listener);
    }
  }
  
  // 注销数据变化监听器
  unregisterDataChangeListener(listener: DataChangeListener): void {
    const index = this.listeners.indexOf(listener);
    if (index >= 0) {
      this.listeners.splice(index, 1);
    }
  }
}

// 2. 创建数据源实例
private dataSource: ContactDataSource = new ContactDataSource(largeContactList);

// 3. 使用LazyForEach渲染列表
List() {
  LazyForEach(
    this.dataSource,
    (contact: Contact) => {
      ListItem() {
        // 列表项内容...
      }
    },
    (contact: Contact) => contact.id.toString() // 唯一标识符生成函数
  )
}

LazyForEach优势

  • 内存占用低:只渲染可视区域的列表项
  • 启动速度快:无需一次性创建所有列表项
  • 滚动流畅:减少DOM节点数量,提升滚动性能

4.2 列表项复用

通过@Reusable装饰器可以实现列表项组件的复用,避免频繁创建和销毁组件实例,进一步提升性能。

复用组件实现

less 复制代码
// 使用@Reusable装饰器标记可复用组件
@Reusable
@Component
struct ReusableContactItem {
  @ObjectLink contact: Contact; // 使用@ObjectLink而非@Prop提高性能
  
  build() {
    Row() {
      // 联系人项内容...
    }
  }
}

// 在列表中使用复用组件
LazyForEach(
  this.dataSource,
  (contact: Contact) => {
    ListItem() {
      ReusableContactItem({ contact: contact })
    }
  },
  (contact: Contact) => contact.id.toString()
)

4.3 其他性能优化建议

减少布局层级

列表项布局应尽量扁平化,避免过深的嵌套,建议嵌套层级不超过3层。

优化前

scss 复制代码
// 不推荐:嵌套过深
ListItem() {
  Column() {
    Row() {
      Column() {
        Text(contact.name)
      }
    }
  }
}

优化后

scss 复制代码
// 推荐:扁平化布局
ListItem() {
  Row() {
    Image(...)
    Column() {
      Text(contact.name)
      Text(contact.phone)
    }
  }
}

图片优化

  • 使用合适分辨率的图片,避免大图缩小显示
  • 实现图片懒加载,只加载可视区域的图片
  • 使用缓存机制,避免重复加载

数据分页加载

对于大量网络数据,采用分页加载策略,滚动到底部时加载下一页数据。

kotlin 复制代码
List() {
  // 列表项...
}
.onReachEnd(() => {
  // 滚动到底部,加载更多数据
  if (!this.isLoading && this.hasMoreData) {
    this.loadMoreData();
  }
})

五、实战案例分析

5.1 联系人列表(综合案例)

功能需求:实现一个联系人列表,支持分组显示、字母索引、点击查看详情、滑动删除等功能。

实现要点

  • 使用分组列表实现联系人按字母分组
  • 添加字母索引栏实现快速定位
  • 实现列表项点击和滑动删除功能
  • 使用LazyForEach优化性能

核心代码结构

less 复制代码
@Entry
@Component
struct ContactListDemo {
  // 联系人数据(分组)
  @State groups: GroupItem[] = [];
  // 索引字母
  private indexLetters: string[] = ['#', 'A', 'B', 'C', ..., 'Z'];
  // 当前选中索引
  @State currentIndex: number = 0;
  // 列表控制器
  private listScroller: Scroller = new Scroller();
  
  // 页面加载时初始化数据
  aboutToAppear() {
    this.loadContacts(); // 加载联系人数据
  }
  
  build() {
    Stack() {
      // 联系人列表
      List(this.listScroller) {
        ForEach(this.groups, (group: GroupItem) => {
          ListItemGroup({
            header: this.GroupHeader(group.groupName),
            sticky: StickyStyle.Header // 分组吸顶
          }) {
            ForEach(group.items, (contact: Contact) => {
              ListItem() {
                this.ContactItem(contact); // 联系人项
              }
              .swipeAction({ end: this.DeleteButton(contact.id) }) // 滑动删除
              .onClick(() => this.navigateToDetail(contact)) // 点击查看详情
            })
          }
        })
      }
      
      // 右侧索引栏
      AlphabetIndexer({
        arrayValue: this.indexLetters,
        selected: this.currentIndex
      })
        .onSelect((index: number) => this.scrollToGroup(index))
        .alignStyle(IndexerAlign.Right)
    }
  }
  
  // 联系人项UI
  @Builder
  ContactItem(contact: Contact) {
    // 实现联系人项UI...
  }
  
  // 分组标题UI
  @Builder
  GroupHeader(title: string) {
    // 实现分组标题UI...
  }
  
  // 删除按钮UI
  @Builder
  DeleteButton(id: number) {
    // 实现删除按钮UI...
  }
  
  // 滚动到指定分组
  scrollToGroup(index: number) {
    // 实现滚动逻辑...
  }
  
  // 导航到详情页
  navigateToDetail(contact: Contact) {
    // 实现页面跳转...
  }
  
  // 加载联系人数据
  loadContacts() {
    // 加载并处理联系人数据...
  }
}

5.2 商品列表(网络数据)

功能需求:实现一个商品列表,从网络加载数据,支持下拉刷新、上拉加载更多、点击查看商品详情。

实现要点

  • 使用LazyForEach优化性能
  • 实现下拉刷新和上拉加载
  • 添加加载状态和空数据提示
  • 处理网络错误情况

下拉刷新实现

kotlin 复制代码
List() {
  // 列表内容...
}
.onRefresh(() => {
  // 下拉刷新回调
  this.refreshData()
    .then(() => {
      // 刷新成功,停止刷新动画
      this.scroller.finishRefresh(true);
    })
    .catch(() => {
      // 刷新失败,停止刷新动画
      this.scroller.finishRefresh(false);
    });
})
.refreshOffset(100) // 触发刷新的滚动距离

上拉加载实现

scss 复制代码
List() {
  // 列表内容...
  
  // 加载更多提示
  if (this.isLoading) {
    ListItem() {
      Row() {
        Progress({ type: ProgressType.Circular })
          .width(20)
          .height(20)
        Text("加载中...")
          .margin({ left: 10 })
      }
      .justifyContent(FlexAlign.Center)
      .padding(15)
    }
  }
}
.onReachEnd(() => {
  // 滚动到底部,加载更多
  if (!this.isLoading && this.hasMore) {
    this.loadMoreData();
  }
})

六、常见问题与解决方案

6.1 列表显示不全

问题描述:List组件中的内容显示不全,底部部分列表项被截断。

原因分析

List组件未正确设置高度,或其父容器尺寸计算错误,导致列表可滚动区域不足。

解决方案

方案一:设置明确高度

scss 复制代码
List() {
  // 列表内容...
}
.height("80%") // 设置明确高度

方案二:使用layoutWeight

scss 复制代码
Column() {
  Text("标题")
  
  List() {
    // 列表内容...
  }
  .layoutWeight(1) // 权重分配剩余空间
}
.height("100%")

方案三:使用Flex布局

scss 复制代码
Flex({ direction: FlexDirection.Column }) {
  Text("标题")
  
  List() {
    // 列表内容...
  }
  .flexGrow(1) // 填充剩余空间
}
.height("100%")

6.2 列表滚动卡顿

问题描述:列表滚动时不流畅,出现卡顿或掉帧现象。

常见原因

  • 数据量过大,未使用LazyForEach
  • 列表项布局复杂,嵌套层级过深
  • 列表项包含复杂计算或图片加载
  • 未实现组件复用

解决方案

  1. 使用LazyForEach替代ForEach
  2. 简化列表项布局,减少嵌套
  3. 实现图片懒加载和缓存
  4. 使用@Reusable复用组件
  5. 将复杂计算移至子线程

6.3 动态数据不更新

问题描述:数据源变化后,列表未自动更新。

常见原因

  • 直接修改数组元素,未触发状态更新
  • 使用了不可变数据结构
  • 数据源未正确实现IDataSource接口

解决方案

数组更新正确方式

kotlin 复制代码
// 错误方式:直接修改数组元素
this.items[0] = "New Value"; // 不会触发更新

// 正确方式:创建新数组
this.items = this.items.map((item, index) => 
  index === 0 ? "New Value" : item
);

// 添加元素
this.items = [...this.items, "New Item"];

// 删除元素
this.items = this.items.filter((_, index) => index !== 2);

LazyForEach数据更新

对于LazyForEach,需要通过数据源的notify方法通知数据变化:

typescript 复制代码
// 在数据源类中添加通知方法
class MyDataSource implements IDataSource {
  // ...其他代码
  
  // 通知数据变化
  notifyDataChanged() {
    this.listeners.forEach(listener => {
      listener.onDataReloaded(); // 全量刷新
      // 或使用更精确的通知:onDataAdded、onDataMoved等
    });
  }
}

// 数据变化后调用通知
this.dataSource.notifyDataChanged();

七、总结与进阶学习

7.1 知识要点回顾

通过本文学习,你已经掌握了List组件的核心用法:

  • 基础使用:创建列表、设置样式、绑定数据
  • 高级特性:分组列表、索引列表、交互操作
  • 性能优化:LazyForEach懒加载、组件复用、布局优化
  • 实战应用:联系人列表、商品列表等常见场景

List组件是鸿蒙开发中最基础也最重要的组件之一,掌握它的使用能够解决大部分数据展示需求。

7.2 进阶学习建议

深入学习

  • 分布式列表:多设备协同的列表同步
  • 虚拟列表:超大数据量的高效渲染
  • 自定义列表布局:实现复杂的列表效果

推荐社区资源

鸿蒙开发燾啊的动态 - 哔哩哔哩

  • List组件API参考
  • List组件实战案例
  • 优秀列表实现的源码学习

实践建议

  • 实现一个完整的通讯录应用
  • 开发一个带筛选功能的商品列表
  • 尝试实现一个高性能的聊天消息列表

掌握List组件只是鸿蒙开发的开始,结合状态管理、路由导航等知识,你可以构建更加复杂和功能完善的鸿蒙应用。持续实践和学习,不断提升你的鸿蒙开发技能!

相关推荐
小小小小小星1 小时前
鸿蒙开发状态管理与工程化关键信息通俗解释及案例汇总
harmonyos
奶糖不太甜1 小时前
鸿蒙开发问题之鸿蒙弹窗:方法论与技术探索
harmonyos
鸿蒙先行者2 小时前
鸿蒙ArkUI布局与性能优化技术探索
harmonyos·arkui
威哥爱编程2 小时前
鸿蒙 NEXT开发中轻松实现人脸识别功能
harmonyos
小喷友7 小时前
第4章 数据与存储
前端·app·harmonyos
小小小小小星1 天前
鸿蒙开发核心功能模块全解析:从架构到实战应用
harmonyos
奶糖不太甜1 天前
鸿蒙开发问题之纯血鸿蒙自启动步骤详解
harmonyos
xq95271 天前
鸿蒙next 获取versionCode和versionName
harmonyos