• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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![en-us_image_0000001562940589](figures/en-us_image_0000001562940589.png)
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![en-us_image_0000001511580940](figures/en-us_image_0000001511580940.png)
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![en-us_image_0000001511421344](figures/en-us_image_0000001511421344.png)
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![en-us_image_0000001562940581](figures/en-us_image_0000001562940581.png)
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![en-us_image_0000001511580956](figures/en-us_image_0000001511580956.png)
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![en-us_image_0000001511740548](figures/en-us_image_0000001511740548.png)
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![](./figures/list_foreach.png)
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![](./figures/list_lazyforeach.png)
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![](./figures/list_repeatv.png)
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![](./figures/list_repeat.png)
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![en-us_image_0000001563060761](figures/en-us_image_0000001563060761.png)
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![en-us_image_0000001511421328](figures/en-us_image_0000001511421328.png)
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![en-us_image_0000001511580960](figures/en-us_image_0000001511580960.png)
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![en-us_image_0000001511740544](figures/en-us_image_0000001511740544.gif)
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![ScrollBar](figures/list_scrollbar.gif)
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![en-us_image_0000001511580948](figures/en-us_image_0000001511580948.png)
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![en-us_image_0000001511740552](figures/en-us_image_0000001511740552.gif)
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![en-us_image_0000001511900520](figures/en-us_image_0000001511900520.gif)
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![en-us_image_0000001563060769](figures/en-us_image_0000001563060769.gif)
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![en-us_image_0000001563060773](figures/en-us_image_0000001563060773.gif)
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![en-us_image_0000001511580952](figures/en-us_image_0000001511580952.png)
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![en-us_image_0000001511740556](figures/en-us_image_0000001511740556.gif)
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![en-us_image_0000001562820877](figures/en-us_image_0000001562820877.gif)
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![en-us_image_0000001949866104](figures/en-us_image_0000001949866104.gif)
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![en-us_image_0000001949866105](figures/en-us_image_0000001949866105.gif)
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