1# Creating a List (List) 2 3 4## Overview 5 6A list is a container that displays a collection of items. If the list items go beyond the screen, the list can scroll to reveal the content off the screen. The list is applicable for presenting similar data types or data type sets, such as images and text. Some common lists seen in applications are the contacts list, playlist, and shopping list. 7 8You can use lists to easily and efficiently display structured, scrollable information. Specifically, you can provide a single view of rows or columns by arranging the [ListItemGroup](../reference/apis-arkui/arkui-ts/ts-container-listitemgroup.md) or [ListItem](../reference/apis-arkui/arkui-ts/ts-container-listitem.md) child components linearly in a vertical or horizontal direction in the [List](../reference/apis-arkui/arkui-ts/ts-container-list.md) component, or use [ForEach](../ui/state-management/arkts-rendering-control-foreach.md) to iterate over a group of rows or columns, or mix any number of single views and **ForEach** structures to build a list. The **List** component supports the generation of child components in various [rendering](../ui/state-management/arkts-rendering-control-overview.md) modes, including conditional rendering, iterative rendering, and lazy data loading. 9 10On devices with circular screens, the [ArcList](../reference/apis-arkui/arkui-ts/ts-container-arclist.md) component is recommended. For details, see [Creating an Arc List (ArcList)](./arkts-layout-development-create-arclist.md). 11 12## Layout and Constraints 13 14A list automatically arranges child components in the direction it scrolls. Adding or removing child components from the list will trigger re-arrangement of the child components. 15 16As shown in the following figure, in a vertical list, **ListItemGroup** or **ListItem** components are automatically arranged vertically. 17 18**ListItemGroup** is used to display list data by group. Its child component is also **ListItem**. **ListItem** represents a list item, which can contain a single child component. 19 20 **Figure 1** Relationships between List, ListItemGroup, and ListItem 21 22 23 24>**NOTE** 25> 26>A **List** component can contain only **ListItemGroup** or **ListItem** as its child components. **ListItemGroup** and **ListItem** must be used together with **List**. 27 28 29### Layout 30 31In addition to providing vertical and horizontal layout support with adaptive scrolling for off-screen content, the **List** component also offers the capability of adapting to the number of rows in the cross axis direction. 32 33When used in vertical layout, the list can contain one or more scrollable columns, as shown below. 34 35 **Figure 2** Vertical scrolling list (left: one column; right: multiple columns) 36 37 38 39When used in horizontal layout, the list can contain one or more scrollable rows, as shown below. 40 41 **Figure 3** Horizontal scrolling list (left: one column; right: multiple columns) 42 43 44 45 46While **Grid** and **WaterFlow** can also create single-column and multi-column layouts, there are scenarios where the **List** is the more suitable choice. Specifically, if your layout design requires columns of equal width and items do not need to span rows or columns, opt for the **List**. 47 48### Constraints 49 50The main axis direction of a list refers to the direction in which the child component columns are laid out and in which the list scrolls. An axis perpendicular to the main axis is referred to as a cross axis, and the direction of the cross axis is perpendicular to a direction of the main axis. 51 52As shown below, the main axis of a vertical list is in the vertical direction, and the cross axis is in the horizontal direction. The main axis of a horizontal list is in the horizontal direction, and the cross axis is in the vertical direction. 53 54 **Figure 4** Main axis and cross axis of the list 55 56 57 58If a size is set for the main axis or cross axis of the **List** component, it is used as the size of the component in the corresponding direction. 59 60If no size is set for the main axis of the **List** component, the size of the **List** component in the main axis direction automatically adapts to the total size of its child components, as long as the total size of the child components in the main axis direction does not exceed the size of the parent component of **List**. 61 62In the example shown below, no height is set for vertical list B, and the height of its parent component A is 200 vp. If the total height of all child components C is 150 vp, the height of list B is 150 vp. 63 64 **Figure 5** Main axis height constraint example 1 (A: parent component of List; B: List component; C: all child components of List) 65 66 67 68If the total size of the child components in the main axis direction is greater than the size of the parent component of **List**, the size of the **List** component in the main axis direction automatically adapts to the size of its parent component. 69 70In the example shown below, still no height is set for vertical list B, and the height of its parent component A is 200 vp. If the total height of all child components C is 300 vp, the height of list B is 200 vp. 71 72 **Figure 6** Main axis height constraint example 2 (A: parent component of List; B: List component; C: all child components of List) 73 74 75 76If no size is set for the cross axis of the **List** component, the size of the **List** component in the cross axis direction automatically adapts to the size of its parent component. 77 78 79## Developing the Layout 80 81 82### Setting the Main Axis Direction 83 84By default, the main axis of the **List** component runs in the vertical direction. This means that you can create a vertical scrolling list without the need to manually set the list direction. 85 86To create a horizontal scrolling list, set the **listDirection** attribute to **Axis.Horizontal**. The default value of **listDirection** is **Axis.Vertical**. 87 88 89```ts 90List() { 91 // ... 92} 93.listDirection(Axis.Horizontal) 94``` 95 96 97### Setting the Cross Axis Layout 98 99The cross axis layout of the **List** component can be set using the **lanes** and **alignListItem** attributes. The **lanes** attribute controls the number of list items along the cross axis, and the **alignListItem** attribute controls the alignment mode of child components along the cross axis. 100 101The **lanes** attribute of the **List** component is usually used to adaptively construct lists with different numbers of rows or columns for devices of different sizes, enabling one-time development for multi-device deployment. Its value type is number or [LengthConstrain](../reference/apis-arkui/arkui-ts/ts-types.md#lengthconstrain). If you are building a two-column vertical list shown on the right in Figure 2, set the **lanes** attribute to **2**. The default value of **lanes** is **1**. 102 103 104```ts 105List() { 106 // ... 107} 108.lanes(2) 109``` 110 111If set to a value of the LengthConstrain type, the **lanes** attribute determines the number of rows or columns based on the LengthConstrain settings and the size of the **List** component. 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 128For example, if the **lanes** attribute is set to **{ minLength: 200, maxLength: 300 }** for a vertical list, then: 129 130- When the list width is 300 vp, the list contains one column, because **minLength** is 200 vp. 131 132- When the list width changes to 400 vp, which is twice that of the **minLength** value, the list is automatically adapted to two-column. 133 134With regard to a vertical list, when the **alignListItem** attribute is set to **ListItemAlign.Center**, list items are center-aligned horizontally; when the **alignListItem** attribute is at its default value **ListItemAlign.Start**, list items are aligned toward the start edge of the cross axis in the list. 135 136 137```ts 138List() { 139 // ... 140} 141.alignListItem(ListItemAlign.Center) 142``` 143 144## ListItem Lifecycle Management 145### Creating ListItem Components with ForEach 146When a **List** component is created, all **ListItem** components are created immediately, but their behavior varies by area: 147 148- Visible area: **ListItem** components are laid out during the first frame. 149- Preload area: **ListItem** components are laid out during idle time. 150- Outside the preload area: Only the **ListItem** container is created; its child components are not created. 151 152During scrolling, the **ListItem** components entering the preload and visible areas create their child components and complete layout, and the **ListItem** components leaving these areas are not destroyed. 153 154**Figure 7** Lifecycle of ListItem components created using ForEach 155 156 157### Creating ListItem Components with LazyForEach 158When a **List** component is created: 159 160- Visible area: **ListItem** components are created and laid out immediately. 161- Preload area: **ListItem** components are created and laid out during idle time but not mounted to the component tree. 162- Outside the preload area: No **ListItem** components are created. 163 164During scrolling, the **ListItem** components entering the preload and visible areas are created and laid out. If they contain @Reusable decorated custom components, these components are reused from the cache pool when possible. **ListItem** components leaving the preload and visible areas are destroyed. If they contain @Reusable decorated custom components, these components are recycled into the cache pool. 165 166**Figure 8** Lifecycle of ListItem components created using LazyForEach 167 168 169### Creating ListItem Components with Repeat 170**With virtualScroll Enabled** 171 172When a **List** component is created: 173 174- Visible area: **ListItem** components are created and laid out immediately. 175- Preload area: **ListItem** components are created and laid out during idle time, and then mounted to the component tree. 176- Outside the preload area: No **ListItem** components are created. 177 178During scrolling, for **ListItem** components entering the preload and visible areas, the system first attempts to reuse components from cache pool. If no components are unavailable in the cache pool, the system creates **ListItem** components and lays them out. **ListItem** components leaving the preload and visible areas are recycled into the cache pool. 179 180**Figure 9** Lifecycle of ListItem components created using Repeat with virtualScroll enabled 181 182 183**With virtualScroll Disabled** 184 185When a **List** component is created, all **ListItem** components are created immediately, but their behavior varies by area: 186 187- Visible area: **ListItem** components are laid out during the first frame. 188- Preload area: **ListItem** components are laid out during idle time. 189- Outside the preload area: No layout is performed. 190 191During scrolling, the **ListItem** components in the preload and visible areas are laid out; the **ListItem** components leaving these areas are not destroyed. 192 193**Figure 10** Lifecycle of ListItem components created using Repeat with virtualScroll disabled 194 195 196 197## Displaying Data in the List 198 199The list displays a collection of items horizontally or vertically and can scroll to reveal content off the screen. In the simplest case, a **List** component is statically made up of **ListItem** components. 200 201 **Figure 11** Example of a city list 202 203 204 205```ts 206@Entry 207@Component 208struct CityList { 209 build() { 210 List() { 211 ListItem() { 212 Text('Beijing').fontSize(24) 213 } 214 215 ListItem() { 216 Text('Hangzhou').fontSize(24) 217 } 218 219 ListItem() { 220 Text('Shanghai').fontSize(24) 221 } 222 } 223 .backgroundColor('#FFF1F3F5') 224 .alignListItem(ListItemAlign.Center) 225 } 226} 227``` 228 229Each **ListItem** component can contain only one root child component. Therefore, it does not allow for child components in tile mode. If tile mode is required, encapsulate the child components into a container or create a custom component. 230 231 **Figure 12** Example of a contacts list 232 233 234 235As shown above, as a list item, each contact has a profile picture and a name. To present it, you can encapsulate **Image** and **Text** components into a **Row** container. 236 237 238```ts 239List() { 240 ListItem() { 241 Row() { 242 // app.media.iconE is a custom resource. 243 Image($r('app.media.iconE')) 244 .width(40) 245 .height(40) 246 .margin(10) 247 248 Text('Tom') 249 .fontSize(20) 250 } 251 } 252 253 ListItem() { 254 Row() { 255 // app.media.iconF is a custom resource. 256 Image($r('app.media.iconF')) 257 .width(40) 258 .height(40) 259 .margin(10) 260 261 Text('Tracy') 262 .fontSize(20) 263 } 264 } 265} 266``` 267 268 269## Iterating List Content 270 271Compared with a static list, a dynamic list is more common in applications. For dynamic lists, you can use [ForEach](../ui/state-management/arkts-rendering-control-foreach.md) to obtain data from the data source and create components for each data item, thereby reducing code complexity. 272 273For example, when creating a contacts list, you can store the contact name and profile picture data in a **Contact** class structure to the **contacts** array, and nest **ListItem** components in **ForEach**, thereby reducing repeated code needed for tiling similar **ListItem** components. 274 275 276```ts 277import { util } from '@kit.ArkTS'; 278 279class Contact { 280 key: string = util.generateRandomUUID(true); 281 name: string; 282 icon: Resource; 283 284 constructor(name: string, icon: Resource) { 285 this.name = name; 286 this.icon = icon; 287 } 288} 289 290@Entry 291@Component 292struct SimpleContacts { 293 private contacts: Array<object> = [ 294 new Contact('Tom', $r("app.media.iconA")), 295 new Contact('Tracy', $r("app.media.iconB")), 296 ]; 297 298 build() { 299 List() { 300 ForEach(this.contacts, (item: Contact) => { 301 ListItem() { 302 Row() { 303 Image(item.icon) 304 .width(40) 305 .height(40) 306 .margin(10) 307 Text(item.name).fontSize(20) 308 } 309 .width('100%') 310 .justifyContent(FlexAlign.Start) 311 } 312 }, (item: Contact) => JSON.stringify(item)) 313 } 314 .width('100%') 315 } 316} 317``` 318 319In the **List** component, **ForEach** can be used to render **ListItemGroup** items as well as **ListItem** items. For details, see [Adding Grouping Support](#adding-grouping-support). 320 321 322## Customizing the List Style 323 324 325### Setting the Spacing 326 327When initializing a list, you can use the **space** parameter to add spacing between list items. In the following example, a 10 vp spacing is added between list items along the main axis: 328 329 330```ts 331List({ space: 10 }) { 332 // ... 333} 334``` 335 336 337### Adding Dividers 338 339A divider separates UI items to make them easier to identify. In the following figure that shows the settings screen, a divider is added between the setting items, appearing below the text. 340 341 **Figure 13** Using dividers between the setting items 342 343 344 345To add dividers between list items, you can use the **divider** attribute together with the following style attributes: 346 347- **strokeWidth** and **color**: stroke width and color of the diver, respectively. 348 349- **startMargin** and **endMargin**: distance between the divider and the start edge and end edge of the list, respectively. 350 351 352```ts 353class DividerTmp { 354 strokeWidth: Length = 1; 355 startMargin: Length = 60; 356 endMargin: Length = 10; 357 color: ResourceColor = '#ffe9f0f0'; 358 359 constructor(strokeWidth: Length, startMargin: Length, endMargin: Length, color: ResourceColor) { 360 this.strokeWidth = strokeWidth; 361 this.startMargin = startMargin; 362 this.endMargin = endMargin; 363 this.color = color; 364 } 365} 366@Entry 367@Component 368struct EgDivider { 369 @State egDivider: DividerTmp = new DividerTmp(1, 60, 10, '#ffe9f0f0'); 370 build() { 371 List() { 372 // ... 373 } 374 .divider(this.egDivider) 375 } 376} 377``` 378 379This example draws a divider with a stroke thickness of 1 vp from a position 60 vp away from the start edge of the list to a position 10 vp away from the end edge of the list. The effect is shown in Figure 9. 380 381>**NOTE** 382> 383>1. The stroke width of the divider causes some space between list items. If the content spacing set for the list is smaller than the stroke width of the divider, the latter is used instead. 384> 385>2. When a list contains multiple columns, the **startMargin** and **endMargin** attributes of the divider apply to each column. 386> 387>3. The divider is drawn between list items. No divider is drawn above the first list item and below the last list item. 388 389 390### Adding a Scrollbar 391 392When the total height (width) of list items exceeds the screen height (width), the list can scroll vertically (horizontally). The scrollbar of a list enables users to quickly navigate the list content, as shown below. 393 394 **Figure 14** Scrollbar of a list 395 396 397 398When using the **List** component, you can use the **scrollBar** attribute to control the display of the list scrollbar. The value type of **scrollBar** is [BarState](../reference/apis-arkui/arkui-ts/ts-appendix-enums.md#barstate). When the value is **BarState.Auto**, the scrollbar is displayed as required: It is displayed when the scrollbar area is touched and becomes thicker when being dragged; it automatically disappears after 2 seconds of inactivity. 399 400The default value of the **scrollBar attribute** is **BarState.Off** in API version 9 and earlier versions and **BarState.Auto** since API version 10. 401```ts 402List() { 403 // ... 404} 405.scrollBar(BarState.Auto) 406``` 407 408## Adding an External Scrollbar 409 410To add an external scrollbar to a [List](../reference/apis-arkui/arkui-ts/ts-container-list.md) component, you can use the [ScrollBar](../reference/apis-arkui/arkui-ts/ts-basic-components-scrollbar.md) component. By binding both the **List** and **ScrollBar** components to the same [Scroller](../reference/apis-arkui/arkui-ts/ts-container-scroll.md#scroller) object, you can ensure they stay synchronized. 411 4121. Create a [Scroller](../reference/apis-arkui/arkui-ts/ts-container-scroll.md#scroller) object named **listScroller**. 413 414 ```ts 415 private listScroller: Scroller = new Scroller(); 416 ``` 417 4182. Bind the **listScroller** object to the **List** component using the [scroller](../reference/apis-arkui/arkui-ts/ts-container-list.md#listoptions18) parameter. 419 420 ```ts 421 // Use listScroller to initialize the scroller parameter to bind it with the List component. 422 List({ scroller: this.listScroller }) { 423 // ... 424 } 425 ``` 426 4273. Bind the **listScroller** object to the **ScrollBar** component using the [scroller](../reference/apis-arkui/arkui-ts/ts-basic-components-scrollbar.md#scrollbaroptions) parameter. 428 429 ```ts 430 // Use listScroller to initialize the scroller parameter to bind it with the ScrollBar component. 431 ScrollBar({ scroller: this.listScroller }) 432 ``` 433 434 **Figure 15** External scrollbar of the List component 435 436 437 438>**NOTE** 439>- The [ScrollBar](../reference/apis-arkui/arkui-ts/ts-basic-components-scrollbar.md) component can also be used with other scrollable components such as [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), and [WaterFlow](../reference/apis-arkui/arkui-ts/ts-container-waterflow.md). 440>- On devices with circular screens, you can use the [List](../reference/apis-arkui/arkui-ts/ts-container-list.md) component with the [ArcScrollBar](../reference/apis-arkui/arkui-ts/ts-basic-components-arcscrollbar.md) component to add an arc scrollbar to your list layout. For details, see [Adding an External Scrollbar: ArcScrollBar](./arkts-layout-development-create-arclist.md#adding-an-external-scrollbar-arcscrollbar). 441 442## Adding Grouping Support 443 444By allowing data to be displayed in groups in the list, you make the list easier to scan and navigate. Grouping is common in real-world applications. For example, the contacts list below use grouping. 445 446 **Figure 16** Contacts list with grouping 447 448 449 450You can use **ListItemGroup** to group items in the **List** component to build a two-dimensional list. 451 452A **List** component allows one or more **ListItemGroup** child components. By default, the width of **ListItemGroup** is equal to that of **List**. When initializing **ListItemGroup**, you can use the **header** parameter to set its header. 453 454 455```ts 456@Entry 457@Component 458struct ContactsList { 459 460 @Builder itemHead(text: string) { 461 // Header of the list group, corresponding to the group A and B locations. 462 Text(text) 463 .fontSize(20) 464 .backgroundColor('#fff1f3f5') 465 .width('100%') 466 .padding(5) 467 } 468 469 build() { 470 List() { 471 ListItemGroup({ header: this.itemHead('A') }) { 472 // Render the repeated list items of group A. 473 } 474 475 ListItemGroup({ header: this.itemHead('B') }) { 476 // Render the repeated list items of group B. 477 } 478 } 479 } 480} 481``` 482 483If the structures of multiple **ListItemGroup** components are similar, you can combine the data of these components into an array and use **ForEach** to render them cyclically. For example, in the contacts list, the **contacts** data of each group (for details, see [Iterating List Content](#iterating-list-content)) and the **title** data of the corresponding group are combined and defined as the **contactsGroups** array. Then, with rendering of **contactsGroups** in **ForEach**, a contact list with multiple groups is implemented. For details, see the example in [Adding a Sticky Header](#adding-a sticky-header). 484 485## Adding a Sticky Header 486 487The sticky header is a common pattern for keeping the header in the same place on the screen while the user scrolls down the list. As shown in the following figure, when you scroll through group A in the contacts list, the header of group B is always below group A. When you start scrolling through group B, the header of group B is fixed at the top of the screen. After group B has been scrolled to the bottom, the header of group B is replaced by the header of next group. 488 489Sticky headers not only signify the representation and usage of data in the respective groups, but also help users navigate through a large amount of information, thereby avoiding unnecessary scrolling between the top of the area where the header is located and the area of interest. 490 491 **Figure 17** Sticky header 492 493 494 495You can set a sticky header or footer for a **ListItemGroup** component by setting the **sticky** attribute of its parent **List** component. 496 497Setting the **sticky** attribute to **StickyStyle.Header** implements a sticky header. To implement a sticky footer, use the **footer** parameter to initialize the footer of **ListItemGroup** and set the **sticky** attribute to **StickyStyle.Footer**. 498 499 500```ts 501import { util } from '@kit.ArkTS'; 502class Contact { 503 key: string = util.generateRandomUUID(true); 504 name: string; 505 icon: Resource; 506 507 constructor(name: string, icon: Resource) { 508 this.name = name; 509 this.icon = icon; 510 } 511} 512export class ContactsGroup { 513 title: string = ''; 514 contacts: Array<object> | null = null; 515 key: string = ""; 516} 517 518export class ContactsGroupDataSource implements IDataSource { 519 private list: object[] = []; 520 521 constructor(list: object[]) { 522 this.list = list; 523 } 524 525 totalCount(): number { 526 return this.list.length; 527 } 528 529 getData(index: number): object { 530 return this.list[index]; 531 } 532 533 registerDataChangeListener(listener: DataChangeListener): void { 534 } 535 536 unregisterDataChangeListener(listener: DataChangeListener): void { 537 } 538} 539 540export let contactsGroups: object[] = [ 541 { 542 title: 'A', 543 contacts: [ 544 new Contact('Alice', $r('app.media.iconA')), 545 new Contact('Ann', $r('app.media.iconB')), 546 new Contact('Angela', $r('app.media.iconC')), 547 ], 548 key: util.generateRandomUUID(true) 549 } as ContactsGroup, 550 { 551 title: 'B', 552 contacts: [ 553 new Contact('Ben', $r('app.media.iconD')), 554 new Contact('Bryan', $r('app.media.iconE')), 555 ], 556 key: util.generateRandomUUID(true) 557 } as ContactsGroup, 558 // ... 559] 560export let contactsGroupsDataSource: ContactsGroupDataSource = new ContactsGroupDataSource(contactsGroups); 561 562@Entry 563@Component 564struct ContactsList { 565 // Define the contactsGroups array. 566 @Builder itemHead(text: string) { 567 // Header of the list group, corresponding to the group A and B locations. 568 Text(text) 569 .fontSize(20) 570 .backgroundColor('#fff1f3f5') 571 .width('100%') 572 .padding(5) 573 } 574 build() { 575 List() { 576 // Lazy-load the ListItemGroup components. contactsGroups is the data set of contacts and titles of multiple groups. 577 LazyForEach(contactsGroupsDataSource, (itemGroup: ContactsGroup) => { 578 ListItemGroup({ header: this.itemHead(itemGroup.title) }) { 579 // Lazy-load ListItem components. 580 if (itemGroup.contacts) { 581 LazyForEach(new ContactsGroupDataSource(itemGroup.contacts), (item: Contact) => { 582 ListItem() { 583 // ... 584 } 585 }, (item: Contact) => JSON.stringify(item)) 586 } 587 } 588 }, (itemGroup: ContactsGroup) => JSON.stringify(itemGroup)) 589 }.sticky(StickyStyle.Header) // Set a sticky header. 590 } 591} 592``` 593 594 595## Controlling the Scroll Position 596 597In some cases you may want to control the scroll position of a list. For example, when there are a huge number of items in the news page list, you may want to allow users to quickly jump to the top or bottom of the list after they have scrolled to a certain point. Below is an example. 598 599 **Figure 18** Returning to the top of the list 600 601 602 603When the **List** component is initialized, you can use the **scroller** parameter to bind a [Scroller](../reference/apis-arkui/arkui-ts/ts-container-scroll.md#scroller) object to control the scrolling of the list. In this example of a news page list, the **scrollToIndex** API of the **Scroller** object is used to scroll the list to the list item with the specified index. This allows the user to return to the top of the list by clicking a specific button. 604 605To start with, create a **Scroller** object **listScroller**. 606 607 608```ts 609private listScroller: Scroller = new Scroller(); 610``` 611 612Then, use **listScroller** to initialize the **scroller** parameter to bind it with the **List** component. Set **scrollToIndex** to **0**, meaning to return to the top of the list. 613 614 615```ts 616Stack({ alignContent: Alignment.Bottom }) { 617 // Use listScroller to initialize the scroller parameter to bind it with the List component. 618 List({ space: 20, scroller: this.listScroller }) { 619 // ... 620 } 621 622 Button() { 623 // ... 624 } 625 .onClick(() => { 626 // Specify where e to jump when the specific button is clicked, which is the top of the list in this example. 627 this.listScroller.scrollToIndex(0); 628 }) 629} 630``` 631 632 633## Handling Scroll Position Changes 634 635Many applications need to listen for and respond to changes in list scroll positions. For example, with regard to a contacts list, if scrolling spans more than one group, the alphabetical index bar at one side of the list also needs to be updated to highlight the letter corresponding to the current group. 636 637Another common example is a scrolling list working with a multi-level index bar, as in the case of a product category page in a shopping application. 638 639**Figure 19** Alphabetical index bar's response to contacts list scrolling 640 641 642 643As shown above, when the contacts list scrolls from group A to B, the alphabetical index bar on the right also changes from A to B. This scenario can be implemented by listening for the **onScrollIndex** event of the **List** component. The alphabet index bar is implemented using the [AlphabetIndexer](../reference/apis-arkui/arkui-ts/ts-container-alphabet-indexer.md) component. 644 645When the list scrolls, the **selectedIndex** value of the letter to highlight in the alphabet index bar is recalculated based on the **firstIndex** value of the item to which the list has scrolled. In the **AlphabetIndexer** component, the index of the highlighted item is set through the **selected** attribute. When the value of **selectedIndex** changes, the **AlphabetIndexer** component is re-rendered to highlight the corresponding letter. 646 647 648```ts 649const alphabets = ['#', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 650 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']; 651@Entry 652@Component 653struct ContactsList { 654 @State selectedIndex: number = 0; 655 private listScroller: Scroller = new Scroller(); 656 657 build() { 658 Stack({ alignContent: Alignment.End }) { 659 List({ scroller: this.listScroller }) {} 660 .onScrollIndex((firstIndex: number) => { 661 // Recalculate the value of this.selectedIndex in the alphabetical index bar based on the index of the item to which the list has scrolled. 662 }) 663 664 // AlphabetIndexer component 665 AlphabetIndexer({ arrayValue: alphabets, selected: 0 }) 666 .selected(this.selectedIndex) 667 .onSelect((index: number) => { 668 this.listScroller.scrollToIndex(index); 669 }) 670 } 671 } 672} 673``` 674 675>**NOTE** 676> 677>During index calculation, each **ListItemGroup** component is taken as a whole and assigned an index, and the indexes of the list items within are not included in the calculation. 678 679 680## Responding to Swipe on List Items 681 682Swipe menus are common in many applications. For example, a messaging application generally provides a swipe-to-delete feature for its message list. This feature allows users to delete a message by swiping left on it and touching the delete button, as shown in the following figure. For details about how to add a badge to the profile picture of a list item, see [Adding a Badge to a List Item](#adding-a-badge-to-a-list-item). 683 684**Figure 20** Swipe-to-delete feature 685 686 687 688Swiping left or right on a list item can be implemented through the [swipeAction](../reference/apis-arkui/arkui-ts/ts-container-listitem.md#swipeaction9) attribute. In initialization of the **swipeAction** attribute, the **SwipeActionOptions** parameter is mandatory, wherein the **start** parameter indicates the component that appears from the start edge when the list item is swiped right, and the **end** parameter indicates the component that appears from the end edge when the list item is swiped left. 689 690In the example of the message list, the **end** parameter is set to a custom delete button. In initialization of the **end** attribute, the index of the sliding list item is passed to the delete button. When the user touches the delete button, the data corresponding to the list item is deleted based on the index. 691 6921. Build the component that appears from the end edge when the list item is swiped left. 693 694 ```ts 695 @Builder itemEnd(index: number) { 696 // Build the component that appears from the end edge when the list item is swiped left. 697 Button({ type: ButtonType.Circle }) { 698 Image($r('app.media.ic_public_delete_filled')) 699 .width(20) 700 .height(20) 701 } 702 .onClick(() => { 703 // this.messages is the list data source, which can be constructed as required. A specified data item can be deleted from the data source upon click. 704 this.messages.splice(index, 1); 705 }) 706 } 707 ``` 708 7092. Binds the **swipeAction** attribute to a list item that can be swiped left. 710 711 ```ts 712 // When constructing a list, use ForEach to render list items based on the data source this.messages. 713 ListItem() { 714 // ... 715 } 716 .swipeAction({ 717 end: { 718 // index is the index of the list item. 719 builder: () => { this.itemEnd(index) }, 720 } 721 }) // Set the swipe action. 722 ``` 723 724## Adding a Badge to a List Item 725 726A badge is an intuitive, unobtrusive visual indicator to draw attention and convey a specific message. For example, a badge can be displayed in the upper right corner of the contact's profile picture to indicate that there is a new message from that contact, as shown in the following figure. 727 728 **Figure 21** Adding a badge to a list item 729 730 731 732To add a badge, use the [Badge](../reference/apis-arkui/arkui-ts/ts-container-badge.md) component in **ListItem**. The **Badge** component is a container that can be attached to another component for tagging. 733 734In this example, when implementing the **Image** component for presenting the profile picture of a list item, add it to **Badge** as a child component. 735 736In the **Badge** component, the **count** and **position** parameters are used to set the number of notifications and the position to display the badge, respectively. You can also use the **style** parameter to spruce up the badge. 737 738 739```ts 740ListItem() { 741 Badge({ 742 count: 1, 743 position: BadgePosition.RightTop, 744 style: { badgeSize: 16, badgeColor: '#FA2A2D' } 745 }) { 746 // The Image component implements the contact profile picture. 747 // ... 748 } 749} 750``` 751 752 753## Implementing Pull-Down-to-Refresh and Pull-Up-to-Load 754 755The pull-down-to-refresh and pull-up-to-load features are widely used in mobile applications, such as news applications. In effect, the implementation of these two features follows the same process: (1) As response to a [touch event](../reference/apis-arkui/arkui-ts/ts-universal-events-touch.md), a refresh or load view is displayed at the top or bottom of the page; (2) when the refresh or load is complete, the refresh or load view is hidden. 756 757The following describes the implementation of the pull-and-refresh feature: 758 7591. Listen for the finger press event and record the value of the initial position. 760 7612. Listen for the finger movement event, and record and calculate the difference between the value of the current position and the initial value. If the difference is greater than 0, the finger moves downward. Set the maximum value for the movement. 762 7633. Listen for the finger lift event. If the movement reaches the maximum value, trigger data loading and display the refresh view. After the loading is complete, hide the view. 764 765> **NOTE** 766> 767> To implement the pull-down-to-refresh feature, you are advised to use the [Refresh](../reference/apis-arkui/arkui-ts/ts-container-refresh.md) component. 768 769<!--RP1--><!--RP1End--> 770 771<!--Del--> 772 <!--DelEnd--> 773 774 775## Editing a List 776 777The list editing mode is frequently used in various scenarios, such as to-do list management, file management, and note management. In editing mode, adding and deleting list items are the most basic functions. The core is to add and delete data in the data set corresponding to the list items. 778 779The following uses to-do list management as an example to describe how to quickly add and delete list items. 780 781 782### Adding a List Item 783 784As shown below, when a user touches **Add**, a page is displayed for the user to set options for the new list item. After the user touches **OK**, the corresponding item is added to the list. 785 786 **Figure 22** Adding a to-do task 787 788 789 790The process of implementing the addition feature is as follows: 791 7921. Define the list item data structure. In this example, a to-do data structure is defined. 793 794 ```ts 795 //ToDo.ets 796 import { util } from '@kit.ArkTS'; 797 798 export class ToDo { 799 key: string = util.generateRandomUUID(true); 800 name: string; 801 802 constructor(name: string) { 803 this.name = name; 804 } 805 } 806 ``` 807 8082. Build the overall list layout and list items. 809 810 ```ts 811 //ToDoListItem.ets 812 import { ToDo } from './ToDo'; 813 @Component 814 export struct ToDoListItem { 815 @Link isEditMode: boolean; 816 @Link selectedItems: ToDo[]; 817 private toDoItem: ToDo = new ToDo(""); 818 819 build() { 820 Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { 821 // ... 822 } 823 .width('100%') 824 .height(80) 825 // .padding(): Set this parameter based on the use case. 826 .borderRadius(24) 827 // .linearGradient(): Set this parameter based on the use case. 828 .gesture( 829 GestureGroup(GestureMode.Exclusive, 830 LongPressGesture() 831 .onAction(() => { 832 // ... 833 }) 834 ) 835 ) 836 } 837 } 838 ``` 839 8403. Initialize the to-do list data and available items, and build the list layout and list items. 841 842 ```ts 843 //ToDoList.ets 844 import { ToDo } from './ToDo'; 845 import { ToDoListItem } from './ToDoListItem'; 846 847 @Entry 848 @Component 849 struct ToDoList { 850 @State toDoData: ToDo[] = []; 851 @Watch('onEditModeChange') @State isEditMode: boolean = false; 852 @State selectedItems: ToDo[] = []; 853 private availableThings: string[] = ['Reading', 'Fitness', 'Travel', 'Music', 'Movie', 'Singing']; 854 855 onEditModeChange() { 856 if (!this.isEditMode) { 857 this.selectedItems = []; 858 } 859 } 860 861 build() { 862 Column() { 863 Row() { 864 if (this.isEditMode) { 865 Text('X') 866 .fontSize(20) 867 .onClick(() => { 868 this.isEditMode = false; 869 }) 870 .margin({ left: 20, right: 20 }) 871 } else { 872 Text('To-Do') 873 .fontSize(36) 874 .margin({ left: 40 }) 875 Blank() 876 Text('+') // Provide an entry for adding a list item, that is, add a click event for the add button. 877 .onClick(() => { 878 this.getUIContext().showTextPickerDialog({ 879 range: this.availableThings, 880 onAccept: (value: TextPickerResult) => { 881 let arr = Array.isArray(value.index) ? value.index : [value.index]; 882 for (let i = 0; i < arr.length; i++) { 883 this.toDoData.push(new ToDo(this.availableThings[arr[i]])); // Add to-do list items (available items). 884 } 885 }, 886 }) 887 }) 888 } 889 List({ space: 10 }) { 890 ForEach(this.toDoData, (toDoItem: ToDo) => { 891 ListItem() { 892 // Place each item of toDoData into the list item in the form of model. 893 ToDoListItem({ 894 isEditMode: this.isEditMode, 895 toDoItem: toDoItem, 896 selectedItems: this.selectedItems }) 897 } 898 }, (toDoItem: ToDo) => toDoItem.key.toString()) 899 } 900 } 901 } 902 } 903 } 904 ``` 905 906 907### Deleting a List Item 908 909As shown below, when the user long presses a list item to enter the deletion mode, a page is displayed for the user to delete the list item. After the user selects the list item and touches the delete button, the list item is deleted. 910 911 **Figure 23** Deleting a to-do task 912 913 914 915The process of implementing the deletion feature is as follows: 916 9171. Generally, the deletion feature is available only after the list enters the editing mode. Therefore, the entry to the editing mode needs to be provided. 918 In this example, by listening for the long press event of a list item, the list enters the editing mode when the user long presses a list item. 919 920 ```ts 921 // Structure reference 922 export class ToDo { 923 key: string = util.generateRandomUUID(true); 924 name: string; 925 toDoData: ToDo[] = []; 926 927 constructor(name: string) { 928 this.name = name; 929 } 930 } 931 ``` 932 ```ts 933 // Implementation reference 934 Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { 935 // ... 936 } 937 .gesture( 938 GestureGroup(GestureMode.Exclusive, 939 LongPressGesture() 940 .onAction(() => { 941 if (!this.isEditMode) { 942 this.isEditMode = true; // Enter the editing mode. 943 } 944 }) 945 ) 946 ) 947 ``` 948 9492. Respond to the user's selection and record the list items to be deleted. 950 In this to-do list example, the list items are selected or unselected according to the user's selection. 951 952 ```ts 953 // Structure reference 954 import { util } from '@kit.ArkTS'; 955 export class ToDo { 956 key: string = util.generateRandomUUID(true); 957 name: string; 958 toDoData: ToDo[] = []; 959 960 constructor(name: string) { 961 this.name = name; 962 } 963 } 964 ``` 965 ```ts 966 // Implementation reference 967 if (this.isEditMode) { 968 Checkbox() 969 .onChange((isSelected) => { 970 if (isSelected) { 971 this.selectedItems.push(toDoList.toDoItem); // When an item is selected, it is added to the selectedItems array. Make adjustment based on actual scenarios. 972 } else { 973 let index = this.selectedItems.indexOf(toDoList.toDoItem); 974 if (index !== -1) { 975 this.selectedItems.splice(index, 1); // When an item is deselected, it is deleted from the selectedItems array. 976 } 977 } 978 }) 979 } 980 ``` 981 9823. Respond to the user's clicking the delete button and delete the corresponding items from the list. 983 984 ```ts 985 // Structure reference 986 import { util } from '@kit.ArkTS'; 987 export class ToDo { 988 key: string = util.generateRandomUUID(true); 989 name: string; 990 toDoData: ToDo[] = []; 991 992 constructor(name: string) { 993 this.name = name; 994 } 995 } 996 ``` 997 ```ts 998 // Implementation reference 999 Button('Delete') 1000 .onClick(() => { 1001 // this.toDoData is the to-do list item, which can be constructed based on service requirements. After an item is clicked, the corresponding data is removed. 1002 let leftData = this.toDoData.filter((item) => { 1003 return !this.selectedItems.find((selectedItem) => selectedItem == item); 1004 }) 1005 this.toDoData = leftData; 1006 this.isEditMode = false; 1007 }) 1008 ``` 1009 1010 1011## Handling a Long List 1012 1013While [ForEach](../ui/state-management/arkts-rendering-control-foreach.md) is suitable for short lists, using it for long lists with a large number of items can significantly slow down page loading, as it loads all items at once. Therefore, for better list performance, use [LazyForEach](../ui/state-management/arkts-rendering-control-lazyforeach.md) instead to implement on-demand iterative data loading. 1014 1015For details about the implementation, see the example in [LazyForEach: Lazy Data Loading](../ui/state-management/arkts-rendering-control-lazyforeach.md). 1016 1017When the list is rendered in lazy loading mode, to improve the list scrolling experience and minimize white blocks during list scrolling, you can use the **cachedCount** parameter of the **List** component to set the number of cached list items. With lazy loading, only content outside the visible area up to the **cachedCount** limit will be preloaded, whereas non-lazy loading will load all content. For both lazy and non-lazy loading, only the items within the visible area plus the **cachedCount**-specified number of items outside the visible area are laid out. 1018 1019 1020```ts 1021List() { 1022 // ... 1023}.cachedCount(3) 1024``` 1025 1026The following uses a vertical list as an example: 1027 1028- When **cachedCount** is set for the list, the system preloads and lays out the **cachedCount**-specified number of rows of list items both above and below the currently visible area of the list. When calculating the number of rows for list items, the system takes into account the number of rows from the list items within a list item group. If a list item group does not contain any list items, then the entire list item group is counted as one row. 1029 1030- When a list is nested with **LazyForEach**, and within **LazyForEach** there is a list item group, **LazyForEach** will create **cachedCount**-specified number of list item groups both above and below the currently visible area of the list. 1031 1032>**NOTE** 1033> 1034>1. A greater **cachedCount** value may result in higher CPU and memory overhead of the UI. Adjust the value by taking into account both the comprehensive performance and user experience. 1035> 1036>2. When a list uses data lazy loading, all list items except the list items in the display area and the cached list items are destroyed. 1037 1038 1039## Collapsing and Expanding 1040 1041The collapsing and expanding of list items are widely used, often applied in scenarios such as displaying information lists and filling out forms. 1042 1043 **Figure 24** Collapsing and expanding of list items 1044 1045 1046 1047The process of implementing the collapsing and expanding effect of list items is as follows: 1048 10491. Define the list item data structure. 1050 1051 ```ts 1052 import { curves } from '@kit.ArkUI'; 1053 interface ItemInfo { 1054 index: number, 1055 name: string, 1056 label: ResourceStr, 1057 type?: string, 1058 } 1059 1060 interface ItemGroupInfo extends ItemInfo { 1061 children: ItemInfo[] 1062 } 1063 ``` 1064 10652. Construct a list structure. 1066 1067 ```ts 1068 @State routes: ItemGroupInfo[] = [ 1069 { 1070 index: 0, 1071 name: 'basicInfo', 1072 label: 'Basic personal information', 1073 children: [ 1074 { 1075 index: 0, 1076 name: 'Nickname', 1077 label: 'xxxx', 1078 type: 'Text' 1079 }, 1080 { 1081 index: 1, 1082 name: 'Profile picture', 1083 label: $r('sys.media.ohos_user_auth_icon_face'), 1084 type: 'Image' 1085 }, 1086 { 1087 index: 2, 1088 name: 'Age', 1089 label: 'xxxx', 1090 type: 'Text' 1091 }, 1092 { 1093 index: 3, 1094 name: 'Birthday', 1095 label: 'xxxxxxxxx', 1096 type: 'Text' 1097 }, 1098 { 1099 index: 4, 1100 name: 'Gender', 1101 label: 'xxxxxxxx', 1102 type: 'Text' 1103 }, 1104 ] 1105 }, 1106 { 1107 index: 1, 1108 name: 'equipInfo', 1109 label: 'Device information', 1110 children: [] 1111 }, 1112 { 1113 index: 2, 1114 name: 'appInfo', 1115 label: 'App usage', 1116 children: [] 1117 }, 1118 { 1119 index: 3, 1120 name: 'uploadInfo', 1121 label: 'Data you actively upload', 1122 children: [] 1123 }, 1124 { 1125 index: 4, 1126 name: 'tradeInfo', 1127 label: 'Transactions & assets', 1128 children: [] 1129 }, 1130 { 1131 index: 5, 1132 name: 'otherInfo', 1133 label: 'Other materials', 1134 children: [] 1135 }, 1136 ]; 1137 @State expandedItems: boolean[] = Array(this.routes.length).fill(false); 1138 @State selection: string | null = null; 1139 build() { 1140 Column() { 1141 // ... 1142 1143 List({ space: 10 }) { 1144 ForEach(this.routes, (itemGroup: ItemGroupInfo) => { 1145 ListItemGroup({ 1146 header: this.ListItemGroupHeader(itemGroup), 1147 style: ListItemGroupStyle.CARD, 1148 }) { 1149 if (this.expandedItems[itemGroup.index] && itemGroup.children) { 1150 ForEach(itemGroup.children, (item: ItemInfo) => { 1151 ListItem({ style: ListItemStyle.CARD }) { 1152 Row() { 1153 Text(item.name) 1154 Blank() 1155 if (item.type === 'Image') { 1156 Image(item.label) 1157 .height(20) 1158 .width(20) 1159 } else { 1160 Text(item.label) 1161 } 1162 Image($r('sys.media.ohos_ic_public_arrow_right')) 1163 .fillColor($r('sys.color.ohos_id_color_fourth')) 1164 .height(30) 1165 .width(30) 1166 } 1167 .width("100%") 1168 } 1169 .width("100%") 1170 .animation({ curve: curves.interpolatingSpring(0, 1, 528, 39) }) 1171 }) 1172 } 1173 }.clip(true) 1174 }) 1175 } 1176 .width("100%") 1177 } 1178 .width('100%') 1179 .height('100%') 1180 .justifyContent(FlexAlign.Start) 1181 .backgroundColor($r('sys.color.ohos_id_color_sub_background')) 1182 } 1183 ``` 1184 11853. Control whether each list item is expanded by changing the state of **ListItem**, and achieve the animation effects during the expanding and collapsing process through **animation** and **animateTo**. 1186 1187 ```ts 1188 @Builder 1189 ListItemGroupHeader(itemGroup: ItemGroupInfo) { 1190 Row() { 1191 Text(itemGroup.label) 1192 Blank() 1193 Image($r('sys.media.ohos_ic_public_arrow_down')) 1194 .fillColor($r('sys.color.ohos_id_color_fourth')) 1195 .height(30) 1196 .width(30) 1197 .rotate({ angle: !!itemGroup.children.length ? (this.expandedItems[itemGroup.index] ? 180 : 0) : 180 }) 1198 .animation({ curve: curves.interpolatingSpring(0, 1, 228, 22) }) 1199 } 1200 .width("100%") 1201 .padding(10) 1202 .animation({ curve: curves.interpolatingSpring(0, 1, 528, 39) }) 1203 .onClick(() => { 1204 if (itemGroup.children.length) { 1205 this.getUIContext()?.animateTo({ curve: curves.interpolatingSpring(0, 1, 528, 39) }, () => { 1206 this.expandedItems[itemGroup.index] = !this.expandedItems[itemGroup.index]; 1207 }) 1208 } 1209 }) 1210 } 1211 ``` 1212 1213## Switching the Layout Direction 1214 1215In certain scenarios, you may want a list to automatically scroll upward when new data is inserted at the bottom of the list to reveal the newly added items. Typical use cases include live-stream comments and instant messaging applications. By default, when the **List** component is using its normal layout, appending items to the bottom maintains the current content position without automatic scrolling. This behavior can be modified by switching the layout direction to achieve the desired effect. 1216 1217 **Figure 25** Real-time message scrolling 1218 1219 1220 12211. Define the list item data structure. 1222 1223 ```ts 1224 interface Message { 1225 id: number 1226 content: string 1227 sender: string 1228 } 1229 ``` 1230 12312. Construct a list structure and set **stackFromEnd** to **true**. In this way, the list content automatically scrolls upward to reveal newly inserted data items. 1232 1233 ```ts 1234 @State messages: Message[] = [ 1235 { id: 1, content: 'Welcome to the live stream!', sender: 'System' }, 1236 { id: 2, content: 'Hello everyone!', sender: 'Host' } 1237 ]; 1238 build() { 1239 Column() { 1240 List({ space: 10 }) { 1241 ForEach(this.messages, (item: Message) => { 1242 ListItem() { 1243 this.MessageItem(item) 1244 } 1245 }, (item: Message) => item.id.toString()) 1246 } 1247 .stackFromEnd(true) 1248 .layoutWeight(1) 1249 .alignListItem(ListItemAlign.Center) 1250 // ... 1251 } 1252 .width('100%') 1253 .height('100%') 1254 } 1255 ``` 1256 1257## Handling Swipe Release Events 1258 1259Since API version 20, scrollable components ([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), and [WaterFlow](../reference/apis-arkui/arkui-ts/ts-container-waterflow.md)) support swipe release event callbacks. These callbacks are triggered when the user lifts their finger from the screen, reporting the instantaneous swipe velocity. You can use the callbacks to implement custom scroll positioning effects, such as snap scrolling for short news items and free scrolling for long articles. 1260 1261 12621. Define the news item data structure. 1263 1264 ```ts 1265 // Structure reference 1266 class news { 1267 public id: string; 1268 public title: string; 1269 public content: string; 1270 public type: string; 1271 1272 constructor(id: string, title: string, content: string, type: string) { 1273 this.id = id; 1274 this.title = title; 1275 this.content = content; 1276 this.type = type; 1277 } 1278 } 1279 ``` 1280 12812. Construct news data, using **type** to distinguish between news item types. 1282 1283 ```ts 1284 // Implementation reference 1285 @State newsData: Array<news> = [ 1286 new news('1', 'Headline 1', 'Short news content for quick browsing', 'short'), 1287 new news('2', 'Headline 2', 'Another brief news item', 'short'), 1288 new news('3', 'Headline 3', 'Long-form article with detailed content. '.repeat(20), 'long'), 1289 new news('4', 'Headline 4', 'Quick news update', 'short'), 1290 new news('5', 'Headline 5', 'In-depth analysis piece. '.repeat(15), 'long') 1291 ]; 1292 ``` 1293 12943. Implement the swipe release event handling (**onWillStopDragging**) and news processing logic: 1295 - The **onWillStopDragging** callback reports the instantaneous swipe velocity when the user lifts their finger, with direction detection (positive value for upward swipes, negative for downward swipes). 1296 1297 ```ts 1298 // Implementation reference 1299 onWillStopDragging((velocity: number) => { 1300 if (velocity < 0) { 1301 // Handle downward swipes. 1302 } else { 1303 // Handle upward swipes. 1304 } 1305 }) 1306 ``` 1307 1308 - The current item's position information is obtained through the **getItemRect** API. 1309 1310 ```ts 1311 // Implementation reference 1312 let rect = this.scrollerForList.getItemRect(this.currentIndex); 1313 ``` 1314 1315 - For short news items, the list directly snaps to adjacent items. 1316 1317 ```ts 1318 // Implementation reference 1319 if (velocity > 10) { 1320 this.scrollerForList.scrollToIndex(this.currentIndex, true, ScrollAlign.START); 1321 } else if (velocity < -10) { 1322 this.scrollerForList.scrollToIndex(this.currentIndex + 1, true, ScrollAlign.START); 1323 } 1324 ``` 1325 1326 - For long news articles, the system calculates the remaining visible area to determine the optimal scroll end point. 1327 1328 ```ts 1329 let rect = this.scrollerForList.getItemRect(this.currentIndex); 1330 if (velocity < -30) { 1331 if (rect) { 1332 // Calculate the remaining visible portion of the current item. 1333 let leftRect = rect.y + rect.height; 1334 // Determine the scroll end point. 1335 let mainPosition = -velocity * DEFAULT_FRICTION / FRICTION_SCALE; 1336 if (leftRect + mainPosition > 0.75 * this.listHeight) { 1337 this.scrollerForList.scrollToIndex(this.currentIndex + 1, true, ScrollAlign.START); 1338 return; 1339 } else if (leftRect + mainPosition < 0.25 * this.listHeight) { 1340 this.scrollerForList.scrollToIndex(this.currentIndex, true, ScrollAlign.END, 1341 { extraOffset: LengthMetrics.vp(this.listHeight * 0.3) }) 1342 return; 1343 } 1344 } 1345 } else if (velocity > 30) { 1346 let leftRect = rect?.y + rect?.height; 1347 let mainPosition = velocity * DEFAULT_FRICTION / FRICTION_SCALE; 1348 if (leftRect + mainPosition > 0.75 * this.listHeight) { 1349 this.scrollerForList.scrollToIndex(this.currentIndex, true, ScrollAlign.START); 1350 return; 1351 } 1352 } 1353 ``` 1354 1355<!--RP2--><!--RP2End--> 1356