We use essential cookies for the website to function, as well as analytics cookies for analyzing and creating statistics of the website performance. To agree to the use of analytics cookies, click "Accept All". You can manage your preferences at any time by clicking "Cookie Settings" on the footer. More Information.

Only Essential Cookies
Accept All
ReferencesApplication FrameworkArkUIArkTS ComponentsScrolling and SwipingLazyDynamicLayout

LazyDynamicLayout

Phone26.0.0+PC/2in126.0.0+Tablet26.0.0+TV26.0.0+Wearable26.0.0+

This component implements a dynamic layout container that supports lazy loading and allows developers to customize the layout algorithm. It is suitable for scenarios where a large number of child components need to be displayed in a scrollable component. By loading and laying out only the child components within the visible area on demand, it reduces the first frame rendering time and memory overhead.

The parent components supported by this component include List, WaterFlow, FlowItem, Scroll, and LazyColumnLayout. It also supports being wrapped in a custom component or NodeContainer and then used in the above components.

NOTE
  • This module's APIs can only be used in the stage model.

  • The lazy loading support conditions for this component under different parent components are as follows:

    1. Under the WaterFlow component, lazy loading is supported only when WaterFlow is in single-column mode or in a single-column segment within a segmented layout.
    2. Under the List component, when lanes is greater than 1, chainAnimation is set to true, or scrollSnapAlign is set to a value other than ScrollSnapAlign.NONE, List does not use the nested lazy loading measurement process. In this case, this component is measured as a regular child item, and the lazy loading feature becomes ineffective.
    3. When used under the Scroll, List, or WaterFlow component, the scroll direction (horizontal or vertical) of Scroll, List, or WaterFlow must be the same as the layout direction of this component. If the layout directions differ, the app will crash.
  • When wrapped in FlowItem, LazyColumnLayout, a custom component, or NodeContainer, the framework searches upward along the parent component chain for a Scroll, List, or WaterFlow component that matches the layout direction of this component. The lazy loading support conditions are determined based on the upper-level scrollable component found.

Since: 26.0.0

Child Components

Child components are supported.

APIs

LazyDynamicLayout(algorithm: LazyLayoutAlgorithm)

Defines a lazy-loading dynamic layout container.

Since: 26.0.0

Model restriction: This API can be used only in the stage model.

Atomic service API: This API can be used in atomic services since API version 26.0.0.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Expand
Name Type Mandatory Description
algorithm LazyLayoutAlgorithm Yes Layout algorithm for the lazy-loading dynamic layout component. An instance of the LazyLayoutAlgorithm type must be passed in. You can customize the measurement and layout logic by inheriting LazyCustomLayoutAlgorithm. When obtaining child components or the total number of child components in a custom algorithm, use ExpandMode.LAZY_NOT_EXPAND and ChildrenCountMode.ALL_NOT_EXPAND respectively to prevent full loading from disabling lazy loading.

Attributes

The universal attributes are supported.

NOTE

When the layout algorithm is LazyCustomLayoutAlgorithm, the setMeasuredSize method of the LazyDynamicLayout component's FrameNode takes precedence over the size and border attributes, and the measure and layout methods of the child component's FrameNode take precedence over the ignoreLayoutSafeArea attribute. After the custom algorithm completes measurement or layout, the framework no longer executes the default measurement or layout process, but instead adopts the size and position set by the custom algorithm.

Events

The universal events are supported.

onVisibleIndexesChange

Phone26.0.0+PC/2in126.0.0+Tablet26.0.0+TV26.0.0+Wearable26.0.0+

onVisibleIndexesChange(callback: Callback<number[]> | undefined)

Sets the onVisibleIndexesChange callback. This callback is triggered when the list of child component indexes in the visible area of LazyDynamicLayout changes, and returns the list of child component indexes in the visible area.

Since: 26.0.0

Model restriction: This API can be used only in the stage model.

Atomic service API: This API can be used in atomic services since API version 26.0.0.

System capability: SystemCapability.ArkUI.ArkUI.Full

Parameters

Expand
Name Type Mandatory Description
callback Callback<number[]> | undefined Yes Callback triggered when the list of child component indexes in the visible area of LazyDynamicLayout changes. Returns the list of child component indexes in the visible area. When the input parameter is undefined, the listener is canceled.

Example

Example 1: Implementing Lazy-Loading Custom Layout

A custom lazy-loading list layout is implemented through the List and LazyDynamicLayout components, and the index is called back through onVisibleIndexesChange when the visible area changes.

LazyListLayout implements a custom lazy loading list layout algorithm. In the layout algorithm, the setAdjustedOffset API is used to ensure that the position of the first child component in the visible area remains unchanged when the spacing between child components changes.

MyDataSource implements the LazyForEach data source API IDataSource, which is used to provide child components to LazyDynamicLayout through LazyForEach.

