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