LazyVWaterFlowLayout

该组件用于实现支持懒加载的瀑布流布局,其父组件仅限于ListScrollWaterFlowFlowItemLazyColumnLayout,并支持使用自定义组件或NodeContainer组件封装后应用在上述组件中。

说明:

  • LazyVWaterFlowLayout组件高度默认自适应内容,不建议设置高度、高度约束或宽高比,设置后会导致显示异常。
  • 当父组件设置主轴方向尺寸时,LazyVWaterFlowLayout按照父组件可视区域进行懒加载;当父组件未设置主轴方向尺寸时,LazyVWaterFlowLayout会被内容撑开,导致所有子组件都会被加载布局。
  • 该组件在不同父组件下的懒加载支持条件如下:
    1. 在List组件下,要求List组件布局方向必须是竖直方向(即listDirection属性设置为Axis.Vertical),在非竖直方向的List中使用该组件会导致应用崩溃。当List设置了laneschainAnimationscrollSnapAlign属性中的任意一个时,该组件的懒加载功能会失效。
    2. 在Scroll组件下,要求Scroll组件布局方向必须是竖直方向(即scrollable属性设置为ScrollDirection.Vertical),在非竖直方向的Scroll中使用该组件会导致应用崩溃。
    3. 在WaterFlow组件下,仅在WaterFlow组件的单列模式或分段布局中的单列分段,并且布局方向为FlexDirection.Column时支持懒加载。当WaterFlow为多列模式或布局方向为FlexDirection.Row、FlexDirection.RowReverse时,该组件的懒加载功能会失效。此外,在布局方向为FlexDirection.ColumnReverse的WaterFlow组件下使用该组件会导致显示异常。
  • 当懒加载功能生效时,该组件仅加载父组件可视区域内的子组件,并在帧间空闲时隙预加载可视区域上方和下方各半屏的内容。
  • 此处的父组件指最靠近当前组件的上层滚动组件,其他文档下的具体含义请参考对应内容。

起始版本: 26.0.0

导入模块

import { LazyVWaterFlowLayout } from '@kit.ArkUI';

接口

LazyVWaterFlowLayout()

创建垂直方向的LazyVWaterFlowLayout组件。

起始版本: 26.0.0

原子化服务API: 从API版本26.0.0开始,该接口支持在原子化服务中使用。

模型约束: 此接口仅可在Stage模型下使用。

系统能力: SystemCapability.ArkUI.ArkUI.Full

属性

除支持通用属性外,还支持以下属性:

columnsTemplate

columnsTemplate(value: string | ItemFillPolicy | undefined)

设置当前LazyVWaterFlowLayout的列数,不设置时默认1列。

当value设置为string类型时,可设置当前LazyVWaterFlowLayout的列数或最小列宽值。例如,columnsTemplate('1fr 1fr 2fr')可将LazyVWaterFlowLayout分为3列,LazyVWaterFlowLayout宽分为4等份,第1列占1份,第2列占1份,第3列占2份。

可使用columnsTemplate('repeat(auto-fill, track-size)')根据给定的列宽track-size自动计算列数,其中repeat、auto-fill为关键字,track-size为可设置的宽度,支持的单位包括px、vp、%或有效数字,默认单位为vp。

当value设置为ItemFillPolicy类型时,将根据LazyVWaterFlowLayout组件宽度对应断点类型确定列数。

例如,ItemFillPolicy.BREAKPOINT_DEFAULT在组件宽度相当于sm及更小的设备上显示2列,相当于md设备时显示3列,相当于lg及更大的设备时显示5列,且每列均为1fr(表示每列占用1等份可用宽度)。

起始版本: 26.0.0

原子化服务API: 从API版本26.0.0开始,该接口支持在原子化服务中使用。

模型约束: 此接口仅可在Stage模型下使用。

系统能力: SystemCapability.ArkUI.ArkUI.Full

参数:

参数名 类型 必填 说明
value string | ItemFillPolicy | undefined 当前LazyVWaterFlowLayout的列数或最小列宽值。
方法入参为undefined时,恢复为默认值(1列)。

返回值:

类型 说明
T 返回当前组件。

columnsGap

columnsGap(value: LengthMetrics | undefined): T

设置列与列的间距。默认值为0vp,设置为小于0的值时,按默认值显示。

