你好,我是华鸿技术栈的作者。
前几篇把鸿蒙开发的基础组件和 Flex 布局搞定了。Text、Image、Button、TextInput 会用,Column 和 Row 能把组件排好布局。今天开始搞列表。
列表是 鸿蒙开发里最常见的页面结构。新闻列表、商品列表、聊天记录、设置菜单——都是列表。鸿蒙里做列表核心就三个东西:List 容器、ListItem 子项、ForEach/LazyForEach 循环渲染。
这篇会讲静态列表的创建,动态数据的 ForEach 循环渲染,数据量大怎么用 LazyForEach 懒加载提升性能。附带一个文章列表的完整代码,后面做 App 的时候可以直接拿过去用。
先看静态列表——列表里每一项都是确定的数据,几条数据就几个 ListItem。
List() {ListItem() {Text('第一条').fontSize(16).width('100%').padding(16)}ListItem() {Text('第二条').fontSize(16).width('100%').padding(16)}ListItem() {Text('第三条').fontSize(16).width('100%').padding(16)}}
List 是容器,ListItem 是每一项。List 默认就是滚动的,内容超出屏幕自动出滚动条。
分割线:每条之间加分割线,ListItem 里塞一个 Divider:
ListItem() {Column() {Text('第一条').fontSize(16).padding(16)Divider()}}
滚动方向:List 默认竖直滚动。水平滚动加 listDirection(Axis.Horizontal):
List() {// ...}.listDirection(Axis.Horizontal)
水平列表在 Banner 轮播、横向标签栏里用到,大部分场景还是默认的竖直滚动。
几条数据可以手写 ListItem,多了就用循环渲染:提供数据数组和每一项的 UI 模板,框架自动帮你生成列表。
@Entry@Componentstruct ArticleList {@State articles: string[] = ['鸿蒙开发入门','ArkTS 语法速成','声明式 UI 详解','@State 状态管理','@Prop 和 @Link','Flex 弹性布局'];build() {List() {ForEach(this.articles, (item: string, index: number) => {ListItem() {Text(item).fontSize(16).padding(16).width('100%')}})}}}
ForEach 三个参数:数据数组、每一项的 UI 模板(回调函数,接收 item 和 index)、key 生成函数(可选,默认用 index)。框架自动遍历数组,每个元素生成一个 ListItem。
数据源变了,比如往 this.articles 里 push 一条新数据,列表自动刷新。这就是之前学的 @State 在起作用——数据变,UI 跟着变。
数据量不大,几十条、一两百条,用 ForEach 没问题。它一次性把所有 ListItem 都创建出来,数据少的时候渲染很快。
数据量大了,几百上千条,ForEach 一次性全创建会卡。LazyForEach 只创建屏幕上能看见的那几项,滑到哪加载哪,看不见的回收掉。
LazyForEach 和 ForEach 的写法差别,在于 LazyForEach 需要自己实现一个数据源类,实现 IDataSource 接口。看着复杂,其实就是一个固定套路,写一次以后到处复制。
// 数据源类:实现 IDataSourceclass ArticleDataSource implements IDataSource {private articles: string[] = [];constructor(articles: string[]) {this.articles = articles;}totalCount(): number {return this.articles.length;}getData(index: number): string {return this.articles[index];}registerDataChangeListener(): void {}unregisterDataChangeListener(): void {}}@Entry@Componentstruct LazyArticleList {@State articles: ArticleDataSource = new ArticleDataSource(['鸿蒙开发入门', 'ArkTS 语法速成', '声明式 UI 详解','@State 状态管理', '@Prop 和 @Link', 'Flex 弹性布局',// 实际项目里这里可能有几百条数据]);build() {List() {LazyForEach(this.articles, (item: string, index: number) => {ListItem() {Text(item).fontSize(16).padding(16).width('100%')}})}}}
ArticleDataSource 实现 IDataSource 接口,最少要写四个方法:totalCount(返回数据总数)、getData(返回第几项的数据)、registerDataChangeListener 和 unregisterDataChangeListener(数据变化监听,暂时空着)。
写一次,以后做列表页面直接复制这个类,换个数据类型就行。
数据几十条以内,ForEach 省事。数据上百条,或者不确定未来会不会加很多数据,直接用 LazyForEach。
几个注意点:
LazyForEach 的 key 生成器(默认用 index)要保证每条数据有唯一标识,用 index 在数据顺序不变的情况下没问题。如果列表会增删改,key 最好用数据的唯一 ID。
getData 里返回的数据类型要和模板里声明的一致。
把 List、LazyForEach、之前学的 Flex 布局合在一起,做一个文章列表。左边封面图占位,右边标题和摘要,每条之间分割线隔开。
// 文章数据模型type Article = {id: string;title: string;summary: string;author: string;readCount: number;};// 数据源classArticleDataSourceimplementsIDataSource{private articles: Article[] = [];constructor(articles: Article[]) {this.articles = articles;}totalCount(): number {return this.articles.length;}getData(index: number): Article {return this.articles[index];}registerDataChangeListener(): void {}unregisterDataChangeListener(): void {}}@Entry@Componentstruct ArticleListPage {@State dataSource: ArticleDataSource = new ArticleDataSource([{ id: '1', title: '鸿蒙原生应用开发实战:从零搭建一个完整App', summary: '分享实战代码、源码解析、性能优化及生态前沿', author: '华鸿技术栈', readCount: 1280 },{ id: '2', title: 'ArkTS 声明式 UI 入门:扔掉命令式的旧思维', summary: '命令式 vs 声明式,build() 方法,链式调用,手写计数器组件', author: '华鸿技术栈', readCount: 960 },{ id: '3', title: '@State 状态管理详解:数据变了,UI 自动刷新', summary: '@State 原理,同值不重画,对象和数组的整体替换规则', author: '华鸿技术栈', readCount: 1560 },{ id: '4', title: '@Prop 和 @Link:父子组件传值的正确写法', summary: '单向绑定和双向绑定怎么选,$ 前缀的作用,常见错误排查', author: '华鸿技术栈', readCount: 820 },{ id: '5', title: 'Flex 弹性布局:Column 和 Row 的对齐规则', summary: '主轴交叉轴怎么对齐,space 控制间距,layoutWeight 弹性分配', author: '华鸿技术栈', readCount: 1100 },]);build() {List() {LazyForEach(this.dataSource, (item: Article) => {ListItem() {Column() {Row() {// 左侧封面图占位Column().width(100).height(75).backgroundColor('#E8F0FE').borderRadius(8).justifyContent(FlexAlign.Center)// 右侧文字区域Column({ space: 6 }) {Text(item.title).fontSize(16).fontWeight(FontWeight.Medium).fontColor('#333333').maxLines(2).textOverflow({ overflow: TextOverflow.Ellipsis })Text(item.summary).fontSize(13).fontColor('#999999').maxLines(2).textOverflow({ overflow: TextOverflow.Ellipsis })Row() {Text(item.author).fontSize(12).fontColor('#666666')Text(' · ').fontSize(12).fontColor('#CCCCCC')Text(`${item.readCount}阅读`).fontSize(12).fontColor('#666666')}}.layoutWeight(1).margin({ left: 12 }).alignItems(HorizontalAlign.Start)}.width('100%').padding(16)Divider().margin({ left: 16 })}}})}}}
运行起来就是一个完整的文章列表。封面图占位用了一个带背景色的 Column,后面换成 Image 组件加载网络图片就行,思路跟第10篇讲的一样。
每条的结构:Row 里左边封面图占位、右边标题摘要作者阅读量,最下面一条分割线。这个结构在后面做 App 首页文章流的时候会直接用。
ForEach vs LazyForEach:数据少(几十条以内)用 ForEach,写法简单。数据多或者不确定数据量用 LazyForEach,只渲染可见区域的列表项,滑出屏幕的回收掉,内存和性能都好很多。
key 的作用:ForEach 和 LazyForEach 都有 key 参数。框架靠 key 判断哪些列表项是新增的、哪些是删掉的、哪些位置变了。默认用 index 当 key,如果列表会动态增删数据,最好用数据的唯一 ID 当 key,避免 key 重复导致渲染错乱。
ListItem 里别放太重的东西:每一项的 UI 尽量精简,图片用懒加载,复杂计算放到数据层提前做完,别在 build 里做耗时操作。
鸿蒙开发中列表三件套:
List:滚动列表容器,支持竖直和水平方向。
ForEach:循环渲染,数据量少的时候用,写法简单。
LazyForEach:懒加载,只渲染可见区域的列表项,数据多的时候用。
Q: List 和 ForEach 一定要一起用吗?A: 不一定。ForEach 可以放在 Column 里做非滚动的循环渲染。但如果数据多需要滚动,就得放在 List 里。
Q: ForEach 和 LazyForEach 怎么选?A: 数据量少(几十条以内)用 ForEach,写法简单。数据量多或者不确定用 LazyForEach。提前换成 LazyForEach 没有额外代价。
Q: LazyForEach 的 IDataSource 必须实现那四个方法吗?A: totalCount 和 getData 必须实现,框架靠这两个方法知道有多少数据、每项渲染什么。registerDataChangeListener 和 unregisterDataChangeListener 暂时空着。
Q: key 默认用 index 有什么问题?A: 列表数据顺序不变的话没问题。如果列表会增删改,最好用数据的唯一 ID 当 key,避免渲染错乱。
下一篇:学习了列表渲染,但数据是本地的。下一篇讲网络请求——http 模块,async/await 怎么处理异步,把文章列表换成从服务器获取的真实数据。
本文由「华鸿技术栈」原创发布,专注鸿蒙原生应用与元服务开发。分享实战代码、源码解析、性能优化及生态前沿。不讲虚的,只上干货。转载请联系授权。
#鸿蒙开发 #ArkTS #ArkUI #List #ForEach #LazyForEach #鸿蒙原生应用开发 #鸿蒙培训