鸿蒙5.0版开发:ArkTS容器组件(WaterFlow)

89 篇文章 0 订阅
85 篇文章 0 订阅

往期鸿蒙全套实战文章必看:


WaterFlow

瀑布流容器,由“行”和“列”分割的单元格所组成,通过容器自身的排列规则,将不同大小的“项目”自上而下,如瀑布般紧密布局。

说明:

该组件从API Version 9 开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。

子组件

包含FlowItem子组件。

说明:

WaterFlow子组件的visibility属性设置为None时不显示,但该子组件周围的columnsGap、rowsGap、margin仍会生效。

接口

WaterFlow(options?: WaterFlowOptions)

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

参数:

参数名参数类型必填参数描述
optionsWaterFlowOptions瀑布流组件参数。

WaterFlowOptions对象说明

参数名参数类型必填参数描述
footerCustomBuilder设置WaterFlow尾部组件。
原子化服务API: 从API version 11开始,该接口支持在原子化服务中使用。
scrollerScroller可滚动组件的控制器,与可滚动组件绑定。
说明:
不允许和其他滚动类组件,如:ListGridScroll等绑定同一个滚动控制对象。
原子化服务API: 从API version 11开始,该接口支持在原子化服务中使用。
sections12+WaterFlowSections设置FlowItem分组,实现同一个瀑布流组件内部各分组使用不同列数混合布局。
说明:
1. 使用分组混合布局时会忽略columnsTemplate和rowsTemplate属性。
2. 使用分组混合布局时不支持单独设置footer,可以使用最后一个分组作为尾部组件。

WaterFlowSections12+

瀑布流分组信息。

splice12+

splice(start: number, deleteCount?: number, sections?: Array<SectionOptions>): boolean

移除或者替换已存在的分组和/或添加新分组。

参数:

名称类型必填描述
startnumber从0开始计算的索引,会转换为整数,表示要开始改变分组的位置。
说明:
1. 如果索引是负数,则从末尾开始计算,使用start + WaterFlowSections.length()
2. 如果 start < -WaterFlowSections.length(),则使用0。
3. 如果 start >= WaterFlowSections.length(),则在最后添加新分组。
deleteCountnumber表示要从start开始删除的分组数量。
说明:
1. 如果省略了deleteCount,或者其值大于或等于由start指定的位置到WaterFlowSections末尾的分组数量,那么从start到WaterFlowSections末尾的所有分组将被删除。
2. 如果deleteCount是0或者负数,则不会删除任何分组。
sectionsArray<SectionOptions>表示要从start开始加入的分组。如果不指定,splice()将只从瀑布流中删除分组。

返回值:

类型说明
boolean分组是否修改成功,要加入的分组中有任意分组的itemsCount不是正整数时返回false。

push12+

push(section: SectionOptions): boolean

将指定分组添加到瀑布流末尾。

参数:

名称类型必填描述
sectionSectionOptions添加到瀑布流末尾的分组。

返回值:

类型说明
boolean分组是否添加成功,新分组的itemsCount不是正整数时返回false。

update12+

update(sectionIndex: number, section: SectionOptions): boolean

修改指定索引分组的配置信息。

参数:

名称类型必填描述
sectionIndexnumber从0开始计算的索引,会转换为整数,表示要修改的分组的位置。
说明:
1. 如果索引是负数,则从末尾开始计算,使用sectionIndex + WaterFlowSections.length()
2. 如果sectionIndex < -WaterFlowSections.length(),则使用0。
3. 如果sectionIndex >= WaterFlowSections.length(),则在最后添加新分组。
sectionSectionOptions新的分组信息。

返回值:

类型说明
boolean分组是否更新成功,新分组的itemsCount不是正整数时返回false。

values12+

values(): Array<SectionOptions>

获取瀑布流中所有分组配置信息。

返回值:

类型说明
Array<SectionOptions>瀑布流中所有分组配置信息。

length12+

length(): number

获取瀑布流中分组数量。

返回值:

类型说明
number瀑布流中分组数量。

