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](../quick-start/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](../quick-start/arkts-rendering-control-overview.md) modes, including conditional rendering, rendering of repeated content, and lazy data loading. 9 10 11## Layout and Constraints 12 13A 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. 14 15As shown in the following figure, in a vertical list, **ListItemGroup** or **ListItem** components are automatically arranged vertically. 16 17**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. 18 19 **Figure 1** Relationships between List, ListItemGroup, and ListItem 20 21 22 23>**NOTE** 24> 25>A **List** component can contain only **ListItemGroup** or **ListItem** as its child components. **ListItemGroup** and **ListItem** must be used together with **List**. 26 27 28### Layout 29 30Apart from the aforementioned features, the list is also able to adapt to the number of elements in the cross axis direction. 31 32When used in vertical layout, the list can contain one or more scrollable columns, as shown below. 33 34 **Figure 2** Vertical scrolling list (left: one column; right: multiple columns) 35 36 37 38When used in horizontal layout, the list can contain one or more scrollable rows, as shown below. 39 40 **Figure 3** Horizontal scrolling list (left: one column; right: multiple columns) 41 42 43 44 45While **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**. 46 47### Constraints 48 49The 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. 50 51As 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. 52 53 **Figure 4** Main axis and cross axis of the list 54 55 56 57If 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. 58 59If 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**. 60 61In 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. 62 63 **Figure 5** Main axis height constraint example 1 (A: parent component of List; B: List component; C: all child components of List) 64 65 66 67If 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. 68 69In 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. 70 71 **Figure 6** Main axis height constraint example 2 (A: parent component of List; B: List component; C: all child components of List) 72 73 74 75If 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. 76 77 78## Developing the Layout 79 80 81### Setting the Main Axis Direction 82 83By 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. 84 85To create a horizontal scrolling list, set the **listDirection** attribute to **Axis.Horizontal**. The default value of **listDirection** is **Axis.Vertical**. 86 87 88```ts 89List() { 90 // ... 91} 92.listDirection(Axis.Horizontal) 93``` 94 95 96### Setting the Cross Axis Layout 97 98The 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. 99 100The lanes attribute of the **List** component is useful in building a list that auto-adapts the numbers of rows or columns on devices of different sizes. 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**. 101 102 103```ts 104List() { 105 // ... 106} 107.lanes(2) 108``` 109 110If 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. 111 112 113```ts 114@Entry 115@Component 116struct EgLanes { 117 @State egLanes: LengthConstrain = { minLength: 200, maxLength: 300 } 118 build() { 119 List() { 120 // ... 121 } 122 .lanes(this.egLanes) 123 } 124} 125``` 126 127For example, if the **lanes** attribute is set to **{ minLength: 200, maxLength: 300 }** for a vertical list, then: 128 129- When the list width is 300 vp, the list contains one column, because **minLength** is 200 vp. 130 131- When the list width changes to 400 vp, which is twice that of the **minLength** value, the list is automatically adapted to two-column. 132 133With 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. 134 135 136```ts 137List() { 138 // ... 139} 140.alignListItem(ListItemAlign.Center) 141``` 142 143 144## Displaying Data in the List 145 146The 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. 147 148 **Figure 7** Example of a city list 149 150 151 152```ts 153@Entry 154@Component 155struct CityList { 156 build() { 157 List() { 158 ListItem() { 159 Text('Beijing').fontSize(24) 160 } 161 162 ListItem() { 163 Text('Hangzhou').fontSize(24) 164 } 165 166 ListItem() { 167 Text('Shanghai').fontSize(24) 168 } 169 } 170 .backgroundColor('#FFF1F3F5') 171 .alignListItem(ListItemAlign.Center) 172 } 173} 174``` 175 176Each **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. 177 178 **Figure 8** Example of a contacts list 179 180 181 182As 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. 183 184 185```ts 186List() { 187 ListItem() { 188 Row() { 189 Image($r('app.media.iconE')) 190 .width(40) 191 .height(40) 192 .margin(10) 193 194 Text('Tom') 195 .fontSize(20) 196 } 197 } 198 199 ListItem() { 200 Row() { 201 Image($r('app.media.iconF')) 202 .width(40) 203 .height(40) 204 .margin(10) 205 206 Text('Tracy') 207 .fontSize(20) 208 } 209 } 210} 211``` 212 213 214## Iterating List Content 215 216Compared with a static list, a dynamic list is more common in applications. You can use [ForEach](../quick-start/arkts-rendering-control-foreach.md) to obtain data from the data source and create components for each data item. 217 218 For 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**s in **ForEach**, thereby reducing repeated code needed for tiling similar list items. 219 220 221```ts 222import { util } from '@kit.ArkTS' 223 224class Contact { 225 key: string = util.generateRandomUUID(true); 226 name: string; 227 icon: Resource; 228 229 constructor(name: string, icon: Resource) { 230 this.name = name; 231 this.icon = icon; 232 } 233} 234 235@Entry 236@Component 237struct SimpleContacts { 238 private contacts: Array<object> = [ 239 new Contact('Tom', $r ("app.media.iconA")), 240 new Contact('Tracy', $r ("app.media.iconB")), 241 ] 242 243 build() { 244 List() { 245 ForEach(this.contacts, (item: Contact) => { 246 ListItem() { 247 Row() { 248 Image(item.icon) 249 .width(40) 250 .height(40) 251 .margin(10) 252 Text(item.name).fontSize(20) 253 } 254 .width('100%') 255 .justifyContent(FlexAlign.Start) 256 } 257 }, (item: Contact) => JSON.stringify(item)) 258 } 259 .width('100%') 260 } 261} 262``` 263 264In 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). 265 266 267## Customizing the List Style 268 269 270### Setting the Spacing 271 272When initializing a list, you can use the **space** parameter to add spacing between list items. In the following example, a 10vp spacing is added between list items along the main axis: 273 274 275```ts 276List({ space: 10 }) { 277 // ... 278} 279``` 280 281 282### Adding Dividers 283 284A divider separates UI items to make them easier to identify. In the following figure, a divider is added between the setting items. Note that since the icons are easy to identify in their own right, the divers do not extend below the icons. 285 286 **Figure 9** Using dividers between the setting items 287 288 289 290To add dividers between list items, you can use the **divider** attribute together with the following style attributes:<br> **strokeWidth** and **color**: stroke width and color of the diver, respectively. 291 292**startMargin** and **endMargin**: distance between the divider and the start edge and end edge of the list, respectively. 293 294 295```ts 296class DividerTmp { 297 strokeWidth: Length = 1 298 startMargin: Length = 60 299 endMargin: Length = 10 300 color: ResourceColor = '#ffe9f0f0' 301 302 constructor(strokeWidth: Length, startMargin: Length, endMargin: Length, color: ResourceColor) { 303 this.strokeWidth = strokeWidth 304 this.startMargin = startMargin 305 this.endMargin = endMargin 306 this.color = color 307 } 308} 309@Entry 310@Component 311struct EgDivider { 312 @State egDivider: DividerTmp = new DividerTmp(1, 60, 10, '#ffe9f0f0') 313 build() { 314 List() { 315 // ... 316 } 317 .divider(this.egDivider) 318 } 319} 320``` 321 322This 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. 323 324>**NOTE** 325> 326>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. 327> 328>2. When a list contains multiple columns, the **startMargin** and **endMargin** attributes of the divider apply to each column. 329> 330>3. The divider is drawn between list items. No divider is drawn above the first list item and below the last list item. 331 332 333### Adding a Scrollbar 334 335When 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. 336 337 **Figure 10** Scrollbar of a list 338 339 340 341When 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. 342 343The default value of the **scrollBar attribute** is **BarState.Off** in API version 9 and earlier versions and **BarState.Auto** since API version 10. 344```ts 345List() { 346 // ... 347} 348.scrollBar(BarState.Auto) 349``` 350 351 352## Adding Grouping Support 353 354By 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. 355 356 **Figure 11** Contacts list with grouping 357 358 359 360You can use **ListItemGroup** to group items in the **List** component to build a two-dimensional list. 361 362A **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. 363 364 365```ts 366@Entry 367@Component 368struct ContactsList { 369 370 @Builder itemHead(text: string) { 371 // Header of the list group, corresponding to the group A and B locations. 372 Text(text) 373 .fontSize(20) 374 .backgroundColor('#fff1f3f5') 375 .width('100%') 376 .padding(5) 377 } 378 379 build() { 380 List() { 381 ListItemGroup({ header: this.itemHead('A') }) { 382 // Render the repeated list items of group A. 383 } 384 385 ListItemGroup({ header: this.itemHead('B') }) { 386 // Render the repeated list items of group B. 387 } 388 } 389 } 390} 391``` 392 393If 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). 394 395## Adding a Sticky Header 396 397The 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. 398 399Sticky 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. 400 401 **Figure 12** Sticky header 402 403 404 405You can set a sticky header or footer for a **ListItemGroup** component by setting the **sticky** attribute of its parent **List** component. 406 407Setting 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**. 408 409 410```ts 411import { util } from '@kit.ArkTS' 412class Contact { 413 key: string = util.generateRandomUUID(true); 414 name: string; 415 icon: Resource; 416 417 constructor(name: string, icon: Resource) { 418 this.name = name; 419 this.icon = icon; 420 } 421} 422class ContactsGroup { 423 title: string = '' 424 contacts: Array<object> | null = null 425 key: string = "" 426} 427export let contactsGroups: object[] = [ 428 { 429 title: 'A', 430 contacts: [ 431 new Contact('Alice', $r('app.media.iconA')), 432 new Contact('Ann', $r('app.media.iconB')), 433 new Contact('Angela', $r('app.media.iconC')), 434 ], 435 key: util.generateRandomUUID(true) 436 } as ContactsGroup, 437 { 438 title: 'B', 439 contacts: [ 440 new Contact('Ben', $r('app.media.iconD')), 441 new Contact('Bryan', $r('app.media.iconE')), 442 ], 443 key: util.generateRandomUUID(true) 444 } as ContactsGroup, 445 // ... 446] 447@Entry 448@Component 449struct ContactsList { 450 // Define the contactsGroups array. 451 @Builder itemHead(text: string) { 452 // Header of the list group, corresponding to the group A and B locations. 453 Text(text) 454 .fontSize(20) 455 .backgroundColor('#fff1f3f5') 456 .width('100%') 457 .padding(5) 458 } 459 build() { 460 List() { 461 // Render the ListItemGroup components cyclically. contactsGroups is the data set of contacts and titles of multiple groups. 462 ForEach(contactsGroups, (itemGroup: ContactsGroup) => { 463 ListItemGroup({ header: this.itemHead(itemGroup.title) }) { 464 // Render ListItem components cyclically. 465 if (itemGroup.contacts) { 466 ForEach(itemGroup.contacts, (item: Contact) => { 467 ListItem() { 468 // ... 469 } 470 }, (item: Contact) => JSON.stringify(item)) 471 } 472 } 473 }, (itemGroup: ContactsGroup) => JSON.stringify(itemGroup)) 474 }.sticky(StickyStyle.Header) // Set a sticky header. 475 } 476} 477``` 478 479 480## Controlling the Scrolling Position 481 482In some cases you may want to control the scrolling 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. 483 484 **Figure 13** Returning to the top of the list 485 486 487 488When 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. 489 490To start with, create a **Scroller** object **listScroller**. 491 492 493```ts 494private listScroller: Scroller = new Scroller(); 495``` 496 497Then, 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. 498 499 500```ts 501Stack({ alignContent: Alignment.Bottom }) { 502 // Use listScroller to initialize the scroller parameter to bind it with the List component. 503 List({ space: 20, scroller: this.listScroller }) { 504 // ... 505 } 506 507 Button() { 508 // ... 509 } 510 .onClick(() => { 511 // Specify where e to jump when the specific button is clicked, which is the top of the list in this example. 512 this.listScroller.scrollToIndex(0) 513 }) 514} 515``` 516 517 518## Responding to the Scrolling Position 519 520Many applications need to listen for the scrolling position change of the list and respond. 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. 521 522Another 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. 523 524**Figure 14** Alphabetical index bar's response to contacts list scrolling 525 526 527 528As 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. 529 530When 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. 531 532 533```ts 534const alphabets = ['#', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 535 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']; 536@Entry 537@Component 538struct ContactsList { 539 @State selectedIndex: number = 0; 540 private listScroller: Scroller = new Scroller(); 541 542 build() { 543 Stack({ alignContent: Alignment.End }) { 544 List({ scroller: this.listScroller }) {} 545 .onScrollIndex((firstIndex: number) => { 546 // Recalculate the value of this.selectedIndex in the alphabetical index bar based on the index of the item to which the list has scrolled. 547 }) 548 549 // AlphabetIndexer component 550 AlphabetIndexer({ arrayValue: alphabets, selected: 0 }) 551 .selected(this.selectedIndex) 552 } 553 } 554} 555``` 556 557>**NOTE** 558> 559>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. 560 561 562## Responding to Swipe on List Items 563 564Swipe 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). 565 566**Figure 15** Swipe-to-delete feature 567 568 569 570Swiping 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. 571 572In 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. 573 5741. Build the component that appears from the end edge when the list item is swiped left. 575 576 ```ts 577 @Builder itemEnd(index: number) { 578 // Build the component that appears from the end edge when the list item is swiped left. 579 Button({ type: ButtonType.Circle }) { 580 Image($r('app.media.ic_public_delete_filled')) 581 .width(20) 582 .height(20) 583 } 584 .onClick(() => { 585 // 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. 586 this.messages.splice(index, 1); 587 }) 588 } 589 ``` 590 5912. Binds the **swipeAction** attribute to a list item that can be swiped left. 592 593 ```ts 594 // When constructing a list, use ForEach to render list items based on the data source this.messages. 595 ListItem() { 596 // ... 597 } 598 .swipeAction({ 599 end: { 600 // index is the index of the list item. 601 builder: () => { this.itemEnd(index) }, 602 } 603 }) // Set the swipe action. 604 ``` 605 606## Adding a Badge to a List Item 607 608A 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. 609 610 **Figure 16** Adding a badge to a list item 611 612 613 614To 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. 615 616In this example, when implementing the **Image** component for presenting the profile picture of a list item, add it to **Badge** as a child component. 617 618In 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. 619 620 621```ts 622ListItem() { 623 Badge({ 624 count: 1, 625 position: BadgePosition.RightTop, 626 style: { badgeSize: 16, badgeColor: '#FA2A2D' } 627 }) { 628 // The Image component implements the contact profile picture. 629 // ... 630 } 631} 632``` 633 634 635## Implementing Pull-Down-to-Refresh and Pull-Up-to-Load 636 637The 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. 638 639The following describes the implementation of the pull-and-refresh feature: 640 6411. Listen for the finger press event and record the value of the initial position. 642 6432. 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. 644 6453. 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. 646 647> **NOTE** 648> 649> 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. 650 651<!--RP1--><!--RP1End--> 652 653<!--Del--> 654 <!--DelEnd--> 655 656 657## Editing a List 658 659The 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. 660 661The following uses to-do list management as an example to describe how to quickly add and delete list items. 662 663 664### Adding a List Item 665 666As 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. 667 668 **Figure 17** Adding a to-do task 669 670 671 672The process of implementing the addition feature is as follows: 673 6741. Define the list item data structure. In this example, a to-do data structure is defined. 675 676 ```ts 677 //ToDo.ets 678 import { util } from '@kit.ArkTS' 679 680 export class ToDo { 681 key: string = util.generateRandomUUID(true); 682 name: string; 683 684 constructor(name: string) { 685 this.name = name; 686 } 687 } 688 ``` 689 6902. Build the overall list layout and list items. 691 692 ```ts 693 //ToDoListItem.ets 694 import { ToDo } from './ToDo'; 695 @Component 696 export struct ToDoListItem { 697 @Link isEditMode: boolean 698 @Link selectedItems: ToDo[] 699 private toDoItem: ToDo = new ToDo(""); 700 701 build() { 702 Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { 703 // ... 704 } 705 .width('100%') 706 .height(80) 707 // .padding(): Set this parameter based on the use case. 708 .borderRadius(24) 709 // .linearGradient(): Set this parameter based on the use case. 710 .gesture( 711 GestureGroup(GestureMode.Exclusive, 712 LongPressGesture() 713 .onAction(() => { 714 // ... 715 }) 716 ) 717 ) 718 } 719 } 720 ``` 721 7223. Initialize the to-do list data and available items, and build the list layout and list items. 723 724 ```ts 725 //ToDoList.ets 726 import { ToDo } from './ToDo'; 727 import { ToDoListItem } from './ToDoListItem'; 728 729 @Entry 730 @Component 731 struct ToDoList { 732 @State toDoData: ToDo[] = [] 733 @Watch('onEditModeChange') @State isEditMode: boolean = false 734 @State selectedItems: ToDo[] = [] 735 private availableThings: string[] = ['Reading', 'Fitness', 'Travel','Music','Movie', 'Singing'] 736 737 onEditModeChange() { 738 if (!this.isEditMode) { 739 this.selectedItems = [] 740 } 741 } 742 743 build() { 744 Column() { 745 Row() { 746 if (this.isEditMode) { 747 Text('X') 748 .fontSize(20) 749 .onClick(() => { 750 this.isEditMode = false; 751 }) 752 .margin({ left: 20, right: 20 }) 753 } else { 754 Text('To-Do') 755 .fontSize(36) 756 .margin({ left: 40 }) 757 Blank() 758 Text('+') // Provide an entry for adding a list item, that is, add a click event for the add button. 759 .onClick(() => { 760 this.getUIContext().showTextPickerDialog({ 761 range: this.availableThings, 762 onAccept: (value: TextPickerResult) => { 763 let arr = Array.isArray(value.index) ? value.index : [value.index]; 764 for (let i = 0; i < arr.length; i++) { 765 this.toDoData.push(new ToDo(this.availableThings[arr[i]])); // Add to-do list items (available items). 766 } 767 }, 768 }) 769 }) 770 } 771 List({ space: 10 }) { 772 ForEach(this.toDoData, (toDoItem: ToDo) => { 773 ListItem() { 774 // Place each item of toDoData into the list item in the form of model. 775 ToDoListItem({ 776 isEditMode: this.isEditMode, 777 toDoItem: toDoItem, 778 selectedItems: this.selectedItems }) 779 } 780 }, (toDoItem: ToDo) => toDoItem.key.toString()) 781 } 782 } 783 } 784 } 785 } 786 ``` 787 788 789### Deleting a List Item 790 791As 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. 792 793 **Figure 18** Deleting a to-do task 794 795 796 797The process of implementing the deletion feature is as follows: 798 7991. 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. 800 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. 801 802 ```ts 803 // Structure reference 804 export class ToDo { 805 key: string = util.generateRandomUUID(true); 806 name: string; 807 toDoData: ToDo[] = []; 808 809 constructor(name: string) { 810 this.name = name; 811 } 812 } 813 ``` 814 ```ts 815 // Implementation reference 816 Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { 817 // ... 818 } 819 .gesture( 820 GestureGroup(GestureMode.Exclusive, 821 LongPressGesture() 822 .onAction(() => { 823 if (!this.isEditMode) { 824 this.isEditMode = true; // Enter the editing mode. 825 } 826 }) 827 ) 828 ) 829 ``` 830 8312. Respond to the user's selection and record the list items to be deleted. 832 In this to-do list example, the list items are selected or unselected according to the user's selection. 833 834 ```ts 835 // Structure reference 836 import { util } from '@kit.ArkTS' 837 export class ToDo { 838 key: string = util.generateRandomUUID(true); 839 name: string; 840 toDoData: ToDo[] = []; 841 842 constructor(name: string) { 843 this.name = name; 844 } 845 } 846 ``` 847 ```ts 848 // Implementation reference 849 if (this.isEditMode) { 850 Checkbox() 851 .onChange((isSelected) => { 852 if (isSelected) { 853 When this.selectedItems.push(toDoList.toDoItem) // this.selectedItems is selected, the selected list items are recorded. You can construct the list items based on the site requirements. 854 } else { 855 let index = this.selectedItems.indexOf(toDoList.toDoItem) 856 if (index !== -1) { 857 this.selectedItems.splice(index, 1) // When an item is deselected, it is deleted from the selectedItems array. 858 } 859 } 860 }) 861 } 862 ``` 863 8643. Respond to the user's clicking the delete button and delete the corresponding items from the list. 865 866 ```ts 867 // Structure reference 868 import { util } from '@kit.ArkTS' 869 export class ToDo { 870 key: string = util.generateRandomUUID(true); 871 name: string; 872 toDoData: ToDo[] = []; 873 874 constructor(name: string) { 875 this.name = name; 876 } 877 } 878 ``` 879 ```ts 880 // Implementation reference 881 Button('Delete') 882 .onClick(() => { 883 // 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. 884 let leftData = this.toDoData.filter((item) => { 885 return !this.selectedItems.find((selectedItem) => selectedItem == item); 886 }) 887 this.toDoData = leftData; 888 this.isEditMode = false; 889 }) 890 ``` 891 892 893## Handling a Long List 894 895[ForEach](../quick-start/arkts-rendering-control-foreach.md) is applicable to short lists. With regard to a long list with a large number of list items, using **ForEach** will greatly slow down page loading, as it loads all list items at once. Therefore, for better list performance, use [LazyForEach](../quick-start/arkts-rendering-control-lazyforeach.md) instead to implement on-demand iterative data loading. 896 897For details about the implementation, see the example in [LazyForEach: Lazy Data Loading](../quick-start/arkts-rendering-control-lazyforeach.md). 898 899When 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. This parameter sets the number of list items preloaded outside of the screen and is valid only in **LazyForEach**. 900 901 902```ts 903List() { 904 // ... 905}.cachedCount(3) 906``` 907 908The following uses a vertical list as an example: 909 910- If lazy loading is used for list items and the list contains only one column, the number of the list items to cache before and after the currently displayed one equals the value of **cachedCount**. If the list contains multiple columns, the number of the list items to cache is the value of **cachedCount** multiplied by the number of columns. 911 912- If lazy loading is used for list item groups, the number of the list item groups to cache before and after the currently displayed one equals the value of **cachedCount**, regardless of the number of columns. 913 914>**NOTE** 915> 916>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. 917> 918>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. 919 920 921## Collapsing and Expanding 922 923The collapsing and expanding of list items are widely used, often applied in scenarios such as displaying information lists and filling out forms. 924 925 **Figure 19** Collapsing and expanding of list items 926 927 928 929The process of implementing the collapsing and expanding effect of list items is as follows: 930 9311. Define the list item data structure. 932 933 ```ts 934 interface ItemInfo { 935 index: number, 936 name: string, 937 label: ResourceStr, 938 type?: string, 939 } 940 941 interface ItemGroupInfo extends ItemInfo { 942 children: ItemInfo[] 943 } 944 ``` 945 9462. Construct a list structure. 947 948 ```ts 949 @State routes: ItemGroupInfo[] = [ 950 { 951 index: 0, 952 name: 'basicInfo', 953 label: 'Basic personal information', 954 children: [ 955 { 956 index: 0, 957 name: 'Nickname', 958 label: 'xxxx', 959 type: 'Text' 960 }, 961 { 962 index: 1, 963 name: 'Profile picture', 964 label: $r('sys.media.ohos_user_auth_icon_face'), 965 type: 'Image' 966 }, 967 { 968 index: 2, 969 name: 'Age', 970 label: 'xxxx', 971 type: 'Text' 972 }, 973 { 974 index: 3, 975 name: 'Birthday', 976 label: 'xxxxxxxxx', 977 type: 'Text' 978 }, 979 { 980 index: 4, 981 name: 'Gender', 982 label: 'xxxxxxxx', 983 type: 'Text' 984 }, 985 ] 986 }, 987 { 988 index: 1, 989 name: 'equipInfo', 990 label: 'Device information', 991 children: [] 992 }, 993 { 994 index: 2, 995 name: 'appInfo', 996 label: 'App usage', 997 children: [] 998 }, 999 { 1000 index: 3, 1001 name: 'uploadInfo', 1002 label: 'Data you actively upload', 1003 children: [] 1004 }, 1005 { 1006 index: 4, 1007 name: 'tradeInfo', 1008 label: 'Transactions & assets', 1009 children: [] 1010 }, 1011 { 1012 index: 5, 1013 name: 'otherInfo', 1014 label: 'Other materials', 1015 children: [] 1016 }, 1017 ]; 1018 @State expandedItems: boolean[] = Array(this.routes.length).fill(false); 1019 @State selection: string | null = null; 1020 build() { 1021 Column() { 1022 // ... 1023 1024 List({ space: 10 }) { 1025 ForEach(this.routes, (itemGroup: ItemGroupInfo) => { 1026 ListItemGroup({ 1027 header: this.ListItemGroupHeader(itemGroup), 1028 style: ListItemGroupStyle.CARD, 1029 }) { 1030 if (this.expandedItems[itemGroup.index] && itemGroup.children) { 1031 ForEach(itemGroup.children, (item: ItemInfo) => { 1032 ListItem({ style: ListItemStyle.CARD }) { 1033 Row() { 1034 Text(item.name) 1035 Blank() 1036 if (item.type === 'Image') { 1037 Image(item.label) 1038 .height(20) 1039 .width(20) 1040 } else { 1041 Text(item.label) 1042 } 1043 Image($r('sys.media.ohos_ic_public_arrow_right')) 1044 .fillColor($r('sys.color.ohos_id_color_fourth')) 1045 .height(30) 1046 .width(30) 1047 } 1048 .width("100%") 1049 } 1050 .width("100%") 1051 .animation({ curve: curves.interpolatingSpring(0, 1, 528, 39) }) 1052 }) 1053 } 1054 }.clip(true) 1055 }) 1056 } 1057 .width("100%") 1058 } 1059 .width('100%') 1060 .height('100%') 1061 .justifyContent(FlexAlign.Start) 1062 .backgroundColor($r('sys.color.ohos_id_color_sub_background')) 1063 } 1064 ``` 1065 10663. 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**. 1067 1068 ```ts 1069 @Builder 1070 ListItemGroupHeader(itemGroup: ItemGroupInfo) { 1071 Row() { 1072 Text(itemGroup.label) 1073 Blank() 1074 Image($r('sys.media.ohos_ic_public_arrow_down')) 1075 .fillColor($r('sys.color.ohos_id_color_fourth')) 1076 .height(30) 1077 .width(30) 1078 .rotate({ angle: !!itemGroup.children.length ? (this.expandedItems[itemGroup.index] ? 180 : 0) : 180 }) 1079 .animation({ curve: curves.interpolatingSpring(0, 1, 228, 22) }) 1080 } 1081 .width("100%") 1082 .padding(10) 1083 .animation({ curve: curves.interpolatingSpring(0, 1, 528, 39) }) 1084 .onClick(() => { 1085 if (itemGroup.children.length) { 1086 this.getUIContext()?.animateTo({ curve: curves.interpolatingSpring(0, 1, 528, 39) }, () => { 1087 this.expandedItems[itemGroup.index] = !this.expandedItems[itemGroup.index] 1088 }) 1089 } 1090 }) 1091 } 1092 ``` 1093