• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 创建列表 (List)
2
3
4## 概述
5
6列表是一种复杂的容器,当列表项达到一定数量,内容超过屏幕大小时,可以自动提供滚动功能。它适合用于呈现同类数据类型或数据类型集,例如图片和文本。在列表中显示数据集合是许多应用程序中的常见要求(如通讯录、音乐列表、购物清单等)。
7
8使用列表可以轻松高效地显示结构化、可滚动的信息。通过在[List](../reference/apis-arkui/arkui-ts/ts-container-list.md)组件中按垂直或者水平方向线性排列子组件[ListItemGroup](../reference/apis-arkui/arkui-ts/ts-container-listitemgroup.md)或[ListItem](../reference/apis-arkui/arkui-ts/ts-container-listitem.md),为列表中的行或列提供单个视图,或使用[循环渲染](../ui/state-management/arkts-rendering-control-foreach.md)迭代一组行或列,或混合任意数量的单个视图和ForEach结构,构建一个列表。List组件支持使用条件渲染、循环渲染、懒加载等[渲染控制](../ui/state-management/arkts-rendering-control-overview.md)方式生成子组件。
9
10在圆形屏幕设备上,推荐使用[ArcList](../reference/apis-arkui/arkui-ts/ts-container-arclist.md)组件,使用方式可参考[创建弧形列表 (ArcList)](./arkts-layout-development-create-arclist.md)。
11
12## 布局与约束
13
14列表作为一种容器,会自动按其滚动方向排列子组件,向列表中添加组件或从列表中移除组件会重新排列子组件。
15
16如下图所示,在垂直列表中,List按垂直方向自动排列ListItemGroup或ListItem。
17
18ListItemGroup用于列表数据的分组展示,其子组件也是ListItem。ListItem表示单个列表项,可以包含单个子组件。
19
20  **图1** List、ListItemGroup和ListItem组件关系  
21
22![zh-cn_image_0000001562940589](figures/zh-cn_image_0000001562940589.png)
23
24>**说明:**
25>
26>List的子组件必须是ListItemGroup或ListItem,ListItem和ListItemGroup必须配合List来使用。
27
28
29### 布局
30
31List除了提供垂直和水平布局能力、超出屏幕时可以滚动的自适应延伸能力之外,还提供了自适应交叉轴方向上排列个数的布局能力。
32
33利用垂直布局能力可以构建单列或者多列垂直滚动列表,如下图所示。
34
35  **图2** 垂直滚动列表(左:单列;右:多列)  
36
37![zh-cn_image_0000001511580940](figures/zh-cn_image_0000001511580940.png)
38
39利用水平布局能力可以是构建单行或多行水平滚动列表,如下图所示。
40
41  **图3** 水平滚动列表(左:单行;右:多行)  
42
43![zh-cn_image_0000001511421344](figures/zh-cn_image_0000001511421344.png)
44
45
46Grid和WaterFlow也可以实现单列、多列布局,如果布局每列等宽,且不需要跨行跨列布局,相比Grid和WaterFlow,则更推荐使用List。
47
48### 约束
49
50列表的主轴方向是指子组件列的排列方向,也是列表的滚动方向。垂直于主轴的轴称为交叉轴,其方向与主轴方向相互垂直。
51
52如下图所示,垂直列表的主轴是垂直方向,交叉轴是水平方向;水平列表的主轴是水平方向,交叉轴是垂直方向。
53
54  **图4** 列表的主轴与交叉轴  
55
56![zh-cn_image_0000001562940581](figures/zh-cn_image_0000001562940581.png)
57
58如果List组件主轴或交叉轴方向设置了尺寸,则其对应方向上的尺寸为设置值。
59
60如果List组件主轴方向没有设置尺寸,当List子组件主轴方向总尺寸小于List的父组件尺寸时,List主轴方向尺寸自动适应子组件的总尺寸。
61
62如下图所示,一个垂直列表B没有设置高度时,其父组件A高度为200vp,若其所有子组件C的高度总和为150vp,则此时列表B的高度为150vp。
63
64  **图5** 列表主轴高度约束示例1(**A**: List的父组件; **B**: List组件; **C**: List的所有子组件)  
65
66![zh-cn_image_0000001511580956](figures/zh-cn_image_0000001511580956.png)
67
68如果子组件主轴方向总尺寸超过List父组件尺寸时,List主轴方向尺寸适应List的父组件尺寸。
69
70如下图所示,同样是没有设置高度的垂直列表B,其父组件A高度为200vp,若其所有子组件C的高度总和为300vp,则此时列表B的高度为200vp。
71
72  **图6** 列表主轴高度约束示例2(**A**: List的父组件; **B**: List组件; **C**: List的所有子组件)  
73
74![zh-cn_image_0000001511740548](figures/zh-cn_image_0000001511740548.png)
75
76List组件交叉轴方向在没有设置尺寸时,其尺寸默认自适应父组件尺寸。
77
78
79## 开发布局
80
81
82### 设置主轴方向
83
84List组件主轴默认是垂直方向,即默认情况下不需要手动设置List方向,就可以构建一个垂直滚动列表。
85
86若是水平滚动列表场景,将List的listDirection属性设置为Axis.Horizontal即可实现。listDirection默认为Axis.Vertical,即主轴默认是垂直方向。
87
88
89```ts
90List() {
91  // ...
92}
93.listDirection(Axis.Horizontal)
94```
95
96
97### 设置交叉轴布局
98
99List组件的交叉轴布局可以通过lanes和alignListItem属性进行设置,lanes属性用于确定交叉轴排列的列表项数量,alignListItem用于设置子组件在交叉轴方向的对齐方式。
100
101List组件的lanes属性通常用于在不同尺寸的设备自适应构建不同行数或列数的列表,即一次开发、多端部署的场景。lanes属性的取值类型是"number | [LengthConstrain](../reference/apis-arkui/arkui-ts/ts-types.md#lengthconstrain)",即整数或者LengthConstrain类型。以垂直列表为例,如果将lanes属性设为2,表示构建的是一个两列的垂直列表,如图2中右图所示。lanes的默认值为1,即默认情况下,垂直列表的列数是1。
102
103
104```ts
105List() {
106  // ...
107}
108.lanes(2)
109```
110
111当其取值为LengthConstrain类型时,表示会根据LengthConstrain与List组件的尺寸自适应决定行或列数。
112
113
114```ts
115@Entry
116@Component
117struct EgLanes {
118  @State egLanes: LengthConstrain = { minLength: 200, maxLength: 300 }
119  build() {
120    List() {
121      // ...
122    }
123    .lanes(this.egLanes)
124  }
125}
126```
127
128例如,假设在垂直列表中设置了lanes的值为{ minLength: 200, maxLength: 300 }。此时:
129
130- 当List组件宽度为300vp时,由于minLength为200vp,此时列表为一列。
131
132- 当List组件宽度变化至400vp时,符合两倍的minLength,则此时列表自适应为两列。
133
134同样以垂直列表为例,当alignListItem属性设置为ListItemAlign.Center表示列表项在水平方向上居中对齐。alignListItem的默认值是ListItemAlign.Start,即列表项在列表交叉轴方向上默认按首部对齐。
135
136
137```ts
138List() {
139  // ...
140}
141.alignListItem(ListItemAlign.Center)
142```
143
144
145## 在列表中显示数据
146
147列表视图垂直或水平显示项目集合,在行或列超出屏幕时提供滚动功能,使其适合显示大型数据集合。在最简单的列表形式中,List静态地创建其列表项ListItem的内容。
148
149  **图7** 城市列表  
150
151![zh-cn_image_0000001563060761](figures/zh-cn_image_0000001563060761.png)
152
153```ts
154@Entry
155@Component
156struct CityList {
157  build() {
158    List() {
159      ListItem() {
160        Text('北京').fontSize(24)
161      }
162
163      ListItem() {
164        Text('杭州').fontSize(24)
165      }
166
167      ListItem() {
168        Text('上海').fontSize(24)
169      }
170    }
171    .backgroundColor('#FFF1F3F5')
172    .alignListItem(ListItemAlign.Center)
173  }
174}
175```
176
177由于在ListItem中只能有一个根节点组件,不支持以平铺形式使用多个组件。因此,若列表项是由多个组件元素组成的,则需要将这多个元素组合到一个容器组件内或组成一个自定义组件。
178
179  **图8** 联系人列表项示例  
180
181![zh-cn_image_0000001511421328](figures/zh-cn_image_0000001511421328.png)
182
183如上图所示,联系人列表的列表项中,每个联系人都有头像和名称。此时,需要将Image和Text封装到一个Row容器内。
184
185
186```ts
187List() {
188  ListItem() {
189    Row() {
190      Image($r('app.media.iconE'))
191        .width(40)
192        .height(40)
193        .margin(10)
194
195      Text('小明')
196        .fontSize(20)
197    }
198  }
199
200  ListItem() {
201    Row() {
202      Image($r('app.media.iconF'))
203        .width(40)
204        .height(40)
205        .margin(10)
206
207      Text('小红')
208        .fontSize(20)
209    }
210  }
211}
212```
213
214
215## 迭代列表内容
216
217通常,应用通过数据集合动态地创建列表。使用[循环渲染](../ui/state-management/arkts-rendering-control-foreach.md)可从数据源中迭代获取数据,并在每次迭代过程中创建相应的组件,降低代码复杂度。
218
219ArkTS通过[ForEach](../ui/state-management/arkts-rendering-control-foreach.md)提供了组件的循环渲染能力。以简单形式的联系人列表为例,将联系人名称和头像数据以Contact类结构存储到contacts数组,使用ForEach中嵌套ListItem的形式来代替多个平铺的、内容相似的ListItem,从而减少重复代码。
220
221
222```ts
223import { util } from '@kit.ArkTS'
224
225class Contact {
226  key: string = util.generateRandomUUID(true);
227  name: string;
228  icon: Resource;
229
230  constructor(name: string, icon: Resource) {
231    this.name = name;
232    this.icon = icon;
233  }
234}
235
236@Entry
237@Component
238struct SimpleContacts {
239  private contacts: Array<object> = [
240    new Contact('小明', $r("app.media.iconA")),
241    new Contact('小红', $r("app.media.iconB")),
242  ]
243
244  build() {
245    List() {
246      ForEach(this.contacts, (item: Contact) => {
247        ListItem() {
248          Row() {
249            Image(item.icon)
250              .width(40)
251              .height(40)
252              .margin(10)
253            Text(item.name).fontSize(20)
254          }
255          .width('100%')
256          .justifyContent(FlexAlign.Start)
257        }
258      }, (item: Contact) => JSON.stringify(item))
259    }
260    .width('100%')
261  }
262}
263```
264
265在List组件中,ForEach除了可以用来循环渲染ListItem,也可以用来循环渲染ListItemGroup。ListItemGroup的循环渲染详细使用请参见[支持分组列表](#支持分组列表)。
266
267
268## 自定义列表样式
269
270
271### 设置内容间距
272
273在初始化列表时,如需在列表项之间添加间距,可以使用space参数。例如,在每个列表项之间沿主轴方向添加10vp的间距:
274
275
276```ts
277List({ space: 10 }) {
278  // ...
279}
280```
281
282
283### 添加分隔线
284
285分隔线用来将界面元素隔开,使单个元素更加容易识别。如下图所示,当列表项左边有图标(如蓝牙图标),由于图标本身就能很好的区分,此时分隔线从图标之后开始显示即可。
286
287  **图9** 设置列表分隔线样式  
288
289![zh-cn_image_0000001511580960](figures/zh-cn_image_0000001511580960.png)
290
291List提供了divider属性用于给列表项之间添加分隔线。在设置divider属性时,可以通过strokeWidth和color属性设置分隔线的粗细和颜色。
292
293startMargin和endMargin属性分别用于设置分隔线距离列表侧边起始端的距离和距离列表侧边结束端的距离。
294
295
296```ts
297class DividerTmp {
298  strokeWidth: Length = 1
299  startMargin: Length = 60
300  endMargin: Length = 10
301  color: ResourceColor = '#ffe9f0f0'
302
303  constructor(strokeWidth: Length, startMargin: Length, endMargin: Length, color: ResourceColor) {
304    this.strokeWidth = strokeWidth
305    this.startMargin = startMargin
306    this.endMargin = endMargin
307    this.color = color
308  }
309}
310@Entry
311@Component
312struct EgDivider {
313  @State egDivider: DividerTmp = new DividerTmp(1, 60, 10, '#ffe9f0f0')
314  build() {
315    List() {
316      // ...
317    }
318    .divider(this.egDivider)
319  }
320}
321```
322
323此示例表示从距离列表侧边起始端60vp开始到距离结束端10vp的位置,画一条粗细为1vp的分割线,可以实现图9设置列表分隔线的样式。
324
325>**说明:**
326>
327>1. 分隔线的宽度会使ListItem之间存在一定间隔,当List设置的内容间距小于分隔线宽度时,ListItem之间的间隔会使用分隔线的宽度。
328>
329>2. 当List存在多列时,分割线的startMargin和endMargin作用于每一列上。
330>
331>3. List组件的分隔线画在两个ListItem之间,第一个ListItem上方和最后一个ListItem下方不会绘制分隔线。
332
333
334### 添加滚动条
335
336当列表项高度(宽度)超出屏幕高度(宽度)时,列表可以沿垂直(水平)方向滚动。在页面内容很多时,若用户需快速定位,可拖拽滚动条,如下图所示。
337
338  **图10** 列表的滚动条 
339
340![zh-cn_image_0000001511740544](figures/zh-cn_image_0000001511740544.gif)
341
342在使用List组件时,可通过scrollBar属性控制列表滚动条的显示。scrollBar的取值类型为[BarState](../reference/apis-arkui/arkui-ts/ts-appendix-enums.md#barstate),当取值为BarState.Auto表示按需显示滚动条。此时,当触摸到滚动条区域时显示控件,可上下拖拽滚动条快速浏览内容,拖拽时会变粗。若不进行任何操作,2秒后滚动条自动消失。
343
344scrollBar属性API version 9及以下版本默认值为BarState.Off,从API version 10版本开始默认值为BarState.Auto345```ts
346List() {
347  // ...
348}
349.scrollBar(BarState.Auto)
350```
351
352## 添加外置滚动条
353
354列表[List](../reference/apis-arkui/arkui-ts/ts-container-list.md)可与[ScrollBar](../reference/apis-arkui/arkui-ts/ts-basic-components-scrollbar.md)组件配合使用,为列表添加外置滚动条。两者通过绑定同一个[Scroller](../reference/apis-arkui/arkui-ts/ts-container-scroll.md#scroller)滚动控制器对象实现联动。
355
3561. 首先,需要创建一个[Scroller](../reference/apis-arkui/arkui-ts/ts-container-scroll.md#scroller)类型的对象listScroller。
357
358   ```ts
359   private listScroller: Scroller = new Scroller();
360   ```
361
3622. 然后,列表通过[scroller](../reference/apis-arkui/arkui-ts/ts-container-list.md#listoptions18对象说明)参数绑定滚动控制器。
363
364   ```ts
365   // listScroller初始化List组件的scroller参数,绑定listScroller与列表。
366   List({ scroller: this.listScroller }) {
367   // ...
368   }
369   ```
370
3713. 最后,滚动条通过[scroller](../reference/apis-arkui/arkui-ts/ts-basic-components-scrollbar.md#scrollbaroptions对象说明)参数绑定滚动控制器。
372
373   ```ts
374   // listScroller初始化ScrollBar组件的scroller参数,绑定listScroller与列表。
375   ScrollBar({ scroller: this.listScroller })
376   ```
377
378  **图11** 列表的外置滚动条
379
380![ScrollBar](figures/list_scrollbar.gif)
381
382>**说明:**
383>- 滚动条组件[ScrollBar](../reference/apis-arkui/arkui-ts/ts-basic-components-scrollbar.md),还可配合其他可滚动组件使用,如[ArcList](../reference/apis-arkui/arkui-ts/ts-container-arclist.md)、[Grid](../reference/apis-arkui/arkui-ts/ts-container-grid.md)、[Scroll](../reference/apis-arkui/arkui-ts/ts-container-scroll.md)、[WaterFlow](../reference/apis-arkui/arkui-ts/ts-container-waterflow.md)。
384>- 在圆形屏幕设备上,[list](../reference/apis-arkui/arkui-ts/ts-container-list.md)可以与弧形滚动条组件[ArcScrollBar](../reference/apis-arkui/arkui-ts/ts-basic-components-arcscrollbar.md)配合使用为列表添加弧形外置滚动条,使用方式可参考[创建弧形列表 (ArcList)](./arkts-layout-development-create-arclist.md)的[添加外置滚动条ArcScrollBar](./arkts-layout-development-create-arclist.md#添加外置滚动条arcscrollbar)章节。
385
386## 支持分组列表
387
388在列表中支持数据的分组展示,可以使列表显示结构清晰,查找方便,从而提高使用效率。分组列表在实际应用中十分常见,如下图所示联系人列表。
389
390  **图12** 联系人分组列表 
391
392![zh-cn_image_0000001511580948](figures/zh-cn_image_0000001511580948.png)
393
394在List组件中使用ListItemGroup对项目进行分组,可以构建二维列表。
395
396在List组件中可以直接使用一个或者多个ListItemGroup组件,ListItemGroup的宽度默认充满List组件。在初始化ListItemGroup时,可通过header参数设置列表分组的头部组件。
397
398
399```ts
400@Entry
401@Component
402struct ContactsList {
403
404  @Builder itemHead(text: string) {
405    // 列表分组的头部组件,对应联系人分组A、B等位置的组件
406    Text(text)
407      .fontSize(20)
408      .backgroundColor('#fff1f3f5')
409      .width('100%')
410      .padding(5)
411  }
412
413  build() {
414    List() {
415      ListItemGroup({ header: this.itemHead('A') }) {
416        // 循环渲染分组A的ListItem
417      }
418
419      ListItemGroup({ header: this.itemHead('B') }) {
420        // 循环渲染分组B的ListItem
421      }
422    }
423  }
424}
425```
426
427如果多个ListItemGroup结构类似,可以将多个分组的数据组成数组,然后使用ForEach对多个分组进行循环渲染。例如在联系人列表中,将每个分组的联系人数据contacts(可参考[迭代列表内容](#迭代列表内容)章节)和对应分组的标题title数据进行组合,定义为数组contactsGroups。然后在ForEach中对contactsGroups进行循环渲染,即可实现多个分组的联系人列表。可参考[添加粘性标题](#添加粘性标题)章节示例代码。
428
429## 添加粘性标题
430
431粘性标题是一种常见的标题模式,常用于定位字母列表的头部元素。如下图所示,在联系人列表中滚动A部分时,B部分开始的头部元素始终处于A的下方。而在开始滚动B部分时,B的头部会固定在屏幕顶部,直到所有B的项均完成滚动后,才被后面的头部替代。
432
433粘性标题不仅有助于阐明列表中数据的表示形式和用途,还可以帮助用户在大量信息中进行数据定位,从而避免用户在标题所在的表的顶部与感兴趣区域之间反复滚动。
434
435  **图13** 粘性标题  
436
437![zh-cn_image_0000001511740552](figures/zh-cn_image_0000001511740552.gif)
438
439List组件的sticky属性配合ListItemGroup组件使用,用于设置ListItemGroup中的头部组件是否呈现吸顶效果或者尾部组件是否呈现吸底效果。
440
441通过给List组件设置sticky属性为StickyStyle.Header,即可实现列表的粘性标题效果。如果需要支持吸底效果,可以通过footer参数初始化ListItemGroup的底部组件,并将sticky属性设置为StickyStyle.Footer442
443
444```ts
445import { util } from '@kit.ArkTS'
446class Contact {
447  key: string = util.generateRandomUUID(true);
448  name: string;
449  icon: Resource;
450
451  constructor(name: string, icon: Resource) {
452    this.name = name;
453    this.icon = icon;
454  }
455}
456class ContactsGroup {
457  title: string = ''
458  contacts: Array<object> | null = null
459  key: string = ""
460}
461export let contactsGroups: object[] = [
462  {
463    title: 'A',
464    contacts: [
465      new Contact('艾佳', $r('app.media.iconA')),
466      new Contact('安安', $r('app.media.iconB')),
467      new Contact('Angela', $r('app.media.iconC')),
468    ],
469    key: util.generateRandomUUID(true)
470  } as ContactsGroup,
471  {
472    title: 'B',
473    contacts: [
474      new Contact('白叶', $r('app.media.iconD')),
475      new Contact('伯明', $r('app.media.iconE')),
476    ],
477    key: util.generateRandomUUID(true)
478  } as ContactsGroup,
479  // ...
480]
481@Entry
482@Component
483struct ContactsList {
484  // 定义分组联系人数据集合contactsGroups数组
485  @Builder itemHead(text: string) {
486    // 列表分组的头部组件,对应联系人分组A、B等位置的组件
487    Text(text)
488      .fontSize(20)
489      .backgroundColor('#fff1f3f5')
490      .width('100%')
491      .padding(5)
492  }
493  build() {
494    List() {
495      // 循环渲染ListItemGroup,contactsGroups为多个分组联系人contacts和标题title的数据集合
496      ForEach(contactsGroups, (itemGroup: ContactsGroup) => {
497        ListItemGroup({ header: this.itemHead(itemGroup.title) }) {
498          // 循环渲染ListItem
499          if (itemGroup.contacts) {
500            ForEach(itemGroup.contacts, (item: Contact) => {
501              ListItem() {
502                // ...
503              }
504            }, (item: Contact) => JSON.stringify(item))
505          }
506        }
507      }, (itemGroup: ContactsGroup) => JSON.stringify(itemGroup))
508    }.sticky(StickyStyle.Header)  // 设置吸顶,实现粘性标题效果
509  }
510}
511```
512
513
514## 控制滚动位置
515
516控制滚动位置在实际应用中十分常见,例如当新闻页列表项数量庞大,用户滚动列表到一定位置时,希望快速滚动到列表底部或返回列表顶部。此时,可以通过控制滚动位置来实现列表的快速定位,如下图所示。
517
518  **图14** 返回列表顶部  
519
520![zh-cn_image_0000001511900520](figures/zh-cn_image_0000001511900520.gif)
521
522List组件初始化时,可以通过scroller参数绑定一个[Scroller](../reference/apis-arkui/arkui-ts/ts-container-scroll.md#scroller)对象,进行列表的滚动控制。例如,用户在新闻应用中,点击新闻页面底部的返回顶部按钮时,就可以通过Scroller对象的scrollToIndex方法使列表滚动到指定的列表项索引位置。
523
524首先,需要创建一个Scroller的对象listScroller。
525
526
527```ts
528private listScroller: Scroller = new Scroller();
529```
530
531然后,通过将listScroller用于初始化List组件的scroller参数,完成listScroller与列表的绑定。在需要跳转的位置指定scrollToIndex的参数为0,表示返回列表顶部。
532
533
534```ts
535Stack({ alignContent: Alignment.Bottom }) {
536  // 将listScroller用于初始化List组件的scroller参数,完成listScroller与列表的绑定。
537  List({ space: 20, scroller: this.listScroller }) {
538    // ...
539  }
540
541  Button() {
542    // ...
543  }
544  .onClick(() => {
545    // 点击按钮时,指定跳转位置,返回列表顶部
546    this.listScroller.scrollToIndex(0)
547  })
548}
549```
550
551
552## 响应滚动位置
553
554许多应用需要监听列表的滚动位置变化并作出响应。例如,在联系人列表滚动时,如果跨越了不同字母开头的分组,则侧边字母索引栏也需要更新到对应的字母位置。
555
556除了字母索引之外,滚动列表结合多级分类索引在应用开发过程中也很常见,例如购物应用的商品分类页面,多级分类也需要监听列表的滚动位置。
557
558**图15** 字母索引响应联系人列表滚动  
559
560![zh-cn_image_0000001563060769](figures/zh-cn_image_0000001563060769.gif)
561
562如上图所示,当联系人列表从A滚动到B时,右侧索引栏也需要同步从选中A状态变成选中B状态。此场景可以通过监听List组件的onScrollIndex事件来实现,右侧索引栏需要使用字母表索引组件[AlphabetIndexer](../reference/apis-arkui/arkui-ts/ts-container-alphabet-indexer.md)。
563
564在列表滚动时,根据列表此时所在的索引值位置firstIndex,重新计算字母索引栏对应字母的位置selectedIndex。由于AlphabetIndexer组件通过selected属性设置了选中项索引值,当selectedIndex变化时会触发AlphabetIndexer组件重新渲染,从而显示为选中对应字母的状态。
565
566
567```ts
568const alphabets = ['#', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
569  'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
570@Entry
571@Component
572struct ContactsList {
573  @State selectedIndex: number = 0;
574  private listScroller: Scroller = new Scroller();
575
576  build() {
577    Stack({ alignContent: Alignment.End }) {
578      List({ scroller: this.listScroller }) {}
579      .onScrollIndex((firstIndex: number) => {
580        // 根据列表滚动到的索引值,重新计算对应联系人索引栏的位置this.selectedIndex
581      })
582
583      // 字母表索引组件
584      AlphabetIndexer({ arrayValue: alphabets, selected: 0 })
585        .selected(this.selectedIndex)
586    }
587  }
588}
589```
590
591>**说明:**
592>
593>计算索引值时,ListItemGroup作为一个整体占一个索引值,不计算ListItemGroup内部ListItem的索引值。
594
595
596## 响应列表项侧滑
597
598侧滑菜单在许多应用中都很常见。例如,通讯类应用通常会给消息列表提供侧滑删除功能,即用户可以通过向左侧滑列表的某一项,再点击删除按钮删除消息,如下图所示。其中,列表项头像右上角标记设置参考[给列表项添加标记](#给列表项添加标记)。
599
600**图16** 侧滑删除列表项  
601
602![zh-cn_image_0000001563060773](figures/zh-cn_image_0000001563060773.gif)
603
604ListItem的[swipeAction属性](../reference/apis-arkui/arkui-ts/ts-container-listitem.md#swipeaction9)可用于实现列表项的左右滑动功能。swipeAction属性方法初始化时有必填参数SwipeActionOptions,其中,start参数表示设置列表项右滑时起始端滑出的组件,end参数表示设置列表项左滑时尾端滑出的组件。
605
606在消息列表中,end参数表示设置ListItem左滑时尾端划出自定义组件,即删除按钮。在初始化end方法时,将滑动列表项的索引传入删除按钮组件,当用户点击删除按钮时,可以根据索引值来删除列表项对应的数据,从而实现侧滑删除功能。
607
6081. 实现尾端滑出组件的构建。
609
610    ```ts
611    @Builder itemEnd(index: number) {
612      // 构建尾端滑出组件
613      Button({ type: ButtonType.Circle }) {
614        Image($r('app.media.ic_public_delete_filled'))
615          .width(20)
616          .height(20)
617      }
618      .onClick(() => {
619        // this.messages为列表数据源,可根据实际场景构造。点击后从数据源删除指定数据项。
620        this.messages.splice(index, 1);
621      })
622    }
623    ```
624
6252. 绑定swipeAction属性到可左滑的ListItem上。
626
627    ```ts
628    // 构建List时,通过ForEach基于数据源this.messages循环渲染ListItem。
629    ListItem() {
630      // ...
631    }
632    .swipeAction({
633      end: {
634        // index为该ListItem在List中的索引值。
635        builder: () => { this.itemEnd(index) },
636      }
637    }) // 设置侧滑属性.
638    ```
639
640## 给列表项添加标记
641
642添加标记是一种无干扰性且直观的方法,用于显示通知或将注意力集中到应用内的某个区域。例如,当消息列表接收到新消息时,通常对应的联系人头像的右上方会出现标记,提示有若干条未读消息,如下图所示。
643
644  **图17** 给列表项添加标记  
645
646![zh-cn_image_0000001511580952](figures/zh-cn_image_0000001511580952.png)
647
648在ListItem中使用[Badge](../reference/apis-arkui/arkui-ts/ts-container-badge.md)组件可实现给列表项添加标记功能。Badge是可以附加在单个组件上用于信息标记的容器组件。
649
650在消息列表中,若希望在联系人头像右上角添加标记,可在实现消息列表项ListItem的联系人头像时,将头像Image组件作为Badge的子组件。
651
652在Badge组件中,count和position参数用于设置需要展示的消息数量和提示点显示位置,还可以通过style参数灵活设置标记的样式。
653
654
655```ts
656ListItem() {
657  Badge({
658    count: 1,
659    position: BadgePosition.RightTop,
660    style: { badgeSize: 16, badgeColor: '#FA2A2D' }
661  }) {
662    // Image组件实现消息联系人头像
663    // ...
664  }
665}
666```
667
668
669## 下拉刷新与上拉加载
670
671页面的下拉刷新与上拉加载功能在移动应用中十分常见,例如,新闻页面的内容刷新和加载。这两种操作的原理都是通过响应用户的[触摸事件](../reference/apis-arkui/arkui-ts/ts-universal-events-touch.md),在顶部或者底部显示一个刷新或加载视图,完成后再将此视图隐藏。
672
673以下拉刷新为例,其实现主要分成三步:
674
6751. 监听手指按下事件,记录其初始位置的值。
676
6772. 监听手指按压移动事件,记录并计算当前移动的位置与初始值的差值,大于0表示向下移动,同时设置一个允许移动的最大值。
678
6793. 监听手指抬起事件,若此时移动达到最大值,则触发数据加载并显示刷新视图,加载完成后将此视图隐藏。
680
681> **说明:**
682>
683> 页面的下拉刷新操作推荐使用[Refresh](../reference/apis-arkui/arkui-ts/ts-container-refresh.md)组件实现。
684
685<!--RP1--><!--RP1End-->
686
687<!--Del-->
688下拉刷新与上拉加载的具体实现可参考[相关实例](#相关实例)中新闻数据加载。若开发者希望快速实现此功能,也可使用三方组件[PullToRefresh](https://gitee.com/openharmony-sig/PullToRefresh)。<!--DelEnd-->
689
690
691## 编辑列表
692
693列表的编辑模式用途十分广泛,常见于待办事项管理、文件管理、备忘录的记录管理等应用场景。在列表的编辑模式下,新增和删除列表项是最基础的功能,其核心是对列表项对应的数据集合进行数据添加和删除。
694
695下面以待办事项管理为例,介绍如何快速实现新增和删除列表项功能。
696
697
698### 新增列表项
699
700如下图所示,当用户点击添加按钮时,提供用户新增列表项内容选择或填写的交互界面,用户点击确定后,列表中新增对应的项目。
701
702  **图18** 新增待办  
703
704![zh-cn_image_0000001511740556](figures/zh-cn_image_0000001511740556.gif)
705
706添加列表项功能实现主要流程如下:
707
7081. 定义列表项数据结构,以待办事项管理为例,首先定义待办数据结构。
709
710   ```ts
711   //ToDo.ets
712   import { util } from '@kit.ArkTS'
713
714   export class ToDo {
715     key: string = util.generateRandomUUID(true);
716     name: string;
717
718     constructor(name: string) {
719       this.name = name;
720     }
721   }
722   ```
723
7242. 构建列表整体布局和列表项。
725
726   ```ts
727   //ToDoListItem.ets
728   import { ToDo } from './ToDo';
729   @Component
730   export struct ToDoListItem {
731     @Link isEditMode: boolean
732     @Link selectedItems: ToDo[]
733     private toDoItem: ToDo = new ToDo("");
734
735     build() {
736      Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
737        // ...
738      }
739      .width('100%')
740      .height(80)
741      //.padding() 根据具体使用场景设置
742      .borderRadius(24)
743      //.linearGradient() 根据具体使用场景设置
744      .gesture(
745        GestureGroup(GestureMode.Exclusive,
746        LongPressGesture()
747          .onAction(() => {
748            // ...
749          })
750        )
751      )
752     }
753   }
754   ```
755
7563. 初始化待办列表数据和可选事项,最后,构建列表布局和列表项。
757
758   ```ts
759   //ToDoList.ets
760   import { ToDo } from './ToDo';
761   import { ToDoListItem } from './ToDoListItem';
762
763   @Entry
764   @Component
765   struct ToDoList {
766     @State toDoData: ToDo[] = []
767     @Watch('onEditModeChange') @State isEditMode: boolean = false
768     @State selectedItems: ToDo[] = []
769    private availableThings: string[] = ['读书', '运动', '旅游', '听音乐', '看电影', '唱歌']
770
771     onEditModeChange() {
772       if (!this.isEditMode) {
773         this.selectedItems = []
774       }
775    }
776
777     build() {
778       Column() {
779         Row() {
780           if (this.isEditMode) {
781             Text('X')
782               .fontSize(20)
783               .onClick(() => {
784                 this.isEditMode = false;
785               })
786               .margin({ left: 20, right: 20 })
787           } else {
788             Text('待办')
789               .fontSize(36)
790               .margin({ left: 40 })
791             Blank()
792             Text('+') //提供新增列表项入口,即给新增按钮添加点击事件
793               .onClick(() => {
794                 this.getUIContext().showTextPickerDialog({
795                   range: this.availableThings,
796                   onAccept: (value: TextPickerResult) => {
797                     let arr = Array.isArray(value.index) ? value.index : [value.index];
798                     for (let i = 0; i < arr.length; i++) {
799                       this.toDoData.push(new ToDo(this.availableThings[arr[i]])); // 新增列表项数据toDoData(可选事项)
800                     }
801                   },
802                 })
803               })
804           }
805           List({ space: 10 }) {
806             ForEach(this.toDoData, (toDoItem: ToDo) => {
807               ListItem() {
808                 // 将toDoData的每个数据放入到以model的形式放进ListItem里
809                 ToDoListItem({
810                   isEditMode: this.isEditMode,
811                   toDoItem: toDoItem,
812                   selectedItems: this.selectedItems })
813               }
814             }, (toDoItem: ToDo) => toDoItem.key.toString())
815           }
816         }
817       }
818     }
819   }
820   ```
821
822
823### 删除列表项
824
825如下图所示,当用户长按列表项进入删除模式时,提供用户删除列表项选择的交互界面,用户勾选完成后点击删除按钮,列表中删除对应的项目。
826
827  **图19** 长按删除待办事项  
828
829![zh-cn_image_0000001562820877](figures/zh-cn_image_0000001562820877.gif)
830
831删除列表项功能实现主要流程如下:
832
8331. 列表的删除功能一般进入编辑模式后才可使用,所以需要提供编辑模式的入口。
834   以待办列表为例,通过监听列表项的长按事件,当用户长按列表项时,进入编辑模式。
835
836    ```ts
837    // 结构参考
838    export class ToDo {
839      key: string = util.generateRandomUUID(true);
840      name: string;
841      toDoData: ToDo[] = [];
842
843      constructor(name: string) {
844        this.name = name;
845      }
846    }
847    ```
848    ```ts
849    // 实现参考
850    Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
851      // ...
852    }
853    .gesture(
854    GestureGroup(GestureMode.Exclusive,
855      LongPressGesture()
856        .onAction(() => {
857          if (!this.isEditMode) {
858            this.isEditMode = true; //进入编辑模式
859          }
860        })
861      )
862    )
863    ```
864
8652. 需要响应用户的选择交互,记录要删除的列表项数据。
866   在待办列表中,通过勾选框的勾选或取消勾选,响应用户勾选列表项变化,记录所有选择的列表项。
867
868    ```ts
869   // 结构参考
870   import { util } from '@kit.ArkTS'
871   export class ToDo {
872     key: string = util.generateRandomUUID(true);
873     name: string;
874     toDoData: ToDo[] = [];
875
876     constructor(name: string) {
877       this.name = name;
878     }
879   }
880    ```
881    ```ts
882    // 实现参考
883    if (this.isEditMode) {
884      Checkbox()
885        .onChange((isSelected) => {
886          if (isSelected) {
887            this.selectedItems.push(toDoList.toDoItem) // this.selectedItems为勾选时,记录选中的列表项,可根据实际场景构造
888          } else {
889            let index = this.selectedItems.indexOf(toDoList.toDoItem)
890            if (index !== -1) {
891              this.selectedItems.splice(index, 1) // 取消勾选时,则将此项从selectedItems中删除
892            }
893          }
894        })
895    }
896    ```
897
8983. 需要响应用户点击删除按钮事件,删除列表中对应的选项。
899
900    ```ts
901    // 结构参考
902    import { util } from '@kit.ArkTS'
903    export class ToDo {
904      key: string = util.generateRandomUUID(true);
905      name: string;
906      toDoData: ToDo[] = [];
907
908      constructor(name: string) {
909        this.name = name;
910      }
911    }
912    ```
913    ```ts
914    // 实现参考
915    Button('删除')
916      .onClick(() => {
917        // this.toDoData为待办的列表项,可根据实际场景构造。点击后删除选中的列表项对应的toDoData数据
918        let leftData = this.toDoData.filter((item) => {
919          return !this.selectedItems.find((selectedItem) => selectedItem == item);
920        })
921        this.toDoData = leftData;
922        this.isEditMode = false;
923      })
924    ```
925
926
927## 长列表的处理
928
929[循环渲染](../ui/state-management/arkts-rendering-control-foreach.md)适用于短列表,当构建具有大量列表项的长列表时,如果直接采用循环渲染方式,会一次性加载所有的列表元素,会导致页面启动时间过长,影响用户体验。因此,推荐使用[数据懒加载](../ui/state-management/arkts-rendering-control-lazyforeach.md)(LazyForEach)方式实现按需迭代加载数据,从而提升列表性能。
930
931关于长列表按需加载优化的具体实现可参考[数据懒加载](../ui/state-management/arkts-rendering-control-lazyforeach.md)章节中的示例。
932
933当使用懒加载方式渲染列表时,为了更好的列表滚动体验,减少列表滑动时出现白块,List组件提供了cachedCount参数用于设置列表项缓存数,只在懒加载LazyForEach中生效。
934
935
936```ts
937List() {
938  // ...
939}.cachedCount(3)
940```
941
942以垂直列表为例:
943
944- 若懒加载是用于ListItem,当列表为单列模式时,会在List显示的ListItem前后各缓存cachedCount个ListItem;若是多列模式下,会在List显示的ListItem前后各缓存cachedCount \* 列数个ListItem。
945
946- 若懒加载是用于ListItemGroup,无论单列模式还是多列模式,都是在List显示的ListItem前后各缓存cachedCount个ListItemGroup。
947
948>**说明:**
949>
950>1. cachedCount的增加会增大UI的CPU、内存开销。使用时需要根据实际情况,综合性能和用户体验进行调整。
951>
952>2. 列表使用数据懒加载时,除了显示区域的列表项和前后缓存的列表项,其他列表项会被销毁。
953
954
955## 折叠与展开
956
957列表项的折叠与展开用途广泛,常用于信息清单的展示、填写等应用场景。
958
959  **图20** 列表项的折叠与展开 
960
961![zh-cn_image_0000001949866104](figures/zh-cn_image_0000001949866104.gif)
962
963列表项折叠与展开效果实现主要流程如下:
964
9651. 定义列表项数据结构。
966
967    ```ts
968    interface ItemInfo {
969      index: number,
970      name: string,
971      label: ResourceStr,
972      type?: string,
973    }
974
975    interface ItemGroupInfo extends ItemInfo {
976      children: ItemInfo[]
977    }
978    ```
979
9802. 构造列表结构。
981
982    ```ts
983    @State routes: ItemGroupInfo[] = [
984      {
985        index: 0,
986        name: 'basicInfo',
987        label: '个人基本资料',
988        children: [
989          {
990            index: 0,
991            name: '昵称',
992            label: 'xxxx',
993            type: 'Text'
994          },
995          {
996            index: 1,
997            name: '头像',
998            label: $r('sys.media.ohos_user_auth_icon_face'),
999            type: 'Image'
1000          },
1001          {
1002            index: 2,
1003            name: '年龄',
1004            label: 'xxxx',
1005            type: 'Text'
1006          },
1007          {
1008            index: 3,
1009            name: '生日',
1010            label: 'xxxxxxxxx',
1011            type: 'Text'
1012          },
1013          {
1014            index: 4,
1015            name: '性别',
1016            label: 'xxxxxxxx',
1017            type: 'Text'
1018          },
1019        ]
1020      },
1021      {
1022        index: 1,
1023        name: 'equipInfo',
1024        label: '设备信息',
1025        children: []
1026      },
1027      {
1028        index: 2,
1029        name: 'appInfo',
1030        label: '应用使用信息',
1031        children: []
1032      },
1033      {
1034        index: 3,
1035        name: 'uploadInfo',
1036        label: '您主动上传的数据',
1037        children: []
1038      },
1039      {
1040        index: 4,
1041        name: 'tradeInfo',
1042        label: '交易与资产信息',
1043        children: []
1044      },
1045      {
1046        index: 5,
1047        name: 'otherInfo',
1048        label: '其他资料',
1049        children: []
1050      },
1051    ];
1052    @State expandedItems: boolean[] = Array(this.routes.length).fill(false);
1053    @State selection: string | null = null;
1054    build() {
1055      Column() {
1056        // ...
1057
1058        List({ space: 10 }) {
1059          ForEach(this.routes, (itemGroup: ItemGroupInfo) => {
1060            ListItemGroup({
1061              header: this.ListItemGroupHeader(itemGroup),
1062              style: ListItemGroupStyle.CARD,
1063            }) {
1064              if (this.expandedItems[itemGroup.index] && itemGroup.children) {
1065                ForEach(itemGroup.children, (item: ItemInfo) => {
1066                  ListItem({ style: ListItemStyle.CARD }) {
1067                    Row() {
1068                      Text(item.name)
1069                      Blank()
1070                      if (item.type === 'Image') {
1071                        Image(item.label)
1072                          .height(20)
1073                          .width(20)
1074                      } else {
1075                        Text(item.label)
1076                      }
1077                      Image($r('sys.media.ohos_ic_public_arrow_right'))
1078                        .fillColor($r('sys.color.ohos_id_color_fourth'))
1079                        .height(30)
1080                        .width(30)
1081                    }
1082                    .width("100%")
1083                  }
1084                  .width("100%")
1085                  .animation({ curve: curves.interpolatingSpring(0, 1, 528, 39) })
1086                })
1087              }
1088            }.clip(true)
1089          })
1090        }
1091        .width("100%")
1092      }
1093      .width('100%')
1094      .height('100%')
1095      .justifyContent(FlexAlign.Start)
1096      .backgroundColor($r('sys.color.ohos_id_color_sub_background'))
1097    }
1098    ```
1099
11003. 通过改变ListItem的状态,来控制每个列表项是否展开,并通过animation和animateTo来实现展开与折叠过程中的动效效果。
1101
1102    ```ts
1103    @Builder
1104    ListItemGroupHeader(itemGroup: ItemGroupInfo) {
1105      Row() {
1106        Text(itemGroup.label)
1107        Blank()
1108        Image($r('sys.media.ohos_ic_public_arrow_down'))
1109          .fillColor($r('sys.color.ohos_id_color_fourth'))
1110          .height(30)
1111          .width(30)
1112          .rotate({ angle: !!itemGroup.children.length ? (this.expandedItems[itemGroup.index] ? 180 : 0) : 180 })
1113          .animation({ curve: curves.interpolatingSpring(0, 1, 228, 22) })
1114      }
1115      .width("100%")
1116      .padding(10)
1117      .animation({ curve: curves.interpolatingSpring(0, 1, 528, 39) })
1118      .onClick(() => {
1119        if (itemGroup.children.length) {
1120          this.getUIContext()?.animateTo({ curve: curves.interpolatingSpring(0, 1, 528, 39) }, () => {
1121            this.expandedItems[itemGroup.index] = !this.expandedItems[itemGroup.index]
1122          })
1123        }
1124      })
1125    }
1126    ```
1127
1128## 相关实例
1129
1130如需详细了解ArkUI中列表的创建与使用,请参考以下示例:
1131
1132- [新闻数据加载](https://gitee.com/openharmony/codelabs/tree/master/NetworkManagement/NewsDataArkTS)
1133
1134- [音乐专辑页](../key-features/multi-device-app-dev/music-album-page.md)
1135
1136- [常用组件和容器低代码开发示例(ArkTS)(API9)](https://gitee.com/openharmony/codelabs/tree/master/EfficiencyEnhancementKit/SuperVisualSample)
1137
1138- [二级联动(ArkTS)(API9)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/SecondLevelLinkage)
1139
1140- [List组件的使用之商品列表(ArkTS)(API9)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/List)
1141
1142- [List组件的使用之设置项(ArkTS)(API9)](https://gitee.com/openharmony/codelabs/tree/master/ETSUI/List_HDC)
1143
1144- [PullToRefresh](https://gitee.com/openharmony-sig/PullToRefresh)
1145
1146<!--RP2--><!--RP2End-->
1147