The LazyDynamicLayout component is added since API version 26.0.0.

Collapse
Word wrap
Dark theme
Copy code
  1. import { LazyDynamicLayout, LazyDynamicLayoutAttribute } from '@kit.ArkUI';
  2. import { MyDataSource } from './MyDataSource';
  3. import { LazyListLayout } from './LazyListLayout';
  4. // Custom lazy-loading list layout component.
  5. @Component
  6. struct MyLazyListLayout {
  7. // Spacing size. Use @Watch to monitor changes, triggering the onSpaceChange method when changed.
  8. @Prop @Watch('onSpaceChange') space: number;
  9. arr: MyDataSource<string> = new MyDataSource<string>();
  10. private itemHeight: number = 100;
  11. // Lazy layout algorithm instance. Convert the height to pixel units.
  12. private lazyAlgorithm: LazyListLayout = new LazyListLayout(this.getUIContext().vp2px(this.itemHeight));
  13. // Update the spacing value in the layout algorithm when the spacing changes.
  14. onSpaceChange(): void {
  15. this.lazyAlgorithm.setSpace(this.getUIContext().vp2px(this.space));
  16. }
  17. aboutToAppear(): void {
  18. this.lazyAlgorithm.setSpace(this.getUIContext().vp2px(this.space));
  19. }
  20. build() {
  21. // Use the LazyDynamicLayout component and pass in the lazy layout algorithm.
  22. LazyDynamicLayout(this.lazyAlgorithm) {
  23. LazyForEach(this.arr, (item: string) => {
  24. Text(item)
  25. .height(this.itemHeight)
  26. .width('100%')
  27. .borderRadius(8)
  28. .backgroundColor('#E0E0FF')
  29. .padding(10)
  30. })
  31. }
  32. // Listen for changes in the indexes of child components in the visible area.
  33. .onVisibleIndexesChange((child: number[]) => {
  34. console.info(`onVisibleIndexesChange:start:${child}`);
  35. })
  36. }
  37. }
  38. // Define the group data interface.
  39. interface GroupData {
  40. title: string;
  41. data: MyDataSource<string>;
  42. }
  43. // Main page component.
  44. @Entry
  45. @Component
  46. struct CustomListLayoutTest {
  47. @State groupArr: GroupData[] = []; // Group data array.
  48. @State space: number = 5; // List item spacing.
  49. aboutToAppear(): void {
  50. for (let i = 0; i < 3; i++) {
  51. let data = new MyDataSource<string>();
  52. for (let j = 0; j < 10; j++) {
  53. data.pushData('item' + j.toString());
  54. }
  55. this.groupArr.push({ title: 'group' + i.toString(), data: data });
  56. }
  57. }
  58. build() {
  59. Stack({ alignContent: Alignment.Bottom }) {
  60. List() {
  61. ForEach(this.groupArr, (item: GroupData) => {
  62. ListItem() {
  63. Text(item.title).margin({ top: 20, bottom: 8 })
  64. }
  65. // Use the custom lazy-loading layout component.
  66. MyLazyListLayout({ arr: item.data, space: this.space })
  67. })
  68. }
  69. .layoutWeight(1)
  70. .padding({ left: 12, right: 12 })
  71. .height('100%')
  72. .width('100%')
  73. Button('Space:' + this.space.toString())
  74. .onClick(() => {
  75. // Switch the spacing between 5 and 10, and keep the position of the first child component in the visible area unchanged before and after the switch.
  76. this.space = this.space === 5 ? 10 : 5;
  77. })
  78. }
  79. .height('100%')
  80. .width('100%')
  81. }
  82. }