起始版本: 26.0.0

原子化服务API: 从API版本26.0.0开始,该接口支持在原子化服务中使用。

模型约束: 此接口仅可在Stage模型下使用。

系统能力: SystemCapability.ArkUI.ArkUI.Full

参数:

参数名 类型 必填 说明
value LengthMetrics | undefined 列与列的间距。
方法入参为undefined时,恢复为默认值(0vp)。

返回值:

类型 说明
T 返回当前组件。

rowsGap

rowsGap(value: LengthMetrics | undefined): T

设置行与行的间距。默认值为0vp,设置为小于0的值时,按默认值显示。

起始版本: 26.0.0

原子化服务API: 从API版本26.0.0开始,该接口支持在原子化服务中使用。

模型约束: 此接口仅可在Stage模型下使用。

系统能力: SystemCapability.ArkUI.ArkUI.Full

参数:

参数名 类型 必填 说明
value LengthMetrics | undefined 行与行的间距。
方法入参为undefined时,恢复为默认值(0vp)。

返回值:

类型 说明
T 返回当前组件。

header(builder: CustomBuilder | undefined)

设置当前LazyVWaterFlowLayout的头部组件。

说明:

头部组件位于容器顶部区域,通常用于展示标题、分组说明或其他固定在内容前方的元素。

当本组件随滚动容器滚动至可视区域内,且通过sticky设置了header吸顶模式时,header会吸附在滚动容器可视区域顶部。

起始版本: 26.0.0

原子化服务API: 从API版本26.0.0开始,该接口支持在原子化服务中使用。

模型约束: 此接口仅可在Stage模型下使用。

系统能力: SystemCapability.ArkUI.ArkUI.Full

参数:

参数名 类型 必填 说明
builder CustomBuilder | undefined 头部组件构造函数。
方法入参为undefined时,当前LazyVWaterFlowLayout不设置头部组件,如果已有头部组件,也会被移除。

footer(builder: CustomBuilder | undefined)

设置当前LazyVWaterFlowLayout的尾部组件。

说明:

尾部组件位于容器底部区域,通常用于展示补充信息、加载状态或其他固定在内容后方的元素。

当本组件随滚动容器滚动至可视区域内,且通过sticky设置了footer吸底模式时,footer会吸附在滚动容器可视区域底部。

起始版本: 26.0.0

原子化服务API: 从API版本26.0.0开始,该接口支持在原子化服务中使用。

模型约束: 此接口仅可在Stage模型下使用。

系统能力: SystemCapability.ArkUI.ArkUI.Full

参数:

参数名 类型 必填 说明
builder CustomBuilder | undefined 尾部组件构造函数。
方法入参为undefined时,当前LazyVWaterFlowLayout不设置尾部组件,如果已有尾部组件,也会被移除。

sticky

sticky(sticky: StickyStyle | undefined)

设置headerfooter的吸附效果。

当本组件随滚动容器滚动至可视区域内,且通过sticky设置header吸顶或footer吸底时,header会吸附在滚动容器可视区域顶部,footer会吸附在滚动容器可视区域底部。

说明:

由于浮点数计算精度,设置sticky后,在滚动过程中小概率产生缝隙,可以通过pixelRound指定当前组件向下像素取整解决该问题。

起始版本: 26.0.0

原子化服务API: 从API版本26.0.0开始,该接口支持在原子化服务中使用。

模型约束: 此接口仅可在Stage模型下使用。

系统能力: SystemCapability.ArkUI.ArkUI.Full

参数:

参数名 类型 必填 说明
sticky StickyStyle | undefined 头部组件和尾部组件的吸附模式。sticky属性可以设置为StickyStyle.Header或StickyStyle.Footer,也可以设置为StickyStyle.BOTH,以同时支持头部组件吸顶和尾部组件吸底。
方法入参为undefined时,恢复为默认值StickyStyle.None。
未通过该接口设置时,默认头部组件不吸顶、尾部组件不吸底。

事件

除支持通用事件外,还支持以下事件:

onVisibleIndexesChange

onVisibleIndexesChange(callback: OnVisibleIndexesChangeCallback | undefined): T

设置onVisibleIndexesChange回调函数。当LazyVWaterFlowLayout在可视区域内的子组件的索引值发生变化时触发回调,返回可视区域内子组件的起始索引值和结束索引值。