SectionOptions12+

FlowItem分组配置信息。

参数:

名称类型必填描述
itemsCountnumber分组中FlowItem数量,必须是正整数。
crossCountnumber纵向布局时为列数,横向布局时为行数,默认值:1。
columnsGapDimension该分组的列间距,不设置时使用瀑布流的columnsGap,设置非法值时使用0vp。
rowsGapDimension该分组的行间距,不设置时使用瀑布流的rowsGap,设置非法值时使用0vp。
marginMargin | Dimension该分组的外边距参数为Length类型时,四个方向外边距同时生效。
默认值:0
单位:vp
margin设置百分比时,上下左右外边距均以瀑布流的width作为基础值。
onGetItemMainSizeByIndexGetItemMainSizeByIndex瀑布流组件布局过程中获取指定index的FlowItem的主轴大小,纵向瀑布流时为高度,横向瀑布流时为宽度,单位vp。
说明:
1. 同时使用onGetItemMainSizeByIndex和FlowItem的宽高属性时,主轴大小以onGetItemMainSizeByIndex返回结果为准。
2. 使用onGetItemMainSizeByIndex可以提高瀑布流跳转到指定位置或index时的效率,避免混用设置onGetItemMainSizeByIndex和未设置的分组,会导致布局异常。
3. onGetItemMainSizeByIndex返回负数时FlowItem高度为0。

GetItemMainSizeByIndex12+

type GetItemMainSizeByIndex = (index: number) => number

根据index获取指定Item的主轴大小。

参数:

名称类型必填描述
indexnumberFlowItem在WaterFlow中的索引。

返回值:

类型说明
number指定index的FlowItem的主轴大小,纵向瀑布流时为高度,横向瀑布流时为宽度,单位vp。

属性

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

columnsTemplate

columnsTemplate(value: string)

设置当前瀑布流组件布局列的数量,不设置时默认1列。

例如, ‘1fr 1fr 2fr’ 是将父组件分3列,将父组件允许的宽分为4等份,第一列占1份,第二列占1份,第三列占2份。

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

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

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

参数:

参数名类型必填说明
valuestring当前瀑布流组件布局列的数量。
默认值:‘1fr’

rowsTemplate

rowsTemplate(value: string)

设置当前瀑布流组件布局行的数量,不设置时默认1行。

例如, '1fr 1fr 2fr’是将父组件分三行,将父组件允许的高分为4等份,第一行占1份,第二行占一份,第三行占2份。

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

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

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

参数:

参数名类型必填说明
valuestring当前瀑布流组件布局行的数量。
默认值:‘1fr’

itemConstraintSize

itemConstraintSize(value: ConstraintSizeOptions)

设置约束尺寸,子组件布局时,进行尺寸范围限制。

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

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

参数:

参数名类型必填说明
valueConstraintSizeOptions约束尺寸。

columnsGap

columnsGap(value: Length)

设置列与列的间距。

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

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

参数:

参数名类型必填说明
valueLength列与列的间距。
默认值:0

rowsGap

rowsGap(value: Length)

设置行与行的间距。

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

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

参数:

参数名类型必填说明
valueLength行与行的间距。
默认值:0

layoutDirection

layoutDirection(value: FlexDirection)

设置布局的主轴方向。

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

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

参数:

参数名类型必填说明
valueFlexDirection布局的主轴方向。
默认值:FlexDirection.Column

layoutDirection优先级高于rowsTemplate和columnsTemplate。根据layoutDirection设置情况,分为以下三种设置模式:

  • layoutDirection设置纵向布局(FlexDirection.Column 或 FlexDirection.ColumnReverse)

    此时columnsTemplate有效(如果未设置,取默认值)。例如columnsTemplate设置为"1fr 1fr"、rowsTemplate设置为"1fr 1fr 1fr"时,瀑布流组件纵向布局,辅轴均分成横向2列。

  • layoutDirection设置横向布局(FlexDirection.Row 或 FlexDirection.RowReverse)

    此时rowsTemplate有效(如果未设置,取默认值)。例如columnsTemplate设置为"1fr 1fr"、rowsTemplate设置为"1fr 1fr 1fr"时,瀑布流组件横向布局,辅轴均分成纵向3列。

  • layoutDirection未设置布局方向

    布局方向为layoutDirection的默认值:FlexDirection.Column,此时columnsTemplate有效。例如columnsTemplate设置为"1fr 1fr"、rowsTemplate设置为"1fr 1fr 1fr"时,瀑布流组件纵向布局,辅轴均分成横向2列。