Collapse
Word wrap
Dark theme
Copy code
  1. // LazyListLayout.ets
  2. // Import layout-related interfaces and classes.
  3. import { LayoutConstraint, LazyLayoutHelper, LazyCustomLayoutAlgorithm, ExpandMode, ChildrenCountMode,
  4. LazyLayoutDirection } from '@kit.ArkUI';
  5. // Custom lazy-loading list layout algorithm, inherited from LazyCustomLayoutAlgorithm.
  6. export class LazyListLayout extends LazyCustomLayoutAlgorithm {
  7. private itemHeight: number = 320; // Height of each list item (in pixels).
  8. private totalHeight: number = 0; // Total height of the list.
  9. private childCnt: number = 0; // Total number of child components.
  10. private startIndex: number = -1; // Start index of the current visible area.
  11. private endIndex: number = -1; // End index of the current visible area.
  12. private space: number = 0; // Current spacing size.
  13. private prevSpace: number = 0; // Previous spacing value.
  14. selfNode?: FrameNode; // Reference to the own FrameNode.
  15. // Constructor that receives the list item height parameter.
  16. constructor(itemHeight: number) {
  17. super();
  18. this.itemHeight = itemHeight;
  19. }
  20. // Set the list item spacing.
  21. setSpace(value: number): void {
  22. if (this.space == value) {
  23. return;
  24. }
  25. this.prevSpace = this.space;
  26. this.space = value;
  27. // Trigger layout recalculation.
  28. this.selfNode?.setNeedsLayout();
  29. }
  30. // Measure child components and calculate the component size.
  31. onMeasure(self: FrameNode, constraint: LayoutConstraint, helper?: LazyLayoutHelper): void {
  32. // Obtain the total number of child components. The getChildrenCount API uses ChildrenCountMode.ALL_NOT_EXPAND to avoid full loading of child components when obtaining the total count, which would cause lazy loading to fail.
  33. this.childCnt = self.getChildrenCount(ChildrenCountMode.ALL_NOT_EXPAND);
  34. this.selfNode = self;
  35. // If no lazy loading helper is available, measure all child components.
  36. if (!helper) {
  37. this.measureAllChildren(self, constraint);
  38. self.setMeasuredSize({ width: constraint.maxSize.width, height: this.totalHeight });
  39. this.prevSpace = this.space;
  40. return;
  41. }
  42. // Obtain the start and end positions of the visible area.
  43. let viewStart = helper.getViewStart();
  44. let viewEnd = helper.getViewEnd();
  45. let prevTotalHeight = this.totalHeight;
  46. // Calculate the total list height: child component count * (child component height + spacing) - last spacing.
  47. this.totalHeight = Math.max(this.childCnt * (this.itemHeight + this.space) - this.space, 0);
  48. // Forward layout (top to bottom).
  49. if (helper.getLazyLayoutDirection() == LazyLayoutDirection.FORWARD) {
  50. // If the spacing changes, adjust the offset to keep the position of the first child component in the visible area unchanged.
  51. if (this.startIndex > 0 && this.startIndex < this.childCnt && this.prevSpace != this.space) {
  52. let adjustStartOffset = this.startIndex * (this.prevSpace - this.space);
  53. console.info(`Top setAdjustedOffset:${adjustStartOffset}`);
  54. helper.setAdjustedOffset(adjustStartOffset);
  55. viewStart -= adjustStartOffset;
  56. viewEnd -= adjustStartOffset;
  57. }
  58. } else {
  59. // Reverse layout (bottom to top).
  60. if (this.endIndex >= 0 && this.endIndex < this.childCnt - 1 && this.prevSpace != this.space) {
  61. let adjustEndOffset = (this.childCnt - 1 - this.endIndex) * (this.space - this.prevSpace);
  62. let adjustStartOffset = this.totalHeight - prevTotalHeight - adjustEndOffset;
  63. console.info(`Bottom setAdjustedOffset:${adjustEndOffset}`);
  64. helper.setAdjustedOffset(adjustEndOffset);
  65. viewStart += adjustStartOffset;
  66. viewEnd += adjustStartOffset;
  67. } else if (this.totalHeight != prevTotalHeight) {
  68. let adjustOffset = this.totalHeight - prevTotalHeight;
  69. viewStart += adjustOffset;
  70. viewEnd += adjustOffset;
  71. }
  72. }
  73. this.prevSpace = this.space;
  74. // If the visible area is not within the content range, clear the indexes.
  75. if (viewStart > this.totalHeight || viewEnd < 0 || this.childCnt == 0) {
  76. this.startIndex = -1;
  77. this.endIndex = -1;
  78. this.totalHeight = Math.max(this.childCnt * (this.itemHeight + this.space) - this.space, 0);
  79. self.setMeasuredSize({ width: constraint.maxSize.width, height: this.totalHeight });
  80. return;
  81. }
  82. // Calculate the start and end indexes of the visible area.
  83. let prevStartIndex = this.startIndex;
  84. let prevEndIndex = this.endIndex;
  85. this.startIndex = Math.floor(viewStart / (this.itemHeight + this.space));
  86. this.startIndex = Math.max(this.startIndex, 0);
  87. this.endIndex = Math.floor(viewEnd / (this.itemHeight + this.space));
  88. this.endIndex = Math.min(this.endIndex, this.childCnt - 1);
  89. // Measure child components in the visible area.
  90. for (let i = this.startIndex; i <= this.endIndex; i++) {
  91. // Use the ExpandMode.LAZY_NOT_EXPAND parameter when calling getChild to avoid full loading of child components, which would cause lazy loading to fail.
  92. let child = self.getChild(i, ExpandMode.LAZY_NOT_EXPAND);
  93. if (child) {
  94. child.measure(constraint);
  95. } else {
  96. console.error(`Get child[${i}] error`);
  97. }
  98. }
  99. // Collect the indexes of child components to be recycled.
  100. let recycleList: number[] = [];
  101. // If the start index moves backward, recycle the previous child components.
  102. if (prevStartIndex < this.startIndex) {
  103. for (let i = prevStartIndex; i < this.startIndex; i++) {
  104. recycleList.push(i);
  105. }
  106. }
  107. // If the end index moves forward, recycle the subsequent child components.
  108. if (prevEndIndex > this.endIndex) {
  109. for (let i = this.endIndex + 1; i <= prevEndIndex; i++) {
  110. recycleList.push(i);
  111. }
  112. }
  113. // Set the child components that are no longer visible to the inactive state.
  114. helper.setChildrenInactive(recycleList);
  115. // Set the measured size.
  116. self.setMeasuredSize({ width: constraint.maxSize.width, height: this.totalHeight });
  117. }
  118. // Measure all child components (non-lazy loading mode).
  119. private measureAllChildren(self: FrameNode, constraint: LayoutConstraint): void {
  120. for (let i = 0; i < this.childCnt; i++) {
  121. let child = self.getChild(i, ExpandMode.LAZY_NOT_EXPAND);
  122. if (child) {
  123. child.measure(constraint);
  124. } else {
  125. console.error(`Get child[${i}] error`);
  126. }
  127. }
  128. this.startIndex = 0;
  129. this.endIndex = this.childCnt - 1;
  130. this.totalHeight = Math.max(this.childCnt * (this.itemHeight + this.space) - this.space, 0);
  131. }
  132. // Layout method that determines the position of each child component.
  133. onLayout(self: FrameNode): void {
  134. if (this.childCnt == 0) {
  135. return;
  136. }
  137. // Layout the child components within the visible area.
  138. for (let i = this.startIndex; i <= this.endIndex; i++) {
  139. let child = self.getChild(i, ExpandMode.LAZY_NOT_EXPAND);
  140. child?.layout({ x: 0, y: i * (this.itemHeight + this.space) });
  141. }
  142. }
  143. }