说明:

当父组件设置主轴方向尺寸时,LazyVWaterFlowLayout按照父组件可视区域进行懒加载。此时onVisibleIndexesChange回调中start返回当前可视区域起始位置子组件的索引值,end返回当前可视区域结束位置子组件的索引值。

当父组件未设置主轴方向尺寸时,LazyVWaterFlowLayout会被内容撑开,导致所有子组件都会被加载布局。此时onVisibleIndexesChange回调中start返回0,end返回数据源最后一个子组件的索引值。

此处的父组件指最靠近当前组件的上层滚动组件,其他文档下的具体含义请参考对应内容。

起始版本: 26.0.0

原子化服务API: 从API版本26.0.0开始,该接口支持在原子化服务中使用。

模型约束: 此接口仅可在Stage模型下使用。

系统能力: SystemCapability.ArkUI.ArkUI.Full

参数:

参数名 类型 必填 说明
callback OnVisibleIndexesChangeCallback | undefined 回调函数,当可见区域内的子组件索引发生变化时触发。
方法入参为undefined时,取消监听。

返回值:

类型 说明
T 返回当前组件。

示例

示例1(实现懒加载瀑布流布局)

通过Scroll和LazyVWaterFlowLayout组件实现懒加载瀑布流布局。

MyDataSource实现了LazyForEach数据源接口IDataSource,用于通过LazyForEach给LazyVWaterFlowLayout提供子组件。

从API版本26.0.0开始,新增支持LazyVWaterFlowLayout组件。

import { LengthMetrics, LazyVWaterFlowLayout, LazyVWaterFlowLayoutAttribute } from '@kit.ArkUI';
import { MyDataSource } from './MyDataSource';

@Entry
@Component
struct LazyVWaterFlowLayoutSample1 {
  private arr : MyDataSource<number> = new MyDataSource<number>();

  // 返回随机高度
  private itemHeight(index: number): number {
    return 80 + (index * 37 % 121)
  }

  private itemColor(index: number): string {
    const colors: string[] = ['#FFE0B2', '#C8E6C9', '#BBDEFB', '#F8BBD0', '#D1C4E9', '#FFF9C4']
    return colors[index % colors.length]
  }

  build() {
    Column() {
      Scroll() {
        LazyVWaterFlowLayout() {
          LazyForEach(this.arr, (item: number) => {
            Column() {
              Text('item ' + item.toString())
                .fontSize(16)
                .fontColor(Color.Black)
            }
            .height(this.itemHeight(item))
            .width('100%')
            .borderRadius(8)
            .backgroundColor(this.itemColor(item))
            .justifyContent(FlexAlign.Center)
          })
        }
        .columnsTemplate('1fr 1fr')
        .rowsGap(LengthMetrics.vp(10))
        .columnsGap(LengthMetrics.vp(10))
        .onVisibleIndexesChange((start: number, end: number) => {
          // 滚动监听:即将触底时提前加载更多数据
          if (end + 20 >= this.arr.totalCount()) {
            // 添加100个新数据到数据源
            let currentCount = this.arr.totalCount();
            for (let i = currentCount; i < currentCount + 100; i++) {
              this.arr.pushData(i);
            }
          }
        })
      }
      .padding(10)
      .layoutWeight(1)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#DCDCDC')
  }

  aboutToAppear(): void {
    for (let i = 0; i < 100; i++) {
      this.arr.pushData(i);
    }
  }
}
// MyDataSource.ets
export class BasicDataSource<T> implements IDataSource {
  private listeners: DataChangeListener[] = [];
  protected dataArray: T[] = [];

  public totalCount(): number {
    return this.dataArray.length;
  }

  public getData(index: number): T {
    return this.dataArray[index];
  }

  registerDataChangeListener(listener: DataChangeListener): void {
    if (this.listeners.indexOf(listener) < 0) {
      console.info('add listener');
      this.listeners.push(listener);
    }
  }

  unregisterDataChangeListener(listener: DataChangeListener): void {
    const pos = this.listeners.indexOf(listener);
    if (pos >= 0) {
      console.info('remove listener');
      this.listeners.splice(pos, 1);
    }
  }

  notifyDataReload(): void {
    this.listeners.forEach(listener => {
      listener.onDataReloaded();
    })
  }