enableScrollInteraction10+

enableScrollInteraction(value: boolean)

设置是否支持滚动手势,当设置为false时,无法通过手指或者鼠标滚动,但不影响控制器的滚动接口。

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

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

参数:

参数名类型必填说明
valueboolean是否支持滚动手势。
默认值:true

nestedScroll10+

nestedScroll(value: NestedScrollOptions)

设置向前向后两个方向上的嵌套滚动模式,实现与父组件的滚动联动。

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

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

参数:

参数名类型必填说明
valueNestedScrollOptions嵌套滚动选项。

friction10+

friction(value: number | Resource)

设置摩擦系数,手动划动滚动区域时生效,只对惯性滚动过程有影响,对惯性滚动过程中的链式效果有间接影响。设置为小于等于0的值时,按默认值处理。

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

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

参数:

参数名类型必填说明
valuenumber | Resource摩擦系数。
默认值:非可穿戴设备为0.6,可穿戴设备为0.9。
从API version 11开始,非可穿戴设备默认值为0.7。

cachedCount11+

cachedCount(value: number)

设置预加载的FlowItem的数量,只在LazyForEach中生效。设置该属性后会缓存cachedCount个FlowItem。LazyForEach超出显示和缓存范围的FlowItem会被释放。设置为小于0的值时,按默认值显示。

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

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

参数:

参数名类型必填说明
valuenumber预加载的FlowItem的数量。
默认值:1

scrollBar11+

scrollBar(barState: BarState)

设置滚动条状态。滚动条位置和长度以已布局过的总高度和当前偏移为准,在瀑布流布局全部子节点之前随着滑动持续变化。

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

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

参数:

参数名类型必填说明
barStateBarState滚动条状态。
默认值:BarState.Off

scrollBarWidth11+

scrollBarWidth(value: number | string)

设置滚动条的宽度,不支持百分比设置。宽度设置后,滚动条正常状态和按压状态宽度均为滚动条的宽度值。如果滚动条的宽度超过WaterFlow组件主轴方向的高度,则滚动条的宽度会变为默认值。

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

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

参数:

参数名类型必填说明
valuenumber | string滚动条的宽度。
默认值:4
单位:vp

scrollBarColor11+

scrollBarColor(color: Color | number | string)

设置滚动条的颜色。

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

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

参数:

参数名类型必填说明
valuenumber | string | Color滚动条的颜色。

edgeEffect11+

edgeEffect(value: EdgeEffect, options?: EdgeEffectOptions)

设置边缘滑动效果。

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

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

参数:

参数名类型必填说明
valueEdgeEffect设置瀑布流组件的边缘滑动效果,支持弹簧效果和阴影效果。
默认值:EdgeEffect.None
options11+EdgeEffectOptions设置组件内容大小小于组件自身时,是否开启滑动效果。
默认值:false

flingSpeedLimit11+

flingSpeedLimit(speedLimit: number)

限制跟手滑动结束后,Fling动效开始时的最大初始速度。单位是vp/s。

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

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

参数:

参数名类型必填说明
speedLimitnumberFling动效开始时的最大初始速度。

事件

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

onReachStart

onReachStart(event: () => void)

瀑布流组件到达起始位置时触发。

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

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

onReachEnd

onReachEnd(event: () => void)

瀑布流组件到底末尾位置时触发。

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

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

onScrollFrameBegin10+

onScrollFrameBegin(event: (offset: number, state: ScrollState) => { offsetRemain: number; })

