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 23 24>**说明:** 25> 26>List的子组件必须是ListItemGroup或ListItem,ListItem和ListItemGroup必须配合List来使用。 27 28 29### 布局 30 31List除了提供垂直和水平布局能力、超出屏幕时可以滚动的自适应延伸能力之外,还提供了自适应交叉轴方向上排列个数的布局能力。 32 33利用垂直布局能力可以构建单列或者多列垂直滚动列表,如下图所示。 34 35 **图2** 垂直滚动列表(左:单列;右:多列) 36 37 38 39利用水平布局能力可以是构建单行或多行水平滚动列表,如下图所示。 40 41 **图3** 水平滚动列表(左:单行;右:多行) 42 43 44 45 46Grid和WaterFlow也可以实现单列、多列布局,如果布局每列等宽,且不需要跨行跨列布局,相比Grid和WaterFlow,则更推荐使用List。 47 48### 约束 49 50列表的主轴方向是指子组件列的排列方向,也是列表的滚动方向。垂直于主轴的轴称为交叉轴,其方向与主轴方向相互垂直。 51 52如下图所示,垂直列表的主轴是垂直方向,交叉轴是水平方向;水平列表的主轴是水平方向,交叉轴是垂直方向。 53 54 **图4** 列表的主轴与交叉轴 55 56 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 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 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 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 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 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 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.Auto。 345```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 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 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 438 439List组件的sticky属性配合ListItemGroup组件使用,用于设置ListItemGroup中的头部组件是否呈现吸顶效果或者尾部组件是否呈现吸底效果。 440 441通过给List组件设置sticky属性为StickyStyle.Header,即可实现列表的粘性标题效果。如果需要支持吸底效果,可以通过footer参数初始化ListItemGroup的底部组件,并将sticky属性设置为StickyStyle.Footer。 442 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 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 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 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 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 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 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 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