  notifyDataAdd(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataAdd(index);
    })
  }

  notifyDataChange(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataChange(index);
    })
  }

  notifyDataDelete(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataDelete(index);
    })
  }

  notifyDataMove(from: number, to: number): void {
    this.listeners.forEach(listener => {
      listener.onDataMove(from, to);
    })
  }

  notifyDatasetChange(operations: DataOperation[]): void {
    this.listeners.forEach(listener => {
      listener.onDatasetChange(operations);
    })
  }
}

export class MyDataSource<T> extends BasicDataSource<T> {
  public shiftData(): void {
    this.dataArray.shift();
    this.notifyDataDelete(0);
  }
  public unshiftData(data: T): void {
    this.dataArray.unshift(data);
    this.notifyDataAdd(0);
  }
  public pushData(data: T): void {
    this.dataArray.push(data);
    this.notifyDataAdd(this.dataArray.length - 1);
  }

  public popData(): void {
    this.dataArray.pop();
    this.notifyDataDelete(this.dataArray.length);
  }

  public clearData(): void {
    this.dataArray = [];
    this.notifyDataReload();
  }
}

scroll_lazyvwaterflowlayout.png

示例2(设置头部组件或尾部组件及吸附效果)

该示例通过Scroll嵌套LazyVWaterFlowLayout,并通过headerfootersticky实现瀑布流顶部和底部吸附效果。滚动过程中header吸附在可视区域顶部,footer吸附在可视区域底部。

从API版本26.0.0开始,新增支持header、footer和sticky属性。

import { LengthMetrics, LazyVWaterFlowLayout, LazyVWaterFlowLayoutAttribute } from '@kit.ArkUI';
import { MyDataSource } from './MyDataSource';

@Entry
@Component
struct LazyVWaterFlowLayoutStickyDemo {
  private arr : MyDataSource<number> = new MyDataSource<number>();

  // 返回随机高度
  private itemHeight(index: number): number {
    return 80 + (index * 37 % 121)
  }

  private itemColor(index: number): string {
    const colors: string[] = ['#FFE0B2', '#C8E6C9', '#BBDEFB', '#F8BBD0', '#D1C4E9', '#FFF9C4']
    return colors[index % colors.length]
  }

  @Builder
  HeaderBuilder() {
    Column() {
      Text('Header')
        .fontSize(16)
        .fontColor(Color.Black)
    }
    .height(50)
    .width('100%')
    .borderRadius(8)
    .backgroundColor('#BBDEFB')
    .justifyContent(FlexAlign.Center)
  }

  @Builder
  FooterBuilder() {
    Column() {
      Text('Footer')
        .fontSize(16)
        .fontColor(Color.Black)
    }
    .height(40)
    .width('100%')
    .borderRadius(8)
    .backgroundColor('#D1C4E9')
    .justifyContent(FlexAlign.Center)
  }

  build() {
    Column() {
      Scroll() {
        LazyVWaterFlowLayout() {
          LazyForEach(this.arr, (item: number) => {
            Column() {
              Text('item ' + item.toString())
                .fontSize(16)
                .fontColor(Color.Black)
            }
            .height(this.itemHeight(item))
            .width('100%')
            .borderRadius(8)
            .backgroundColor(this.itemColor(item))
            .justifyContent(FlexAlign.Center)
          })
        }
        .columnsTemplate('1fr 1fr')
        .rowsGap(LengthMetrics.vp(10))
        .columnsGap(LengthMetrics.vp(10))
        .header(this.HeaderBuilder)
        .footer(this.FooterBuilder)
        .sticky(StickyStyle.BOTH)
        .onVisibleIndexesChange((start: number, end: number) => {
          // 滚动监听:即将触底时提前加载更多数据
          if (end + 20 >= this.arr.totalCount()) {
            // 添加100个新数据到数据源
            let currentCount = this.arr.totalCount();
            for (let i = currentCount; i < currentCount + 100; i++) {
              this.arr.pushData(i);
            }
          }
        })
      }
      .padding(10)
      .layoutWeight(1)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#DCDCDC')
  }

  aboutToAppear(): void {
    for (let i = 0; i < 100; i++) {
      this.arr.pushData(i);
    }
  }
}

scroll_lazyvwaterflowlayout_header_footer.gif