瀑布流开始滑动时触发,事件参数传入即将发生的滑动量,事件处理函数中可根据应用场景计算实际需要的滑动量并作为事件处理函数的返回值返回,瀑布流将按照返回值的实际滑动量进行滑动。

触发该事件的条件:手指拖动WaterFlow、WaterFlow惯性划动时每帧开始时触发;WaterFlow超出边缘回弹、使用滚动控制器和拖动滚动条的滚动不会触发。

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

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

参数:

参数名类型必填说明
offsetnumber即将发生的滑动量,单位vp。
stateScrollState当前滑动状态。

返回值:

类型说明
{ offsetRemain: number }实际滑动量,单位vp。

onScrollIndex11+

onScrollIndex(event: (first: number, last: number) => void)

当前瀑布流显示的起始位置/终止位置的子组件发生变化时触发。瀑布流初始化时会触发一次。

瀑布流显示区域上第一个子组件/最后一个组件的索引值有变化就会触发。

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

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

参数:

参数名类型必填说明
firstnumber当前显示的WaterFlow起始位置的索引值。
lastnumber当前显示的瀑布流终止位置的索引值。

onScrollStart11+

onScrollStart(event: () => void)

瀑布流滑动开始时触发。手指拖动瀑布流或瀑布流的滚动条触发的滑动开始时,会触发该事件。使用Scroller滑动控制器触发的带动画的滑动,动画开始时会触发该事件。

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

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

onScrollStop11+

onScrollStop(event: () => void)

瀑布流滑动停止时触发。手指拖动瀑布流或瀑布流的滚动条触发的滑动,手指离开屏幕并且滑动停止时会触发该事件;使用Scroller滑动控制器触发的带动画的滑动,动画停止会触发该事件。

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

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

onScroll(deprecated)

onScroll(event: (scrollOffset: number, scrollState: ScrollState) => void)

瀑布流滑动时触发。

从API version11开始使用。

从API version12开始废弃不再使用,建议使用onDidScroll替代。

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

参数:

参数名类型必填说明
scrollOffsetnumber每帧滚动的偏移量,Grid的内容向上滚动时偏移量为正,向下滚动时偏移量为负。
单位vp。
scrollStateScrollState当前滑动状态。

onWillScroll12+

onWillScroll(handler: Optional<OnWillScrollCallback>)

瀑布流滑动前触发,返回当前帧将要滑动的偏移量,当前滑动状态和滑动操作来源,其中回调的偏移量为计算得到的将要滑动的偏移量值,并非最终实际滑动偏移。可以通过该回调返回值指定瀑布流将要滑动的偏移。

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

参数:

参数名类型必填说明
handlerOptional<OnWillScrollCallback>瀑布流滑动前触发的回调。

说明:

调用ScrollEdge和不带动画的ScrollToIndex时,不触发onWillScroll。

onDidScroll12+

onDidScroll(handler: OnScrollCallback)

瀑布流滑动时触发,返回当前帧滑动的偏移量和当前滑动状态。

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

参数:

参数名类型必填说明
handlerOnScrollCallback瀑布流滑动时触发的回调。

示例

示例1

WaterFlow的基本使用。

// WaterFlowDataSource.ets

// 实现IDataSource接口的对象,用于瀑布流组件加载数据
export class WaterFlowDataSource implements IDataSource {
  private dataArray: number[] = []
  private listeners: DataChangeListener[] = []

  constructor() {
    for (let i = 0; i < 100; i++) {
      this.dataArray.push(i)
    }
  }

  // 获取索引对应的数据
  public getData(index: number): number {
    return this.dataArray[index]
  }

