• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 创建列表
2
3
4## 概述
5
6列表是一种复杂的容器,当列表项达到一定数量,内容超过屏幕大小时,可以自动提供滚动功能。它适合用于呈现同类数据类型或数据类型集,例如图片和文本。在列表中显示数据集合是许多应用程序中的常见要求(如通讯录、音乐列表、购物清单等)。
7
8使用列表可以轻松高效地显示结构化、可滚动的信息。通过在[List](../reference/arkui-ts/ts-container-list.md/)组件中按垂直或者水平方向线性排列子组件[ListItemGroup](../reference/arkui-ts/ts-container-listitemgroup.md/)或[ListItem](../reference/arkui-ts/ts-container-listitem.md),为列表中的行或列提供单个视图,或使用[循环渲染](../quick-start/arkts-rendering-control-foreach.md)迭代一组行或列,或混合任意数量的单个视图和ForEach结构,构建一个列表。List组件支持使用条件渲染、循环渲染、懒加载等[渲染控制](../quick-start/arkts-rendering-control-overview.md)方式生成子组件。
9
10
11## 布局与约束
12
13列表作为一种容器,会自动按其滚动方向排列子组件,向列表中添加组件或从列表中移除组件会重新排列子组件。
14
15如下图所示,在垂直列表中,List按垂直方向自动排列ListItemGroup或ListItem。
16
17ListItemGroup用于列表数据的分组展示,其子组件也是ListItem。ListItem表示单个列表项,可以包含单个子组件。
18
19  **图1** List、ListItemGroup和ListItem组件关系  
20
21![zh-cn_image_0000001562940589](figures/zh-cn_image_0000001562940589.png)
22
23>**说明:**
24>
25>List的子组件必须是ListItemGroup或ListItem,ListItem和ListItemGroup必须配合List来使用。
26
27
28### 布局
29
30List除了提供垂直和水平布局能力、超出屏幕时可以滚动的自适应[延伸能力](../key-features/multi-device-app-dev/adaptive-layout.md/#%E5%BB%B6%E4%BC%B8%E8%83%BD%E5%8A%9B)之外,还提供了自适应交叉轴方向上排列个数的布局能力。
31
32利用垂直布局能力可以构建单列或者多列垂直滚动列表,如下图所示。
33
34  **图2** 垂直滚动列表(左:单列;右:多列)  
35
36![zh-cn_image_0000001511580940](figures/zh-cn_image_0000001511580940.png)
37
38利用水平布局能力可以是构建单行或多行水平滚动列表,如下图所示。
39
40  **图3** 水平滚动列表(左:单行;右:多行)  
41
42![zh-cn_image_0000001511421344](figures/zh-cn_image_0000001511421344.png)
43
44
45### 约束
46
47列表的主轴方向是指子组件列的排列方向,也是列表的滚动方向。垂直于主轴的轴称为交叉轴,其方向与主轴方向相互垂直。
48
49如下图所示,垂直列表的主轴是垂直方向,交叉轴是水平方向;水平列表的主轴是水平方向,交叉轴是水平方向。
50
51  **图4** 列表的主轴与交叉轴  
52
53![zh-cn_image_0000001562940581](figures/zh-cn_image_0000001562940581.png)
54
55如果List组件主轴或交叉轴方向设置了尺寸,则其对应方向上的尺寸为设置值。
56
57如果List组件主轴方向没有设置尺寸,当List子组件主轴方向总尺寸小于List的父组件尺寸时,List主轴方向尺寸自动适应子组件的总尺寸。
58
59如下图所示,一个垂直列表B没有设置高度时,其父组件A高度为200vp,若其所有子组件C的高度总和为150vp,则此时列表B的高度为150vp。
60
61  **图5** 列表主轴高度约束示例1(**A**: List的父组件; **B**: List组件; **C**: List的所有子组件)  
62
63![zh-cn_image_0000001511580956](figures/zh-cn_image_0000001511580956.png)
64
65如果子组件主轴方向总尺寸超过List父组件尺寸时,List主轴方向尺寸适应List的父组件尺寸。
66
67如下图所示,同样是没有设置高度的垂直列表B,其父组件A高度为200vp,若其所有子组件C的高度总和为300vp,则此时列表B的高度为200vp。
68
69  **图6** 列表主轴高度约束示例2(**A**: List的父组件; **B**: List组件; **C**: List的所有子组件)  
70
71![zh-cn_image_0000001511740548](figures/zh-cn_image_0000001511740548.png)
72
73List组件交叉轴方向在没有设置尺寸时,其尺寸默认自适应父组件尺寸。
74
75
76## 开发布局
77
78
79### 设置主轴方向
80
81List组件主轴默认是垂直方向,即默认情况下不需要手动设置List方向,就可以构建一个垂直滚动列表。
82
83若是水平滚动列表场景,将List的listDirection属性设置为Axis.Horizontal即可实现。listDirection默认为Axis.Vertical,即主轴默认是垂直方向。
84
85
86```ts
87List() {
88  ...
89}
90.listDirection(Axis.Horizontal)
91```
92
93
94### 设置交叉轴布局
95
96List组件的交叉轴布局可以通过lanes和alignListItem属性进行设置,lanes属性用于确定交叉轴排列的列表项数量,alignListItem用于设置子组件在交叉轴方向的对齐方式。
97
98List组件的lanes属性通常用于在不同尺寸的设备自适应构建不同行数或列数的列表,即一次开发、多端部署的场景,例如[歌单列表](../key-features/multi-device-app-dev/music-album-page.md#%E6%AD%8C%E5%8D%95%E5%88%97%E8%A1%A8)。lanes属性的取值类型是"number | [LengthConstrain](../reference/arkui-ts/ts-types.md/#lengthconstrain)",即整数或者LengthConstrain类型。以垂直列表为例,如果将lanes属性设为2,表示构建的是一个两列的垂直列表,如图2中右图所示。lanes的默认值为1,即默认情况下,垂直列表的列数是1。
99
100
101```ts
102List() {
103  ...
104}
105.lanes(2)
106```
107
108当其取值为LengthConstrain类型时,表示会根据LengthConstrain与List组件的尺寸自适应决定行或列数。
109
110
111```ts
112List() {
113  ...
114}
115.lanes({ minLength: 200, maxLength: 300 })
116```
117
118例如,假设在垂直列表中设置了lanes的值为{ minLength: 200, maxLength: 300 }。此时,
119
120- 当List组件宽度为300vp时,由于minLength为200vp,此时列表为一列。
121
122- 当List组件宽度变化至400vp时,符合两倍的minLength,则此时列表自适应为两列。
123
124>**说明:**
125>
126>当lanes为LengthConstrain类型时,仅用于计算当前列表的行或列数,不影响列表项本身的尺寸。
127
128同样以垂直列表为例,当alignListItem属性设置为ListItemAlign.Center表示列表项在水平方向上居中对齐。alignListItem的默认值是ListItemAlign.Start,即列表项在列表交叉轴方向上默认按首部对齐。
129
130
131```ts
132List() {
133  ...
134}
135.alignListItem(ListItemAlign.Center)
136```
137
138
139## 在列表中显示数据
140
141列表视图垂直或水平显示项目集合,在行或列超出屏幕时提供滚动功能,使其适合显示大型数据集合。在最简单的列表形式中,List静态地创建其列表项ListItem的内容。
142
143  **图7** 城市列表  
144
145![zh-cn_image_0000001563060761](figures/zh-cn_image_0000001563060761.png)
146
147```ts
148@Component
149struct CityList {
150  build() {
151    List() {
152      ListItem() {
153        Text('北京').fontSize(24)
154      }
155
156      ListItem() {
157        Text('杭州').fontSize(24)
158      }
159
160      ListItem() {
161        Text('上海').fontSize(24)
162      }
163    }
164    .backgroundColor('#FFF1F3F5')
165    .alignListItem(ListItemAlign.Center)
166  }
167}
168```
169
170由于在ListItem中只能有一个根节点组件,不支持以平铺形式使用多个组件。因此,若列表项是由多个组件元素组成的,则需要将这多个元素组合到一个容器组件内或组成一个自定义组件。
171
172  **图8** 联系人列表项示例  
173
174![zh-cn_image_0000001511421328](figures/zh-cn_image_0000001511421328.png)
175
176如上图所示,联系人列表的列表项中,每个联系人都有头像和名称。此时,需要将Image和Text封装到一个Row容器内。
177
178
179```ts
180List() {
181  ListItem() {
182    Row() {
183      Image($r('app.media.iconE'))
184        .width(40)
185        .height(40)
186        .margin(10)
187
188      Text('小明')
189        .fontSize(20)
190    }
191  }
192
193  ListItem() {
194    Row() {
195      Image($r('app.media.iconF'))
196        .width(40)
197        .height(40)
198        .margin(10)
199
200      Text('小红')
201        .fontSize(20)
202    }
203  }
204}
205```
206
207
208## 迭代列表内容
209
210通常更常见的是,应用通过数据集合动态地创建列表。使用[循环渲染](../quick-start/arkts-rendering-control-foreach.md)可从数据源中迭代获取数据,并在每次迭代过程中创建相应的组件,降低代码复杂度。
211
212ArkTS通过[ForEach](../quick-start/arkts-rendering-control-foreach.md)提供了组件的循环渲染能力。以简单形式的联系人列表为例,将联系人名称和头像数据以Contact类结构存储到contacts数组,使用ForEach中嵌套ListItem的形式来代替多个平铺的、内容相似的ListItem,从而减少重复代码。
213
214
215```ts
216import util from '@ohos.util';
217
218class Contact {
219  key: string = util.generateRandomUUID(true);
220  name: string;
221  icon: Resource;
222
223  constructor(name: string, icon: Resource) {
224    this.name = name;
225    this.icon = icon;
226  }
227}
228
229@Entry
230@Component
231struct SimpleContacts {
232  private contacts = [
233    new Contact('小明', $r("app.media.iconA")),
234    new Contact('小红', $r("app.media.iconB")),
235    ...
236  ]
237
238  build() {
239    List() {
240      ForEach(this.contacts, (item: Contact) => {
241        ListItem() {
242          Row() {
243            Image(item.icon)
244              .width(40)
245              .height(40)
246              .margin(10)
247            Text(item.name).fontSize(20)
248          }
249          .width('100%')
250          .justifyContent(FlexAlign.Start)
251        }
252      }, item => item.key)
253    }
254    .width('100%')
255  }
256}
257```
258
259在List组件中,ForEach除了可以用来循环渲染ListItem,也可以用来循环渲染ListItemGroup。ListItemGroup的循环渲染详细使用请参见[支持分组列表](#支持分组列表)。
260
261
262## 自定义列表样式
263
264
265### 设置内容间距
266
267在初始化列表时,如需在列表项之间添加间距,可以使用space参数。例如,在每个列表项之间沿主轴方向添加10vp的间距:
268
269
270```ts
271List({ space: 10 }) {
272  ...
273}
274```
275
276
277### 添加分隔线
278
279分隔线用来将界面元素隔开,使单个元素更加容易识别。如下图所示,当列表项左边有图标(如蓝牙图标),由于图标本身就能很好的区分,此时分隔线从图标之后开始显示即可。
280
281  **图9** 设置列表分隔线样式  
282
283![zh-cn_image_0000001511580960](figures/zh-cn_image_0000001511580960.png)
284
285List提供了divider属性用于给列表项之间添加分隔线。在设置divider属性时,可以通过strokeWidth和color属性设置分隔线的粗细和颜色。
286
287startMargin和endMargin属性分别用于设置分隔线距离列表侧边起始端的距离和距离列表侧边结束端的距离。
288
289
290```ts
291List() {
292  ...
293}
294.divider({
295  strokeWidth: 1,
296  startMargin: 60,
297  endMargin: 10,
298  color: '#ffe9f0f0'
299})
300```
301
302此示例表示从距离列表侧边起始端60vp开始到距离结束端10vp的位置,画一条粗细为1vp的分割线,可以实现图8设置列表分隔线的样式。
303
304>**说明:**
305>
306>1. 分隔线的宽度会使ListItem之间存在一定间隔,当List设置的内容间距小于分隔线宽度时,ListItem之间的间隔会使用分隔线的宽度。
307>
308>2. 当List存在多列时,分割线的startMargin和endMargin作用于每一列上。
309>
310>3. List组件的分隔线画在两个ListItem之间,第一个ListItem上方和最后一个ListItem下方不会绘制分隔线。
311
312
313### 添加滚动条
314
315当列表项高度(宽度)超出屏幕高度(宽度)时,列表可以沿垂直(水平)方向滚动。在页面内容很多时,若用户需快速定位,可拖拽滚动条,如下图所示。
316
317  **图10** 列表的滚动条 
318
319![zh-cn_image_0000001511740544](figures/zh-cn_image_0000001511740544.gif)
320
321在使用List组件时,可通过scrollBar属性控制列表滚动条的显示。scrollBar的取值类型为[BarState](../reference/arkui-ts/ts-appendix-enums.md/#barstate),当取值为BarState.Auto表示按需显示滚动条。此时,当触摸到滚动条区域时显示控件,可上下拖拽滚动条快速浏览内容,拖拽时会变粗。若不进行任何操作,2秒后滚动条自动消失。
322
323
324```ts
325List() {
326  ...
327}
328.scrollBar(BarState.Auto)
329```
330
331
332## 支持分组列表
333
334在列表中支持数据的分组展示,可以使列表显示结构清晰,查找方便,从而提高使用效率。分组列表在实际应用中十分常见,如下图所示联系人列表。
335
336  **图11** 联系人分组列表 
337
338![zh-cn_image_0000001511580948](figures/zh-cn_image_0000001511580948.png)
339
340在List组件中使用ListItemGroup对项目进行分组,可以构建二维列表。
341
342在List组件中可以直接使用一个或者多个ListItemGroup组件,ListItemGroup的宽度默认充满List组件。在初始化ListItemGroup时,可通过header参数设置列表分组的头部组件。
343
344
345```ts
346@Component
347struct ContactsList {
348  ...
349
350  @Builder itemHead(text: string) {
351    // 列表分组的头部组件,对应联系人分组A、B等位置的组件
352    Text(text)
353      .fontSize(20)
354      .backgroundColor('#fff1f3f5')
355      .width('100%')
356      .padding(5)
357  }
358
359  build() {
360    List() {
361      ListItemGroup({ header: this.itemHead('A') }) {
362        // 循环渲染分组A的ListItem
363        ...
364      }
365      ...
366
367      ListItemGroup({ header: this.itemHead('B') }) {
368        // 循环渲染分组B的ListItem
369        ...
370      }
371      ...
372    }
373  }
374}
375```
376
377如果多个ListItemGroup结构类似,可以将多个分组的数据组成数组,然后使用ForEach对多个分组进行循环渲染。例如在联系人列表中,将每个分组的联系人数据contacts(可参考[迭代列表内容](#迭代列表内容)章节)和对应分组的标题title数据进行组合,定义为数组contactsGroups。
378
379
380```ts
381contactsGroups: object[] = [
382  {
383    title: 'A',
384    contacts: [
385      new Contact('艾佳', $r('app.media.iconA')),
386      new Contact('安安', $r('app.media.iconB')),
387      new Contact('Angela', $r('app.media.iconC')),
388    ],
389  },
390  {
391    title: 'B',
392    contacts: [
393      new Contact('白叶', $r('app.media.iconD')),
394      new Contact('伯明', $r('app.media.iconE')),
395    ],
396  },
397  ...
398]
399```
400
401然后在ForEach中对contactsGroups进行循环渲染,即可实现多个分组的联系人列表。
402
403
404```ts
405List() {
406  // 循环渲染ListItemGroup,contactsGroups为多个分组联系人contacts和标题title的数据集合
407  ForEach(this.contactsGroups, item => {
408    ListItemGroup({ header: this.itemHead(item.title) }) {
409      // 循环渲染ListItem
410      ForEach(item.contacts, (contact) => {
411        ListItem() {
412          ...
413        }
414      }, item => item.key)
415    }
416    ...
417  })
418}
419```
420
421
422## 添加粘性标题
423
424粘性标题是一种常见的标题模式,常用于定位字母列表的头部元素。如下图所示,在联系人列表中滚动A部分时,B部分开始的头部元素始终处于A的下方。而在开始滚动B部分时,B的头部会固定在屏幕顶部,直到所有B的项均完成滚动后,才被后面的头部替代。
425
426粘性标题不仅有助于阐明列表中数据的表示形式和用途,还可以帮助用户在大量信息中进行数据定位,从而避免用户在标题所在的表的顶部与感兴趣区域之间反复滚动。
427
428  **图12** 粘性标题  
429
430![zh-cn_image_0000001511740552](figures/zh-cn_image_0000001511740552.gif)
431
432List组件的sticky属性配合ListItemGroup组件使用,用于设置ListItemGroup中的头部组件是否呈现吸顶效果或者尾部组件是否呈现吸底效果。
433
434通过给List组件设置sticky属性为StickyStyle.Header,即可实现列表的粘性标题效果。如果需要支持吸底效果,可以通过footer参数初始化ListItemGroup的底部组件,并将sticky属性设置为StickyStyle.Footer435
436
437```ts
438@Component
439struct ContactsList {
440  // 定义分组联系人数据集合contactsGroups数组
441  ...
442
443  @Builder itemHead(text: string) {
444    // 列表分组的头部组件,对应联系人分组A、B等位置的组件
445    Text(text)
446      .fontSize(20)
447      .backgroundColor('#fff1f3f5')
448      .width('100%')
449      .padding(5)
450  }
451
452  build() {
453    List() {
454      // 循环渲染ListItemGroup,contactsGroups为多个分组联系人contacts和标题title的数据集合
455      ForEach(this.contactsGroups, item => {
456        ListItemGroup({ header: this.itemHead(item.title) }) {
457          // 循环渲染ListItem
458          ForEach(item.contacts, (contact) => {
459            ListItem() {
460              ...
461            }
462          }, item => item.key)
463        }
464        ...
465      })
466    }
467    .sticky(StickyStyle.Header)  // 设置吸顶,实现粘性标题效果
468  }
469}
470```
471
472
473## 控制滚动位置
474
475控制滚动位置在实际应用中十分常见,例如当新闻页列表项数量庞大,用户滚动列表到一定位置时,希望快速滚动到列表底部或返回列表顶部。此时,可以通过控制滚动位置来实现列表的快速定位,如下图所示。
476
477  **图13** 返回列表顶部  
478
479![zh-cn_image_0000001511900520](figures/zh-cn_image_0000001511900520.gif)
480
481List组件初始化时,可以通过scroller参数绑定一个[Scroller](../reference/arkui-ts/ts-container-scroll.md/#scroller)对象,进行列表的滚动控制。例如,用户在新闻应用中,点击新闻页面底部的返回顶部按钮时,就可以通过Scroller对象的scrollToIndex方法使列表滚动到指定的列表项索引位置。
482
483首先,需要创建一个Scroller的对象listScroller。
484
485
486```ts
487private listScroller: Scroller = new Scroller();
488```
489
490然后,通过将listScroller用于初始化List组件的scroller参数,完成listScroller与列表的绑定。在需要跳转的位置指定scrollToIndex的参数为0,表示返回列表顶部。
491
492
493```ts
494Stack({ alignContent: Alignment.BottomEnd }) {
495  // 将listScroller用于初始化List组件的scroller参数,完成listScroller与列表的绑定。
496  List({ space: 20, scroller: this.listScroller }) {
497    ...
498  }
499  ...
500
501  Button() {
502    ...
503  }
504  .onClick(() => {
505    // 点击按钮时,指定跳转位置,返回列表顶部
506    this.listScroller.scrollToIndex(0)
507  })
508  ...
509}
510```
511
512
513## 响应滚动位置
514
515许多应用需要监听列表的滚动位置变化并作出响应。例如,在联系人列表滚动时,如果跨越了不同字母开头的分组,则侧边字母索引栏也需要更新到对应的字母位置。
516
517除了字母索引之外,滚动列表结合多级分类索引在应用开发过程中也很常见,例如购物应用的商品分类页面,多级分类也需要监听列表的滚动位置。
518
519**图14** 字母索引响应联系人列表滚动  
520
521![zh-cn_image_0000001563060769](figures/zh-cn_image_0000001563060769.gif)
522
523如上图所示,当联系人列表从A滚动到B时,右侧索引栏也需要同步从选中A状态变成选中B状态。此场景可以通过监听List组件的onScrollIndex事件来实现,右侧索引栏需要使用字母表索引组件[AlphabetIndexer](../reference/arkui-ts/ts-container-alphabet-indexer.md/)。
524
525在列表滚动时,根据列表此时所在的索引值位置firstIndex,重新计算字母索引栏对应字母的位置selectedIndex。由于AlphabetIndexer组件通过selected属性设置了选中项索引值,当selectedIndex变化时会触发AlphabetIndexer组件重新渲染,从而显示为选中对应字母的状态。
526
527
528```ts
529...
530const alphabets = ['#', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
531  'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
532
533@Entry
534@Component
535struct ContactsList {
536  @State selectedIndex: number = 0;
537  private listScroller: Scroller = new Scroller();
538  ...
539
540  build() {
541    Stack({ alignContent: Alignment.End }) {
542      List({ scroller: this.listScroller }) {
543        ...
544      }
545      .onScrollIndex((firstIndex: number) => {
546        // 根据列表滚动到的索引值,重新计算对应联系人索引栏的位置this.selectedIndex
547        ...
548      })
549      ...
550
551      // 字母表索引组件
552      AlphabetIndexer({ arrayValue: alphabets, selected: 0 })
553        .selected(this.selectedIndex)
554      ...
555    }
556  }
557}
558```
559
560>**说明:**
561>
562>计算索引值时,ListItemGroup作为一个整体占一个索引值,不计算ListItemGroup内部ListItem的索引值。
563
564
565## 响应列表项侧滑
566
567侧滑菜单在许多应用中都很常见。例如,通讯类应用通常会给消息列表提供侧滑删除功能,即用户可以通过向左侧滑列表的某一项,再点击删除按钮删除消息,如下图所示。
568
569  **图15** 侧滑删除列表项  
570
571![zh-cn_image_0000001563060773](figures/zh-cn_image_0000001563060773.gif)
572
573ListItem的swipeAction属性可用于实现列表项的左右滑动功能。swipeAction属性方法初始化时有必填参数SwipeActionOptions,其中,start参数表示设置列表项右滑时起始端滑出的组件,end参数表示设置列表项左滑时尾端滑出的组件。
574
575在消息列表中,end参数表示设置ListItem左滑时尾端划出自定义组件,即删除按钮。在初始化end方法时,将滑动列表项的索引传入删除按钮组件,当用户点击删除按钮时,可以根据索引值来删除列表项对应的数据,从而实现侧滑删除功能。
576
577
578```ts
579@Entry
580@Component
581struct MessageList {
582  @State messages: object[] = [
583    // 初始化消息列表数据
584    ...
585  ];
586
587  @Builder itemEnd(index: number) {
588    // 侧滑后尾端出现的组件
589    Button({ type: ButtonType.Circle }) {
590      Image($r('app.media.ic_public_delete_filled'))
591        .width(20)
592        .height(20)
593    }
594    .onClick(() => {
595      this.messages.splice(index, 1);
596    })
597    ...
598  }
599
600  build() {
601    ...
602      List() {
603        ForEach(this.messages, (item, index) => {
604          ListItem() {
605            ...
606          }
607          .swipeAction({ end: this.itemEnd.bind(this, index) }) // 设置侧滑属性
608        }, item => item.id.toString())
609      }
610    ...
611  }
612}
613```
614
615
616## 给列表项添加标记
617
618添加标记是一种无干扰性且直观的方法,用于显示通知或将注意力集中到应用内的某个区域。例如,当消息列表接收到新消息时,通常对应的联系人头像的右上方会出现标记,提示有若干条未读消息,如下图所示。
619
620  **图16** 给列表项添加标记  
621
622![zh-cn_image_0000001511580952](figures/zh-cn_image_0000001511580952.png)
623
624在ListItem中使用[Badge](../reference/arkui-ts/ts-container-badge.md/)组件可实现给列表项添加标记功能。Badge是可以附加在单个组件上用于信息标记的容器组件。
625
626在消息列表中,若希望在联系人头像右上角添加标记,可在实现消息列表项ListItem的联系人头像时,将头像Image组件作为Badge的子组件。
627
628在Badge组件中,count和position参数用于设置需要展示的消息数量和提示点显示位置,还可以通过style参数灵活设置标记的样式。
629
630
631```ts
632Badge({
633  count: 1,
634  position: BadgePosition.RightTop,
635  style: { badgeSize: 16, badgeColor: '#FA2A2D' }
636}) {
637  // Image组件实现消息联系人头像
638  ...
639}
640...
641```
642
643
644## 下拉刷新与上拉加载
645
646页面的下拉刷新与上拉加载功能在移动应用中十分常见,例如,新闻页面的内容刷新和加载。这两种操作的原理都是通过响应用户的[触摸事件](../reference/arkui-ts/ts-universal-events-touch.md/),在顶部或者底部显示一个刷新或加载视图,完成后再将此视图隐藏。
647
648以下拉刷新为例,其实现主要分成三步:
649
6501. 监听手指按下事件,记录其初始位置的值。
651
6522. 监听手指按压移动事件,记录并计算当前移动的位置与初始值的差值,大于0表示向下移动,同时设置一个允许移动的最大值。
653
6543. 监听手指抬起事件,若此时移动达到最大值,则触发数据加载并显示刷新视图,加载完成后将此视图隐藏。
655
656下拉刷新与上拉加载的具体实现可参考Codelab:[新闻数据加载](https://gitee.com/openharmony/codelabs/tree/master/NetworkManagement/NewsDataArkTS)。若开发者希望快速实现此功能,也可使用三方组件[PullToRefresh](https://gitee.com/openharmony-sig/PullToRefresh)657
658
659## 编辑列表
660
661列表的编辑模式用途十分广泛,常见于待办事项管理、文件管理、备忘录的记录管理等应用场景。在列表的编辑模式下,新增和删除列表项是最基础的功能,其核心是对列表项对应的数据集合进行数据添加和删除。
662
663下面以待办事项管理为例,介绍如何快速实现新增和删除列表项功能。
664
665
666### 新增列表项
667
668如下图所示,当用户点击添加按钮时,提供用户新增列表项内容选择或填写的交互界面,用户点击确定后,列表中新增对应的项目。
669
670  **图17** 新增待办  
671
672![zh-cn_image_0000001511740556](figures/zh-cn_image_0000001511740556.gif)
673
674添加列表项功能实现主要流程如下:
675
6761. 定义列表项数据结构和初始化列表数据,构建列表整体布局和列表项。
677   以待办事项管理为例,首先定义待办数据结构:
678
679   ```ts
680   import util from '@ohos.util';
681
682   export class ToDo {
683     key: string = util.generateRandomUUID(true);
684     name: string;
685
686     constructor(name: string) {
687       this.name = name;
688     }
689   }
690   ```
691
692    然后,初始化待办列表数据和可选事项:
693
694   ```ts
695   @State toDoData: ToDo[] = [];
696   private availableThings: string[] = ['读书', '运动', '旅游', '听音乐', '看电影', '唱歌'];
697   ```
698
699   最后,构建列表布局和列表项:
700
701   ```ts
702   List({ space: 10 }) {
703     ForEach(this.toDoData, (toDoItem) => {
704       ListItem() {
705         ...
706       }
707     }, toDoItem => toDoItem.key)
708   }
709   ```
710
7112. 提供新增列表项入口,即给新增按钮添加点击事件。
712
7133. 响应用户确定新增事件,更新列表数据。
714   待办事项管理示例的步骤2和步骤3功能实现如下:
715
716   ```ts
717   Text('+')
718     .onClick(() => {
719       TextPickerDialog.show({
720         range: this.availableThings,
721         onAccept: (value: TextPickerResult) => {
722            this.toDoData.push(new ToDo(this.availableThings[value.index])); // 新增列表项数据toDoData
723         },
724       })
725     })
726   ```
727
728
729### 删除列表项
730
731如下图所示,当用户长按列表项进入删除模式时,提供用户删除列表项选择的交互界面,用户勾选完成后点击删除按钮,列表中删除对应的项目。
732
733  **图18** 长按删除待办事项  
734
735![zh-cn_image_0000001562820877](figures/zh-cn_image_0000001562820877.gif)
736
737删除列表项功能实现主要流程如下:
738
7391. 列表的删除功能一般进入编辑模式后才可使用,所以需要提供编辑模式的入口。
740   以待办列表为例,通过监听列表项的长按事件,当用户长按列表项时,进入编辑模式。
741
742   ```ts
743   // ToDoListItem.ets
744
745   Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
746     ...
747   }
748   .gesture(
749   GestureGroup(GestureMode.Exclusive,
750     LongPressGesture()
751       .onAction(() => {
752         if (!this.isEditMode) {
753           this.isEditMode = true; //进入编辑模式
754           this.selectedItems.push(this.toDoItem); // 记录长按时选中的列表项
755         }
756       })
757     )
758   )
759   ```
760
7612. 需要响应用户的选择交互,记录要删除的列表项数据。
762   在待办列表中,通过勾选框的勾选或取消勾选,响应用户勾选列表项变化,记录所有选择的列表项。
763
764   ```ts
765   // ToDoListItem.ets
766
767   if (this.isEditMode) {
768     Checkbox()
769       .onChange((isSelected) => {
770         if (isSelected) {
771           this.selectedItems.push(this.toDoItem) // 勾选时,记录选中的列表项
772         } else {
773           let index = this.selectedItems.indexOf(this.toDoItem)
774           if (index !== -1) {
775             this.selectedItems.splice(index, 1) // 取消勾选时,则将此项从selectedItems中删除
776           }
777         }
778       })
779       ...
780   }
781   ```
782
7833. 需要响应用户点击删除按钮事件,删除列表中对应的选项。
784
785   ```ts
786   // ToDoList.ets
787
788   Button('删除')
789     .onClick(() => {
790       // 删除选中的列表项对应的toDoData数据
791       let leftData = this.toDoData.filter((item) => {
792         return this.selectedItems.find((selectedItem) => selectedItem !== item);
793       })
794
795       this.toDoData = leftData;
796       this.isEditMode = false;
797     })
798     ...
799   ```
800
801
802## 长列表的处理
803
804[循环渲染](../quick-start/arkts-rendering-control-foreach.md)适用于短列表,当构建具有大量列表项的长列表时,如果直接采用循环渲染方式,会一次性加载所有的列表元素,会导致页面启动时间过长,影响用户体验。因此,推荐使用[数据懒加载](../quick-start/arkts-rendering-control-lazyforeach.md)(LazyForEach)方式实现按需迭代加载数据,从而提升列表性能。
805
806关于长列表按需加载优化的具体实现可参考[数据懒加载](../quick-start/arkts-rendering-control-lazyforeach.md)章节中的示例。
807
808当使用懒加载方式渲染列表时,为了更好的列表滚动体验,减少列表滑动时出现白块,List组件提供了cachedCount参数用于设置列表项缓存数,只在懒加载LazyForEach中生效。
809
810
811```ts
812List() {
813  LazyForEach(this.dataSource, item => {
814    ListItem() {
815      ...
816    }
817  })
818}.cachedCount(3)
819```
820
821以垂直列表为例:
822
823- 若懒加载是用于ListItem,当列表为单列模式时,会在List显示的ListItem前后各缓存cachedCount个ListItem;若是多列模式下,会在List显示的ListItem前后各缓存cachedCount\*列数个ListItem。
824
825- 若懒加载是用于ListItemGroup,无论单列模式还是多列模式,都是在List显示的ListItem前后各缓存cachedCount个ListItemGroup。
826
827>**说明:**
828>
829>1. cachedCount的增加会增大UI的CPU、内存开销。使用时需要根据实际情况,综合性能和用户体验进行调整。
830>
831>2. 列表使用数据懒加载时,除了显示区域的列表项和前后缓存的列表项,其他列表项会被销毁。
832
833## 相关实例
834
835如需详细了解ArkUI中列表的创建与使用,请参考以下示例:
836
837- [List组件的使用之商品列表](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/List)
838
839- [新闻数据加载](https://gitee.com/openharmony/codelabs/tree/master/NetworkManagement/NewsDataArkTS)
840
841- [音乐专辑页](../key-features/multi-device-app-dev/music-album-page.md)
842