Collapse
Word wrap
Dark theme
Copy code
  1. // MyDataSource.ets
  2. // Basic data source class that implements the IDataSource interface.
  3. export class BasicDataSource<T> implements IDataSource {
  4. private listeners: DataChangeListener[] = [];
  5. protected dataArray: T[] = [];
  6. public totalCount(): number {
  7. return this.dataArray.length;
  8. }
  9. public getData(index: number): T {
  10. return this.dataArray[index];
  11. }
  12. registerDataChangeListener(listener: DataChangeListener): void {
  13. if (this.listeners.indexOf(listener) < 0) {
  14. console.info('add listener');
  15. this.listeners.push(listener);
  16. }
  17. }
  18. unregisterDataChangeListener(listener: DataChangeListener): void {
  19. const pos = this.listeners.indexOf(listener);
  20. if (pos >= 0) {
  21. console.info('remove listener');
  22. this.listeners.splice(pos, 1);
  23. }
  24. }
  25. notifyDataReload(): void {
  26. this.listeners.forEach(listener => {
  27. listener.onDataReloaded();
  28. });
  29. }
  30. notifyDataAdd(index: number): void {
  31. this.listeners.forEach(listener => {
  32. listener.onDataAdd(index);
  33. });
  34. }
  35. notifyDataChange(index: number): void {
  36. this.listeners.forEach(listener => {
  37. listener.onDataChange(index);
  38. });
  39. }
  40. notifyDataDelete(index: number): void {
  41. this.listeners.forEach(listener => {
  42. listener.onDataDelete(index);
  43. });
  44. }
  45. notifyDataMove(from: number, to: number): void {
  46. this.listeners.forEach(listener => {
  47. listener.onDataMove(from, to);
  48. });
  49. }
  50. notifyDatasetChange(operations: DataOperation[]): void {
  51. this.listeners.forEach(listener => {
  52. listener.onDatasetChange(operations);
  53. });
  54. }
  55. }
  56. export class MyDataSource<T> extends BasicDataSource<T> {
  57. public shiftData(): void {
  58. this.dataArray.shift();
  59. this.notifyDataDelete(0);
  60. }
  61. public unshiftData(data: T): void {
  62. this.dataArray.unshift(data);
  63. this.notifyDataAdd(0);
  64. }
  65. public pushData(data: T): void {
  66. this.dataArray.push(data);
  67. this.notifyDataAdd(this.dataArray.length - 1);
  68. }
  69. public popData(): void {
  70. if (this.dataArray.length > 0) {
  71. this.dataArray.pop();
  72. this.notifyDataDelete(this.dataArray.length);
  73. }
  74. }
  75. public clearData(): void {
  76. this.dataArray = [];
  77. this.notifyDataReload();
  78. }
  79. }

Search in References
Enter a keyword.