  // 通知控制器数据重新加载
  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);
    })
  }

  // 获取数据总数
  public totalCount(): number {
    return this.dataArray.length
  }

  // 注册改变数据的控制器
  registerDataChangeListener(listener: DataChangeListener): void {
    if (this.listeners.indexOf(listener) < 0) {
      this.listeners.push(listener)
    }
  }

  // 注销改变数据的控制器
  unregisterDataChangeListener(listener: DataChangeListener): void {
    const pos = this.listeners.indexOf(listener)
    if (pos >= 0) {
      this.listeners.splice(pos, 1)
    }
  }

  // 增加数据
  public add1stItem(): void {
    this.dataArray.splice(0, 0, this.dataArray.length)
    this.notifyDataAdd(0)
  }

  // 在数据尾部增加一个元素
  public addLastItem(): void {
    this.dataArray.splice(this.dataArray.length, 0, this.dataArray.length)
    this.notifyDataAdd(this.dataArray.length - 1)
  }

  // 在指定索引位置增加一个元素
  public addItem(index: number): void {
    this.dataArray.splice(index, 0, this.dataArray.length)
    this.notifyDataAdd(index)
  }

  // 删除第一个元素
  public delete1stItem(): void {
    this.dataArray.splice(0, 1)
    this.notifyDataDelete(0)
  }

  // 删除第二个元素
  public delete2ndItem(): void {
    this.dataArray.splice(1, 1)
    this.notifyDataDelete(1)
  }

  // 删除最后一个元素
  public deleteLastItem(): void {
    this.dataArray.splice(-1, 1)
    this.notifyDataDelete(this.dataArray.length)
  }

  // 在指定索引位置删除一个元素
  public deleteItem(index: number): void {
    this.dataArray.splice(index, 1)
    this.notifyDataDelete(index)
  }

  // 重新加载数据
  public reload(): void {
    this.dataArray.splice(1, 1)
    this.dataArray.splice(3, 2)
    this.notifyDataReload()
  }
}
// Index.ets
import { WaterFlowDataSource } from './WaterFlowDataSource'

@Entry
@Component
struct WaterFlowDemo {
  @State minSize: number = 80
  @State maxSize: number = 180
  @State fontSize: number = 24
  @State colors: number[] = [0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F]
  scroller: Scroller = new Scroller()
  dataSource: WaterFlowDataSource = new WaterFlowDataSource()
  private itemWidthArray: number[] = []
  private itemHeightArray: number[] = []

  // 计算FlowItem宽/高
  getSize() {
    let ret = Math.floor(Math.random() * this.maxSize)
    return (ret > this.minSize ? ret : this.minSize)
  }

  // 设置FlowItem的宽/高数组
  setItemSizeArray() {
    for (let i = 0; i < 100; i++) {
      this.itemWidthArray.push(this.getSize())
      this.itemHeightArray.push(this.getSize())
    }
  }

  aboutToAppear() {
    this.setItemSizeArray()
  }

  @Builder
  itemFoot() {
    Column() {
      Text(`Footer`)
        .fontSize(10)
        .backgroundColor(Color.Red)
        .width(50)
        .height(50)
        .align(Alignment.Center)
        .margin({ top: 2 })
    }
  }

  build() {
    Column({ space: 2 }) {
      WaterFlow() {
        LazyForEach(this.dataSource, (item: number) => {
          FlowItem() {
            Column() {
              Text("N" + item).fontSize(12).height('16')
              Image('res/waterFlowTest(' + item % 5 + ').jpg')
                .objectFit(ImageFit.Fill)
                .width('100%')
                .layoutWeight(1)
            }
          }
          .onAppear(() => {
            // 即将触底时提前增加数据
            if (item + 20 == this.dataSource.totalCount()) {
              for (let i = 0; i < 100; i++) {
                this.dataSource.addLastItem()
              }
            }
          })
          .width('100%')
          .height(this.itemHeightArray[item % 100])
          .backgroundColor(this.colors[item % 5])
        }, (item: string) => item)
      }
      .columnsTemplate("1fr 1fr")
      .columnsGap(10)
      .rowsGap(5)
      .backgroundColor(0xFAEEE0)
      .width('100%')
      .height('100%')
      .onReachStart(() => {
        console.info('waterFlow reach start')
      })
      .onScrollStart(() => {
        console.info('waterFlow scroll start')
      })
      .onScrollStop(() => {
        console.info('waterFlow scroll stop')
      })
      .onScrollFrameBegin((offset: number, state: ScrollState) => {
        console.info('waterFlow scrollFrameBegin offset: ' + offset + ' state: ' + state.toString())
        return { offsetRemain: offset }
      })
    }
  }
}

