【鸿蒙开发教程】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组件只是鸿蒙开发的开始,结合状态管理、路由导航等知识,你可以构建更加复杂和功能完善的鸿蒙应用。持续实践和学习,不断提升你的鸿蒙开发技能!

相关推荐
遇到困难睡大觉哈哈3 小时前
HarmonyOS —— Remote Communication Kit 拦截器(Interceptor)高阶定制能力笔记
笔记·华为·harmonyos
遇到困难睡大觉哈哈4 小时前
HarmonyOS —— Remote Communication Kit 定制处理行为(ProcessingConfiguration)速记笔记
笔记·华为·harmonyos
氤氲息4 小时前
鸿蒙 ArkTs 的WebView如何与JS交互
javascript·交互·harmonyos
遇到困难睡大觉哈哈4 小时前
HarmonyOS支付接入证书准备与生成指南
华为·harmonyos
赵浩生4 小时前
鸿蒙技术干货10:鸿蒙图形渲染基础,Canvas绘图与自定义组件实战
harmonyos
赵浩生4 小时前
鸿蒙技术干货9:deviceInfo 设备信息获取与位置提醒 APP 整合
harmonyos
BlackWolfSky5 小时前
鸿蒙暂未归类知识记录
华为·harmonyos
L、2187 小时前
Flutter 与开源鸿蒙(OpenHarmony):跨平台开发的新未来
flutter·华为·开源·harmonyos
L、2188 小时前
Flutter 与 OpenHarmony 深度融合实践:打造跨生态高性能应用(进阶篇)
javascript·flutter·华为·智能手机·harmonyos