示例2

auto-fill的使用。

//index.ets
import { WaterFlowDataSource } from './WaterFlowDataSource'

@Entry
@Component
struct WaterFlowDemo {
  @State minSize: number = 80
  @State maxSize: number = 180
  @State colors: number[] = [0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F]
  dataSource: WaterFlowDataSource = new WaterFlowDataSource()
  private itemWidthArray: number[] = []
  private itemHeightArray: number[] = []

  // 计算FlowItem宽/高
  getSize() {
    let ret = Math.floor(Math.random() * this.maxSize)
    return (ret > this.minSize ? ret : this.minSize)
  }

  // 设置FlowItem宽/高数组
  setItemSizeArray() {
    for (let i = 0; i < 100; i++) {
      this.itemWidthArray.push(this.getSize())
      this.itemHeightArray.push(this.getSize())
    }
  }

  aboutToAppear() {
    this.setItemSizeArray()
  }

  build() {
    Column({ space: 2 }) {
      WaterFlow() {
        LazyForEach(this.dataSource, (item: number) => {
          FlowItem() {
            Column() {
              Text("N" + item).fontSize(12).height('16')
            }
          }
          .width('100%')
          .height(this.itemHeightArray[item % 100])
          .backgroundColor(this.colors[item % 5])
        }, (item: string) => item)
      }
      .columnsTemplate('repeat(auto-fill,80)')
      .columnsGap(10)
      .rowsGap(5)
      .padding({left:5})
      .backgroundColor(0xFAEEE0)
      .width('100%')
      .height('100%')
    }
  }
}

示例3

WaterFlowSections的使用。

// Index.ets
import { WaterFlowDataSource } from './WaterFlowDataSource'

@Reusable
@Component
struct ReusableFlowItem {
  @State item: number = 0

  // 从复用缓存中加入到组件树之前调用,可在此处更新组件的状态变量以展示正确的内容
  aboutToReuse(params: Record<string, number>) {
    this.item = params.item;
    console.info('Reuse item:' + this.item)
  }

  aboutToAppear() {
    console.info('new item:' + this.item)
  }

  build() {
    Text("N" + this.item).fontSize(16).height('100%')
  }
}

@Entry
@Component
struct WaterFlowDemo {
  minSize: number = 80
  maxSize: number = 180
  fontSize: number = 24
  colors: number[] = [0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F]
  scroller: Scroller = new Scroller()
  dataSource: WaterFlowDataSource = new WaterFlowDataSource()
  dataCount: number = this.dataSource.totalCount()
  private itemHeightArray: number[] = []
  @State sections: WaterFlowSections = new WaterFlowSections()
  sectionMargin: Margin = { top: 10, left: 5, bottom: 10, right: 5 }
  oneColumnSection: SectionOptions = {
    itemsCount: 4,
    crossCount: 1,
    columnsGap: '5vp',
    rowsGap: 10,
    margin: this.sectionMargin,
    onGetItemMainSizeByIndex: (index: number) => {
      return this.itemHeightArray[index % 100]
    }
  }
  twoColumnSection: SectionOptions = {
    itemsCount: 2,
    crossCount: 2,
    onGetItemMainSizeByIndex: (index: number) => {
      return 100
    }
  }
  lastSection: SectionOptions = {
    itemsCount: 20,
    crossCount: 2,
    onGetItemMainSizeByIndex: (index: number) => {
      return this.itemHeightArray[index % 100]
    }
  }

  // 计算FlowItem高度
  getSize() {
    let ret = Math.floor(Math.random() * this.maxSize)
    return (ret > this.minSize ? ret : this.minSize)
  }

  // 设置FlowItem的高度数组
  setItemSizeArray() {
    for (let i = 0; i < 100; i++) {
      this.itemHeightArray.push(this.getSize())
    }
  }

  aboutToAppear() {
    this.setItemSizeArray()
    // 初始化瀑布流分组信息
    let sectionOptions: SectionOptions[] = []
    let count = 0
    let oneOrTwo = 0
    while (count < this.dataCount) {
      if (this.dataCount - count < 20) {
        this.lastSection.itemsCount = this.dataCount - count
        sectionOptions.push(this.lastSection)
        break;
      }
      if (oneOrTwo++ % 2 == 0) {
        sectionOptions.push(this.oneColumnSection)
        count += this.oneColumnSection.itemsCount
      } else {
        sectionOptions.push(this.twoColumnSection)
        count += this.twoColumnSection.itemsCount
      }
    }
    this.sections.splice(0, 0, sectionOptions)
  }

  build() {
    Column({ space: 2 }) {
      Row() {
        Button('splice')
          .height('5%')
          .onClick(() => {
            // 将所有分组替换成一个新分组,注意保证LazyForEach中数据数量和新分组itemsCount保持一致
            let totalCount: number = this.dataSource.totalCount()
            let newSection: SectionOptions = {
              itemsCount: totalCount,
              crossCount: 2,
              onGetItemMainSizeByIndex: (index: number) => {
                return this.itemHeightArray[index % 100]
              }
            }
            let oldLength: number = this.sections.length()
            this.sections.splice(0, oldLength, [newSection])
          })
          .margin({ top: 10, left: 20 })
        Button('update')
          .height('5%')
          .onClick(() => {
            // 在第二个分组增加4个FlowItem,注意保证LazyForEach中数据数量和所有分组itemsCount的和保持一致
            let newSection: SectionOptions = {
              itemsCount: 6,
              crossCount: 3,
              columnsGap: 5,
              rowsGap: 10,
              margin: this.sectionMargin,
              onGetItemMainSizeByIndex: (index: number) => {
                return this.itemHeightArray[index % 100]
              }
            }
            this.dataSource.addItem(this.oneColumnSection.itemsCount)
            this.dataSource.addItem(this.oneColumnSection.itemsCount + 1)
            this.dataSource.addItem(this.oneColumnSection.itemsCount + 2)
            this.dataSource.addItem(this.oneColumnSection.itemsCount + 3)
            const result: boolean = this.sections.update(1, newSection)
            console.info('update:' + result)
          })
          .margin({ top: 10, left: 20 })
        Button('delete')
          .height('5%')
          .onClick(() => {
            // 先点击update再点击delete
            let newSection: SectionOptions = {
              itemsCount: 2,
              crossCount: 2,
              columnsGap: 5,
              rowsGap: 10,
              margin: this.sectionMargin,
              onGetItemMainSizeByIndex: (index: number) => {
                return this.itemHeightArray[index % 100]
              }
            }
            this.dataSource.deleteItem(this.oneColumnSection.itemsCount)
            this.dataSource.deleteItem(this.oneColumnSection.itemsCount)
            this.dataSource.deleteItem(this.oneColumnSection.itemsCount)
            this.dataSource.deleteItem(this.oneColumnSection.itemsCount)
            this.sections.update(1, newSection)
          })
          .margin({ top: 10, left: 20 })
        Button('values')
          .height('5%')
          .onClick(() => {
            const sections: Array<SectionOptions> = this.sections.values();
            for (const value of sections) {
              console.log(JSON.stringify(value));
            }
            console.info('count:' + this.sections.length())
          })
          .margin({ top: 10, left: 20 })
      }.margin({ bottom: 20 })

      WaterFlow({ scroller: this.scroller, sections: this.sections }) {
        LazyForEach(this.dataSource, (item: number) => {
          FlowItem() {
            ReusableFlowItem({ item: item })
          }
          .width('100%')
          // 以onGetItemMainSizeByIndex为准
          // .height(this.itemHeightArray[item % 100])
          .backgroundColor(this.colors[item % 5])
        }, (item: string) => item)
      }
      .columnsTemplate('1fr 1fr') // 瀑布流使用sections参数时该属性无效
      .columnsGap(10)
      .rowsGap(5)
      .backgroundColor(0xFAEEE0)
      .width('100%')
      .height('100%')
      .layoutWeight(1)
      .onScrollIndex((first: number, last: number) => {
        // 即将触底时提前增加数据
        if (last + 20 >= this.dataSource.totalCount()) {
          for (let i = 0; i < 100; i++) {
            this.dataSource.addLastItem()
          }
          // 更新数据源后同步更新sections,修改最后一个section的FlowItem数量
          this.lastSection.itemsCount += 100
          this.sections.update(-1, this.lastSection);
        }
      })
    }
  }
}

示例4

双指缩放改变列数。

// Index.ets
import { WaterFlowDataSource } from './WaterFlowDataSource'

@Reusable
@Component
struct ReusableFlowItem {
  @State item: number = 0

  // 从复用缓存中加入到组件树之前调用,可在此处更新组件的状态变量以展示正确的内容
  aboutToReuse(params: Record<string, number>) {
    this.item = params.item;
    console.info('Reuse item:' + this.item)
  }

  aboutToAppear() {
    console.info('item:' + this.item)
  }

  build() {
    Column() {
      Text("N" + this.item).fontSize(12).height('16')
      Image('res/waterFlow (' + this.item % 5 + ').JPG')
        .objectFit(ImageFit.Fill)
        .width('100%')
        .layoutWeight(1)
    }
  }
}

@Entry
@Component
struct WaterFlowDemo {
  minSize: number = 80
  maxSize: number = 180
  colors: number[] = [0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F]
  @State columns: number = 2
  dataSource: WaterFlowDataSource = new WaterFlowDataSource()
  private itemWidthArray: number[] = []
  private itemHeightArray: number[] = []

  // 计算FlowItem宽/高
  getSize() {
    let ret = Math.floor(Math.random() * this.maxSize)
    return (ret > this.minSize ? ret : this.minSize)
  }

  // 设置FlowItem的宽/高数组
  setItemSizeArray() {
    for (let i = 0; i < 100; i++) {
      this.itemWidthArray.push(this.getSize())
      this.itemHeightArray.push(this.getSize())
    }
  }

  aboutToAppear() {
    let lastCount = AppStorage.get<number>('columnsCount')
    if (typeof lastCount != 'undefined') {
      this.columns = lastCount
    }
    this.setItemSizeArray()
  }

  build() {
    Column({ space: 2 }) {
      Row() {
        Text('双指缩放改变列数')
          .height('5%')
          .margin({ top: 10, left: 20 })
      }

      WaterFlow() {
        LazyForEach(this.dataSource, (item: number) => {
          FlowItem() {
            ReusableFlowItem({ item: item })
          }
          .width('100%')
          .height(this.itemHeightArray[item % 100])
          .backgroundColor(this.colors[item % 5])
        }, (item: string) => item)
      }
      .columnsTemplate('1fr '.repeat(this.columns))
      .columnsGap(10)
      .rowsGap(5)
      .backgroundColor(0xFAEEE0)
      .width('100%')
      .height('100%')
      .layoutWeight(1)
      // 切换列数item位置重排动画
      .animation({
        duration: 300,
        curve: Curve.Smooth
      })
      .priorityGesture(
        PinchGesture()
          .onActionEnd((event: GestureEvent) => {
            console.info('end scale:' + event.scale)
            // 手指分开,减少列数以放大item,触发阈值可以自定义,示例为2
            if (event.scale > 2) {
              this.columns--
            } else if (event.scale < 0.6) {
              this.columns++
            }
            // 可以根据设备屏幕宽度设定最大和最小列数,此处以最小1列最大4列为例
            this.columns = Math.min(4, Math.max(1, this.columns));
            AppStorage.setOrCreate<number>('columnsCount', this.columns)
          })
      )
    }
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值