1# WaterFlow 2 3<!--Kit: ArkUI--> 4<!--Subsystem: ArkUI--> 5<!--Owner: @fangyuhao--> 6<!--Designer: @zcdqs--> 7<!--Tester: @liuzhenshuo--> 8<!--Adviser: @HelloCrease--> 9 10瀑布流容器,由“行”和“列”分割的单元格所组成,通过容器自身的排列规则,将不同大小的“项目”自上而下,如瀑布般紧密布局。 11 12 13> **说明:** 14> 15> 该组件从API version 9 开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。 16> 17> WaterFlow组件支持展示瀑布流布局,不支持编辑模式和子元素拖动功能。 18> 19> 组件内部已绑定手势实现跟手滚动等功能,需要增加自定义手势操作时请参考[手势拦截增强](ts-gesture-blocking-enhancement.md)进行处理。 20## 子组件 21 22 23仅支持[FlowItem](ts-container-flowitem.md)子组件和自定义组件。自定义组件在WaterFlow下使用时,建议使用FlowItem作为自定组件的顶层组件,不建议给自定义组件设置属性和事件方法。 24支持通过渲染控制类型([if/else](../../../ui/state-management/arkts-rendering-control-ifelse.md)、[ForEach](../../../ui/state-management/arkts-rendering-control-foreach.md)、[LazyForEach](../../../ui/state-management/arkts-rendering-control-lazyforeach.md)和[Repeat](../../../ui/state-management/arkts-new-rendering-control-repeat.md))动态生成子组件,更推荐使用LazyForEach或Repeat以优化性能。 25 26> **说明:** 27> 28> WaterFlow子组件的visibility属性设置为None时不显示,但该子组件周围的columnsGap、rowsGap、margin仍会生效。 29> 在涉及大量子组件的情况下,建议采用懒加载、缓存数据、组件复用、固定宽高以及布局优化等方法,以提升性能和减少内存占用。最佳实践请参考[优化瀑布流加载慢丢帧问题](https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-waterflow-performance-optimization)。 30> 31> 纵向布局时,WaterFlow会计算每一列中已放置子组件的累计高度,并将新子组件放入累计高度最小的那一列,以保持整体布局紧凑。 32> 33> 若多个列的高度相同,优先放入最左边的列。在RTL模式下,优先放入最右边的列。 34 35 36## 接口 37 38WaterFlow(options?: WaterFlowOptions) 39 40创建瀑布流容器。 41 42**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 43 44**系统能力:** SystemCapability.ArkUI.ArkUI.Full 45 46**参数:** 47 48| 参数名 | 类型 | 必填 | 说明 | 49| -------- | -------- | -------- | -------- | 50| options | [WaterFlowOptions](#waterflowoptions对象说明)| 否 | 瀑布流组件参数。 | 51 52 53## WaterFlowOptions对象说明 54 55瀑布流组件参数对象。 56 57**系统能力:** SystemCapability.ArkUI.ArkUI.Full 58 59| 名称 | 类型 | 只读 | 可选 | 说明 | 60| ---------- | ----------------------------------------------- | ------ | -- | -------------------------------------------- | 61| footer | [CustomBuilder](ts-types.md#custombuilder8) | 否 | 是 | 设置WaterFlow尾部组件。<br/>**说明:** <br/>使用方法参见[示例1](#示例1使用基本瀑布流)。<br/>**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 | 62| footerContent<sup>18+</sup> | [ComponentContent](../js-apis-arkui-ComponentContent.md) | 否 | 是 | 设置WaterFlow尾部组件。<br/>该参数的优先级高于参数footer,即同时设置footer和footerContent时,以footerContent设置的组件为准。<br/>**原子化服务API:** 从API version 18开始,该接口支持在原子化服务中使用。 | 63| scroller | [Scroller](ts-container-scroll.md#scroller) | 否 | 是 | 可滚动组件的控制器,与可滚动组件绑定。<br/>**说明:** <br/>不允许和其他滚动类组件,如:[ArcList](ts-container-arclist.md)、[List](ts-container-list.md)、[Grid](ts-container-grid.md)、[Scroll](ts-container-scroll.md)和[WaterFlow](ts-container-waterflow.md)绑定同一个滚动控制对象。<br/>**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 | 64| sections<sup>12+</sup> | [WaterFlowSections](#waterflowsections12) | 否 | 是 | 设置FlowItem分组,实现同一个瀑布流组件内部各分组使用不同列数混合布局。<br/>**说明:** <br/>1. 使用分组混合布局时会忽略columnsTemplate和rowsTemplate属性。<br/>2. 使用分组混合布局时不支持单独设置footer,可以使用最后一个分组作为尾部组件。<br/>**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 | 65| layoutMode<sup>12+</sup> |[WaterFlowLayoutMode](#waterflowlayoutmode12枚举说明) | 否 | 是 | 设置WaterFlow的布局模式,根据使用场景选择更切合的模式。<br/>**说明:** <br/>默认值:[ALWAYS_TOP_DOWN](#waterflowlayoutmode12枚举说明)。<br/>**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 66 67 68## WaterFlowSections<sup>12+</sup> 69 70瀑布流分组信息。 71 72> **说明:** 73> 74> 使用splice、push、update修改分组信息后需要保证所有分组子节点总数与瀑布流实际子节点总数一致,否则会出现瀑布流因为不能正常布局而无法滑动的问题。 75 76### constructor 77 78constructor() 79 80创建一个瀑布流分组。 81 82**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 83 84**系统能力:** SystemCapability.ArkUI.ArkUI.Full 85 86### splice<sup>12+</sup> 87 88splice(start: number, deleteCount?: number, sections?: Array\<SectionOptions\>): boolean 89 90移除或者替换已存在的分组和/或添加新分组。 91 92**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 93 94**系统能力:** SystemCapability.ArkUI.ArkUI.Full 95 96**参数:** 97 98| 参数名 | 类型 | 必填 | 说明 | 99| ---- | ----------------------------- | ---- | -------------------- | 100| start | number | 是 | 从0开始计算的索引,会转换为整数,表示要开始改变分组的位置。<br/>**说明:** <br/>1. 如果索引是负数,则从末尾开始计算,使用`start + WaterFlowSections.length()`。<br/>2. 如果 `start < -WaterFlowSections.length()`,则使用0。<br/>3. 如果 `start >= WaterFlowSections.length()`,则在最后添加新分组。 | 101| deleteCount | number | 否 | 表示要从start开始删除的分组数量。<br/>**说明:** <br/>1. 如果省略了deleteCount,或者其值大于或等于由start指定的位置到WaterFlowSections末尾的分组数量,那么从start到WaterFlowSections末尾的所有分组将被删除。<br/>2. 如果deleteCount是0或者负数,则不会删除任何分组。 | 102| sections | Array<[SectionOptions](#sectionoptions12对象说明)> | 否 | 表示要从start开始加入的分组。如果不指定,`splice()`将只从瀑布流中删除分组。 | 103 104**返回值:** 105 106| 类型 | 说明 | 107| ------------------------------------------------------------ | ------------------------------------------------------------ | 108| boolean | 分组修改成功返回true;修改失败(要加入的分组中有任意分组的itemsCount不是非负数)返回false。 | 109 110 111### push<sup>12+</sup> 112 113push(section: SectionOptions): boolean 114 115将指定分组添加到瀑布流末尾。 116 117**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 118 119**系统能力:** SystemCapability.ArkUI.ArkUI.Full 120 121**参数:** 122 123| 参数名 | 类型 | 必填 | 说明 | 124| ---- | ----------------------------- | ---- | -------------------- | 125| section | [SectionOptions](#sectionoptions12对象说明) | 是 | 添加到瀑布流末尾的分组。 | 126 127**返回值:** 128 129| 类型 | 说明 | 130| ------------------------------------------------------------ | ------------------------------------------------------------ | 131| boolean | 分组添加成功返回true,添加失败(新分组的itemsCount不是非负数)返回false。 | 132 133### update<sup>12+</sup> 134 135update(sectionIndex: number, section: SectionOptions): boolean 136 137修改指定索引分组的配置信息。 138 139**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 140 141**系统能力:** SystemCapability.ArkUI.ArkUI.Full 142 143**参数:** 144 145| 参数名 | 类型 | 必填 | 说明 | 146| ---- | ----------------------------- | ---- | -------------------- | 147| sectionIndex | number | 是 | 从0开始计算的索引,会转换为整数,表示要修改的分组的位置。<br/>**说明:** <br/>1. 如果索引是负数,则从末尾开始计算,使用`sectionIndex + WaterFlowSections.length()`。<br/>2. 如果`sectionIndex < -WaterFlowSections.length()`,则使用0。<br/>3. 如果`sectionIndex >= WaterFlowSections.length()`,则在最后添加新分组。 | 148| section | [SectionOptions](#sectionoptions12对象说明) | 是 | 新的分组信息。 | 149 150**返回值:** 151 152| 类型 | 说明 | 153| ------------------------------------------------------------ | ------------------------------------------------------------ | 154| boolean | 分组是否更新成功,新分组的itemsCount不是非负数时返回false。 | 155 156### values<sup>12+</sup> 157 158values(): Array\<SectionOptions\> 159 160获取瀑布流中所有分组配置信息。 161 162**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 163 164**系统能力:** SystemCapability.ArkUI.ArkUI.Full 165 166**返回值:** 167 168| 类型 | 说明 | 169| ------------------------------------------------------------ | ------------------------------------------------------------ | 170| Array<[SectionOptions](#sectionoptions12对象说明)> | 瀑布流中所有分组配置信息。 | 171 172### length<sup>12+</sup> 173 174length(): number 175 176获取瀑布流中分组数量。 177 178**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 179 180**系统能力:** SystemCapability.ArkUI.ArkUI.Full 181 182**返回值:** 183 184| 类型 | 说明 | 185| ------------------------------------------------------------ | ------------------------------------------------------------ | 186| number | 瀑布流中分组数量。 | 187 188## SectionOptions<sup>12+</sup>对象说明 189 190FlowItem分组配置信息。 191 192**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 193 194**系统能力:** SystemCapability.ArkUI.ArkUI.Full 195 196| 名称 | 类型 | 只读 | 可选 | 说明 | 197|------|-----|-----|----|-----| 198| itemsCount | number | 否 | 否 | 分组中FlowItem数量,必须是非负数。若splice、push、update方法收到的分组中有分组的itemsCount小于0,则不会执行该方法。 避免使用itemsCount为0的分组,这可能导致布局计算异常。| 199| crossCount | number | 否 | 是 | 纵向布局时为列数,横向布局时为行数,默认值:1。小于1的按默认值处理。 | 200| columnsGap | [Dimension](ts-types.md#dimension10) | 否 | 是 | 该分组的列间距,不设置时使用瀑布流的columnsGap,设置非法值时使用0vp。 | 201| rowsGap | [Dimension](ts-types.md#dimension10) | 否 | 是 | 该分组的行间距,不设置时使用瀑布流的rowsGap,设置非法值时使用0vp。 | 202| margin | [Margin](ts-types.md#margin) \| [Dimension](ts-types.md#dimension10) | 否 | 是 | 该分组的外边距参数为Length类型时,四个方向外边距同时生效。<br>默认值:0<br>单位:vp<br>margin设置百分比时,上下左右外边距均以瀑布流的width作为基础值。 | 203| onGetItemMainSizeByIndex | [GetItemMainSizeByIndex](#getitemmainsizebyindex12) | 否 | 是 | 瀑布流组件布局过程中获取指定index的FlowItem的主轴大小,纵向瀑布流时为高度,横向瀑布流时为宽度,单位vp。<br/>**说明:** <br/>1. 同时使用onGetItemMainSizeByIndex和FlowItem的宽高属性时,主轴大小以onGetItemMainSizeByIndex返回结果为准,onGetItemMainSizeByIndex会覆盖FlowItem的主轴长度。<br/>2. 使用onGetItemMainSizeByIndex可以提高瀑布流跳转到指定位置或index时的效率,避免混用设置onGetItemMainSizeByIndex和未设置的分组,会导致布局异常。<br/>3. onGetItemMainSizeByIndex返回负数时FlowItem高度为0。 | 204 205 206## GetItemMainSizeByIndex<sup>12+</sup> 207 208type GetItemMainSizeByIndex = (index: number) => number 209 210根据index获取指定Item的主轴大小。 211 212**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 213 214**系统能力:** SystemCapability.ArkUI.ArkUI.Full 215 216**参数:** 217 218| 参数名 | 类型 | 必填 | 说明 | 219| ---- | ----------------------------- | ---- | -------------------- | 220| index | number | 是 | FlowItem在WaterFlow中的索引。<br/>取值范围:[0, 子节点总数-1] | 221 222**返回值:** 223 224| 类型 | 说明 | 225| ------------------------------------------------------------ | ------------------------------------------------------------ | 226| number | 指定index的FlowItem的主轴大小,纵向瀑布流时为高度,横向瀑布流时为宽度,单位vp。 | 227 228## WaterFlowLayoutMode<sup>12+</sup>枚举说明 229 230瀑布流组件布局模式枚举。 231 232**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 233 234**系统能力:** SystemCapability.ArkUI.ArkUI.Full 235 236| 名称 | 值 | 说明 | 237| ------ | ------ | -------------------- | 238| ALWAYS_TOP_DOWN | 0 | 默认的从上到下的布局模式。视窗内的FlowItem依赖视窗上方所有FlowItem的布局信息。因此跳转或切换列数时,需要计算出上方所有的FlowItem的布局信息。 | 239| SLIDING_WINDOW | 1 | 移动窗口式的布局模式。只考虑视窗内的布局信息,对视窗上方的FlowItem没有依赖关系,因此向后跳转或切换列数时只需要布局视窗内的FlowItem。建议优先采用该模式,尤其在应用需要支持屏幕旋转或动态切换列数的场景下。 <br/>**说明:** <br/>1. 无动画跳转到较远的位置时,会以目标位置为基准,向前或向后布局FlowItem。这之后如果滑回跳转前的位置,内容的布局效果可能和之前不一致。 这个效果会导致跳转后回滑到顶部时,顶部节点可能不对齐。所以该布局模式下会在滑动到顶部后自动调整布局,保证顶部对齐。在有多个分组的情况下,会在滑动结束时调整在视窗内的分组。<br/> 2. [scroller](#waterflowoptions对象说明)的[currentOffset](ts-container-scroll.md#currentoffset)接口返回的总偏移量在触发跳转或数据更新后不准确,在回滑到顶部时会重新校准。 <br/> 3. 如果在同一帧内调用跳转(如无动画的[scrollToIndex](ts-container-scroll.md#scrolltoindex)、[scrollEdge](ts-container-scroll.md#scrolledge))和输入偏移量(如滑动手势或滚动动画),两者都会生效。 <br/> 4. 调用无动画的[scrollToIndex](ts-container-scroll.md#scrolltoindex)进行跳转,如果跳转到较远位置(超过视窗内的FlowItem数量的位置)时,移动窗口模式对总偏移量进行估算。 <br/> 5. 仅在API version 18及以上版本中支持滚动条[scrollBar](ts-container-scrollable-common.md#scrollbar11)显示。低于此版本时,设置滚动条将不显示。| 240 241| 对比维度 | ALWAYS_TOP_DOWN (默认) | SLIDING_WINDOW | 242|---------|------------------------|----------------| 243| 适用场景 | 固定列数、简单瀑布流 | 动态列数、大数据量、屏幕旋转 | 244| 布局策略 | 从顶部开始完整布局 | 滑动窗口式布局 | 245| 性能特点 | 依赖上方所有 FlowItem | 只考虑视窗内布局 | 246| 跳转效率 | 需要计算上方所有布局 | 快速跳转,无需完整计算 | 247| 列数切换 | 需要重新计算全部布局 | 只重新布局视窗内容 | 248| 屏幕旋转 | 支持,但性能较差 | 支持,性能好 | 249| 滚动条显示 | 始终支持 | API 18+ 支持 | 250| 布局一致性 | 始终保持一致 | 跳转后可能不一致 | 251 252## 属性 253 254除支持[通用属性](ts-component-general-attributes.md)和[滚动组件通用属性](ts-container-scrollable-common.md#属性)外,还支持以下属性: 255> **说明:** 256> 257> WaterFlow组件使用通用属性[clip<sup>12+</sup>](ts-universal-attributes-sharp-clipping.md#clip12)和通用属性[clip<sup>18+</sup>](ts-universal-attributes-sharp-clipping.md#clip18)时默认值都为true。 258> 259> WaterFlow组件内容裁剪模式[ContentClipMode<sup>14+</sup>枚举说明](ts-container-scrollable-common.md#contentclipmode14枚举说明)为ContentClipMode.CONTENT_ONLY,padding区域会被裁剪不显示。 260 261### columnsTemplate 262 263columnsTemplate(value: string) 264 265设置当前瀑布流组件布局列的数量,不设置时默认1列。 266 267例如,'1fr 1fr 2fr' 是将父组件分3列,将父组件允许的宽分为4等份,第1列占1份,第2列占1份,第3列占2份。 268 269可使用columnsTemplate('repeat(auto-fill,track-size)')根据给定的列宽track-size自动计算列数,其中repeat、auto-fill为关键字,track-size为可设置的宽度,支持的单位包括px、vp、%或有效数字,默认单位为vp,使用方法参见示例2。 270 271**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 272 273**系统能力:** SystemCapability.ArkUI.ArkUI.Full 274 275**参数:** 276 277| 参数名 | 类型 | 必填 | 说明 | 278| ------ | ------ | ---- | ---------------------------------------------- | 279| value | string | 是 | 当前瀑布流组件布局列的数量。<br/>默认值:'1fr' | 280 281### rowsTemplate 282 283rowsTemplate(value: string) 284 285设置当前瀑布流组件布局行的数量,不设置时默认1行。 286 287例如,'1fr 1fr 2fr'是将父组件分3行,将父组件允许的高分为4等份,第1行占1份,第2行占1份,第3行占2份。 288 289可使用rowsTemplate('repeat(auto-fill,track-size)')根据给定的行高track-size自动计算行数,其中repeat、auto-fill为关键字,track-size为可设置的高度,支持的单位包括px、vp、%或有效数字,默认单位为vp。 290 291**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 292 293**系统能力:** SystemCapability.ArkUI.ArkUI.Full 294 295**参数:** 296 297| 参数名 | 类型 | 必填 | 说明 | 298| ------ | ------ | ---- | ---------------------------------------------- | 299| value | string | 是 | 当前瀑布流组件布局行的数量。<br/>默认值:'1fr' | 300 301### itemConstraintSize 302 303itemConstraintSize(value: ConstraintSizeOptions) 304 305设置约束尺寸,子组件布局时,进行尺寸范围限制。 306 307**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 308 309**系统能力:** SystemCapability.ArkUI.ArkUI.Full 310 311**参数:** 312 313| 参数名 | 类型 | 必填 | 说明 | 314| ------ | ---------------------------------------------------------- | ---- | ---------- | 315| value | [ConstraintSizeOptions](ts-types.md#constraintsizeoptions) | 是 | 约束尺寸。设置小于0的值,参数不生效。 <br/>**说明:**<br/>1.同时设置itemConstraintSize和FlowItem的[constraintSize](ts-universal-attributes-size.md#constraintsize)属性时,minWidth/minHeight会取其中的最大值,maxWidth/maxHeight会取其中的最小值,调整后的值作为FlowItem的constraintSize处理。<br/>2.只设置itemConstraintSize时,相当于对WaterFlow所有子组件设置了相同的constraintSize。<br/>3.itemConstraintSize通过以上两种方式转换成FlowItem的constraintSize后的生效规则与通用属性[constraintSize](./ts-universal-attributes-size.md#constraintsize)相同。| 316 317### columnsGap 318 319columnsGap(value: Length) 320 321设置列与列的间距。 322 323**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 324 325**系统能力:** SystemCapability.ArkUI.ArkUI.Full 326 327**参数:** 328 329| 参数名 | 类型 | 必填 | 说明 | 330| ------ | ---------------------------- | ---- | ----------------------------- | 331| value | [Length](ts-types.md#length) | 是 | 列与列的间距。 <br/>默认值:0<br/>取值范围:[0, +∞) | 332 333### rowsGap 334 335rowsGap(value: Length) 336 337设置行与行的间距。 338 339**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 340 341**系统能力:** SystemCapability.ArkUI.ArkUI.Full 342 343**参数:** 344 345| 参数名 | 类型 | 必填 | 说明 | 346| ------ | ---------------------------- | ---- | ----------------------------- | 347| value | [Length](ts-types.md#length) | 是 | 行与行的间距。 <br/>默认值:0<br/>取值范围:[0, +∞) | 348 349### layoutDirection 350 351layoutDirection(value: FlexDirection) 352 353设置布局的主轴方向。 354 355**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 356 357**系统能力:** SystemCapability.ArkUI.ArkUI.Full 358 359**参数:** 360 361| 参数名 | 类型 | 必填 | 说明 | 362| ------ | --------------------------------------------------- | ---- | ------------------------------------------------- | 363| value | [FlexDirection](ts-appendix-enums.md#flexdirection) | 是 | 布局的主轴方向。<br/>默认值:FlexDirection.Column | 364 365layoutDirection优先级高于rowsTemplate和columnsTemplate。根据layoutDirection设置情况,分为以下三种设置模式: 366 367- layoutDirection设置纵向布局(FlexDirection.Column 或 FlexDirection.ColumnReverse) 368 369 此时columnsTemplate有效(如果未设置,取默认值)。例如columnsTemplate设置为"1fr 1fr"、rowsTemplate设置为"1fr 1fr 1fr"时,瀑布流组件纵向布局,辅轴均分成横向2列。 370 371- layoutDirection设置横向布局(FlexDirection.Row 或 FlexDirection.RowReverse) 372 373 此时rowsTemplate有效(如果未设置,取默认值)。例如columnsTemplate设置为"1fr 1fr"、rowsTemplate设置为"1fr 1fr 1fr"时,瀑布流组件横向布局,辅轴均分成纵向3列。 374 375- layoutDirection未设置布局方向 376 377 布局方向为layoutDirection的默认值:FlexDirection.Column,此时columnsTemplate有效。例如columnsTemplate设置为"1fr 1fr"、rowsTemplate设置为"1fr 1fr 1fr"时,瀑布流组件纵向布局,辅轴均分成横向2列。 378 379### enableScrollInteraction<sup>10+</sup> 380 381enableScrollInteraction(value: boolean) 382 383设置是否支持滚动手势。 384 385**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 386 387**系统能力:** SystemCapability.ArkUI.ArkUI.Full 388 389**参数:** 390 391| 参数名 | 类型 | 必填 | 说明 | 392| ------ | ------- | ---- | ----------------------------------- | 393| value | boolean | 是 | 是否支持滚动手势。设置为true时可以通过手指或者鼠标滚动,设置为false时无法通过手指或者鼠标滚动,但不影响控制器[Scroller](ts-container-scroll.md#scroller)的滚动接口。<br/>默认值:true | 394 395> **说明:** 396> 397> 组件无法通过鼠标按下拖动操作进行滚动。 398 399### nestedScroll<sup>10+</sup> 400 401nestedScroll(value: NestedScrollOptions) 402 403设置前后两个方向的嵌套滚动模式,实现与父组件的滚动联动。 404 405**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 406 407**系统能力:** SystemCapability.ArkUI.ArkUI.Full 408 409**参数:** 410 411| 参数名 | 类型 | 必填 | 说明 | 412| ------ | ------------------------------------------------------------ | ---- | -------------- | 413| value | [NestedScrollOptions](ts-container-scrollable-common.md#nestedscrolloptions10对象说明) | 是 | 嵌套滚动选项。 | 414 415### friction<sup>10+</sup> 416 417friction(value: number | Resource) 418 419设置摩擦系数,手动划动滚动区域时生效,仅影响惯性滚动过程,对惯性滚动过程中的链式效果有间接影响。 420 421**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 422 423**系统能力:** SystemCapability.ArkUI.ArkUI.Full 424 425**参数:** 426 427| 参数名 | 类型 | 必填 | 说明 | 428| ------ | ---------------------------------------------------- | ---- | --------------------------------------------------------- | 429| value | number \| [Resource](ts-types.md#resource) | 是 | 摩擦系数。<br/>默认值:非可穿戴设备为0.6,可穿戴设备为0.9。<br/>从API version 11开始,非可穿戴设备默认值为0.7。<br/>从API version 12开始,非可穿戴设备默认值为0.75。<br/>取值范围:(0, +∞),设置为小于等于0的值时,按默认值处理。 | 430 431### cachedCount<sup>11+</sup> 432 433cachedCount(value: number) 434 435设置预加载的FlowItem数量。 436 437只在[LazyForEach](../../../ui/state-management/arkts-rendering-control-lazyforeach.md)和开启了virtualScroll开关的[Repeat](../../../ui/state-management/arkts-new-rendering-control-repeat.md)中生效,超出显示及缓存范围的FlowItem会被释放。 438 439**原子化服务API:** 从API version 12开始,该接口支持在原子化服务中使用。 440 441**系统能力:** SystemCapability.ArkUI.ArkUI.Full 442 443**参数:** 444 445| 参数名 | 类型 | 必填 | 说明 | 446| ------ | ------ | ---- | ------------------------------------------------------------ | 447| value | number | 是 | 预加载的FlowItem的数量。 <br/>默认值:根据屏幕内显示的节点个数设置,最大值为16。<br/>取值范围:[0, +∞),设置为小于0的值时,按1处理。 | 448 449### cachedCount<sup>14+</sup> 450 451cachedCount(count: number, show: boolean) 452 453设置预加载的FlowItem数量,并配置是否显示预加载节点。 454 455配合[裁剪](ts-universal-attributes-sharp-clipping.md#clip12)或[内容裁剪](ts-container-scrollable-common.md#clipcontent14)属性可以显示出预加载节点。 456 457只在[LazyForEach](../../../ui/state-management/arkts-rendering-control-lazyforeach.md)和开启了virtualScroll开关的[Repeat](../../../ui/state-management/arkts-new-rendering-control-repeat.md)中生效,超出显示及缓存范围的FlowItem会被释放。 458 459**原子化服务API:** 从API version 14开始,该接口支持在原子化服务中使用。 460 461**系统能力:** SystemCapability.ArkUI.ArkUI.Full 462 463**参数:** 464 465| 参数名 | 类型 | 必填 | 说明 | 466| ------ | ------ | ---- | ---------------------------------------- | 467| count | number | 是 | 预加载的FlowItem的数量。 <br/>默认值:根据屏幕内显示的节点个数设置,最大值为16。<br/>取值范围:[0, +∞),设置为小于0的值时,按1处理。 | 468| show | boolean | 是 | 被预加载的FlowItem是否需要显示。设置为true时显示预加载的FlowItem,设置为false时不显示预加载的FlowItem。 <br/> 默认值:false | 469 470### syncLoad<sup>20+</sup> 471 472syncLoad(enable: boolean) 473 474设置是否同步加载WaterFlow区域内所有子组件。 475 476**原子化服务API:** 从API version 20开始,该接口支持在原子化服务中使用。 477 478**系统能力:** SystemCapability.ArkUI.ArkUI.Full 479 480**参数:** 481 482| 参数名 | 类型 | 必填 | 说明 | 483| ------ | ------------------------------------------------------------ | ---- | ------------------------------------------------------------ | 484| enable | boolean | 是 | 是否同步加载WaterFlow区域内所有子组件。<br/>true表示同步加载,false表示异步加载。默认值:true。<br/>**说明:** <br/>设置为false时,在首次显示、不带动画scrollToIndex跳转场景,若当帧布局耗时超过50ms,会将WaterFlow区域内尚未布局的子组件延后到下一帧进行布局。 | 485 486## 事件 487 488除支持[通用事件](ts-component-general-events.md)和[滚动组件通用事件](ts-container-scrollable-common.md#事件)外,还支持以下事件: 489 490### onReachStart 491 492onReachStart(event: () => void) 493 494瀑布流内容到达起始位置时触发。 495 496**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 497 498**系统能力:** SystemCapability.ArkUI.ArkUI.Full 499 500**参数:** 501 502| 参数名 | 类型 | 必填 | 说明 | 503| ------ | ------ | ------ | ------| 504| event | () => void | 是 | 瀑布流内容到达起始位置时触发的回调。 | 505 506### onReachEnd 507 508onReachEnd(event: () => void) 509 510瀑布流内容到达末尾位置时触发。 511 512**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 513 514**系统能力:** SystemCapability.ArkUI.ArkUI.Full 515 516**参数:** 517 518| 参数名 | 类型 | 必填 | 说明 | 519| ------ | ------ | ------ | ------| 520| event | () => void | 是 | 瀑布流内容到达末尾位置时触发的回调。 | 521 522### onScrollFrameBegin<sup>10+</sup> 523 524onScrollFrameBegin(event: OnScrollFrameBeginCallback) 525 526该接口回调时,事件参数传入即将发生的滑动量,事件处理函数中可根据应用场景计算实际需要的滑动量并作为事件处理函数的返回值返回,瀑布流将按照返回值的实际滑动量进行滑动。 527 528满足以下任一条件时触发该事件: 529 5301. 用户交互(如手指滑动、键鼠操作等)触发滚动。 5312. WaterFlow惯性滚动。 5323. 调用[fling](ts-container-scroll.md#fling12)接口触发滚动。 533 534不触发该事件的条件: 535 5361. 调用除[fling](ts-container-scroll.md#fling12)接口外的其他滚动控制接口。 5372. 越界回弹。 5383. 拖动滚动条。 539 540**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 541 542**系统能力:** SystemCapability.ArkUI.ArkUI.Full 543 544**参数:** 545 546| 参数名 | 类型 | 必填 | 说明 | 547| ------ | ------------------------------------------------------- | ---- | -------------------------- | 548| event | [OnScrollFrameBeginCallback](ts-container-scroll.md#onscrollframebegincallback18) | 是 | 每帧滚动开始回调函数。 | 549 550### onScrollIndex<sup>11+</sup> 551 552onScrollIndex(event: (first: number, last: number) => void) 553 554当前瀑布流显示的起始位置/终止位置的子组件发生变化时触发。瀑布流初始化时会触发一次。 555 556瀑布流显示区域上第一个子组件/最后一个组件的索引值有变化就会触发。 557 558**原子化服务API:** 从API version 11开始,该接口支持在原子化服务中使用。 559 560**系统能力:** SystemCapability.ArkUI.ArkUI.Full 561 562**参数:** 563 564| 参数名 | 类型 | 必填 | 说明 | 565| ------ | ------ | ---- | ------------------------------------- | 566| first | number | 是 | 当前显示的瀑布流起始位置的索引值。<br/>取值范围:[0, 子节点总数-1] | 567| last | number | 是 | 当前显示的瀑布流终止位置的索引值。<br/>取值范围:[0, 子节点总数-1] | 568 569通过`last`参数可以判断是否“继续加载数据”,参考[示例3使用分组](#示例3使用分组)中"即将触底时提前增加数据"的处理逻辑。 570 571当WaterFlow列表为空时,使用不同的WaterFlowOptions参数会导致onScrollIndex事件的返回值有所不同。具体差异请参见下表: 572 573| layoutMode | sections | first | last | 574| --- | --- | --- | --- | 575| ALWAYS_TOP_DOWN | 无 | 0 | 0 | 576| ALWAYS_TOP_DOWN | 有 | 0 | -1 | 577| SLIDING_WINDOW | 可选 | 1000000 | -1 | 578 579 580## UIWaterFlowEvent<sup>19+</sup> 581frameNode中[getEvent('WaterFlow')](../js-apis-arkui-frameNode.md#geteventwaterflow19)方法的返回值,可用于给WaterFlow节点设置滚动事件。 582 583UIWaterFlowEvent继承于[UIScrollableCommonEvent](./ts-container-scrollable-common.md#uiscrollablecommonevent19)。 584 585### setOnWillScroll<sup>19+</sup> 586 587setOnWillScroll(callback: OnWillScrollCallback | undefined): void 588 589设置[onWillScroll](./ts-container-scrollable-common.md#onwillscroll12)事件的回调。 590 591方法入参为undefined时,会重置事件回调。 592 593**原子化服务API:** 从API version 19开始,该接口支持在原子化服务中使用。 594 595**系统能力:** SystemCapability.ArkUI.ArkUI.Full 596 597**参数:** 598 599| 参数名 | 类型 | 必填 | 说明 | 600| ------ | ------ | ---- | -------------------------- | 601| callback | [OnWillScrollCallback](./ts-container-scrollable-common.md#onwillscrollcallback12) \| undefined | 是 | onWillScroll事件的回调函数。 | 602 603### setOnDidScroll<sup>19+</sup> 604 605setOnDidScroll(callback: OnScrollCallback | undefined): void 606 607设置[onDidScroll](./ts-container-scrollable-common.md#ondidscroll12)事件的回调。 608 609方法入参为undefined时,会重置事件回调。 610 611**原子化服务API:** 从API version 19开始,该接口支持在原子化服务中使用。 612 613**系统能力:** SystemCapability.ArkUI.ArkUI.Full 614 615**参数:** 616 617| 参数名 | 类型 | 必填 | 说明 | 618| ------ | ------ | ---- | -------------------------- | 619| callback | [OnScrollCallback](./ts-container-scrollable-common.md#onscrollcallback12) \| undefined | 是 | onDidScroll事件的回调函数。 | 620 621### setOnScrollIndex<sup>19+</sup> 622 623setOnScrollIndex(callback: OnWaterFlowScrollIndexCallback | undefined): void 624 625设置[onScrollIndex](#onscrollindex11)事件的回调。 626 627方法入参为undefined时,会重置事件回调。 628 629**原子化服务API:** 从API version 19开始,该接口支持在原子化服务中使用。 630 631**系统能力:** SystemCapability.ArkUI.ArkUI.Full 632 633**参数:** 634 635| 参数名 | 类型 | 必填 | 说明 | 636| ------ | ------ | ---- | -------------------------- | 637| callback | [OnWaterFlowScrollIndexCallback](#onwaterflowscrollindexcallback19) \| undefined | 是 | onScrollIndex事件的回调函数。 | 638 639## OnWaterFlowScrollIndexCallback<sup>19+</sup> 640type OnWaterFlowScrollIndexCallback = (first: number, last: number) => void 641 642WaterFlow组件可见区域item变化事件的回调类型。 643 644**原子化服务API:** 从API version 19开始,该接口支持在原子化服务中使用。 645 646**系统能力:** SystemCapability.ArkUI.ArkUI.Full 647 648**参数:** 649 650| 参数名 | 类型 | 必填 | 说明 | 651| ------ | ------ | ---- | ------------------------------------- | 652| first | number | 是 | 当前显示的瀑布流起始位置的索引值。 | 653| last | number | 是 | 当前显示的瀑布流终止位置的索引值。 | 654 655## 示例 656 657### 示例1(使用基本瀑布流) 658该示例展示了WaterFlow组件数据加载处理、属性设置和事件回调等基本使用场景。 659 660WaterFlowDataSource实现了LazyForEach数据源接口[IDataSource](ts-rendering-control-lazyforeach.md#idatasource),用于通过LazyForEach给WaterFlow提供子组件。 661 662<!--code_no_check--> 663```ts 664// WaterFlowDataSource.ets 665 666// 实现IDataSource接口的对象,用于瀑布流组件加载数据 667export class WaterFlowDataSource implements IDataSource { 668 private dataArray: number[] = []; 669 private listeners: DataChangeListener[] = []; 670 671 constructor() { 672 for (let i = 0; i < 100; i++) { 673 this.dataArray.push(i); 674 } 675 } 676 677 // 获取索引对应的数据 678 public getData(index: number): number { 679 return this.dataArray[index]; 680 } 681 682 // 通知控制器数据重新加载 683 notifyDataReload(): void { 684 this.listeners.forEach(listener => { 685 listener.onDataReloaded(); 686 }) 687 } 688 689 // 通知控制器数据增加 690 notifyDataAdd(index: number): void { 691 this.listeners.forEach(listener => { 692 listener.onDataAdd(index); 693 }) 694 } 695 696 // 通知控制器数据变化 697 notifyDataChange(index: number): void { 698 this.listeners.forEach(listener => { 699 listener.onDataChange(index); 700 }) 701 } 702 703 // 通知控制器数据删除 704 notifyDataDelete(index: number): void { 705 this.listeners.forEach(listener => { 706 listener.onDataDelete(index); 707 }) 708 } 709 710 // 通知控制器数据位置变化 711 notifyDataMove(from: number, to: number): void { 712 this.listeners.forEach(listener => { 713 listener.onDataMove(from, to); 714 }) 715 } 716 717 //通知控制器数据批量修改 718 notifyDatasetChange(operations: DataOperation[]): void { 719 this.listeners.forEach(listener => { 720 listener.onDatasetChange(operations); 721 }) 722 } 723 724 // 获取数据总数 725 public totalCount(): number { 726 return this.dataArray.length; 727 } 728 729 // 注册改变数据的控制器 730 registerDataChangeListener(listener: DataChangeListener): void { 731 if (this.listeners.indexOf(listener) < 0) { 732 this.listeners.push(listener); 733 } 734 } 735 736 // 注销改变数据的控制器 737 unregisterDataChangeListener(listener: DataChangeListener): void { 738 const pos = this.listeners.indexOf(listener); 739 if (pos >= 0) { 740 this.listeners.splice(pos, 1); 741 } 742 } 743 744 // 增加数据 745 public add1stItem(): void { 746 this.dataArray.splice(0, 0, this.dataArray.length); 747 this.notifyDataAdd(0); 748 } 749 750 // 在数据尾部增加一个元素 751 public addLastItem(): void { 752 this.dataArray.splice(this.dataArray.length, 0, this.dataArray.length); 753 this.notifyDataAdd(this.dataArray.length - 1); 754 } 755 756 // 在指定索引位置增加一个元素 757 public addItem(index: number): void { 758 this.dataArray.splice(index, 0, this.dataArray.length); 759 this.notifyDataAdd(index); 760 } 761 762 // 删除第一个元素 763 public delete1stItem(): void { 764 this.dataArray.splice(0, 1); 765 this.notifyDataDelete(0); 766 } 767 768 // 删除第二个元素 769 public delete2ndItem(): void { 770 this.dataArray.splice(1, 1); 771 this.notifyDataDelete(1); 772 } 773 774 // 删除最后一个元素 775 public deleteLastItem(): void { 776 this.dataArray.splice(-1, 1); 777 this.notifyDataDelete(this.dataArray.length); 778 } 779 780 // 在指定索引位置删除一个元素 781 public deleteItem(index: number): void { 782 this.dataArray.splice(index, 1); 783 this.notifyDataDelete(index); 784 } 785 786 // 重新加载数据 787 public reload(): void { 788 this.dataArray.splice(1, 1); 789 this.dataArray.splice(3, 2); 790 this.notifyDataReload(); 791 } 792 793 // 在数据尾部增加count个元素 794 public addNewItems(count: number): void { 795 let len = this.dataArray.length; 796 for (let i = 0; i < count; i++) { 797 this.dataArray.push(this.dataArray[len - 1] + i + 1); 798 this.notifyDataAdd(this.dataArray.length - 1); 799 } 800 } 801 802 // 刷新所有元素 803 public refreshItems(): void { 804 let newDataArray: number[] = []; 805 for (let i = 0; i < 100; i++) { 806 newDataArray.push(this.dataArray[0] + i + 1000); 807 } 808 this.dataArray = newDataArray; 809 this.notifyDataReload(); 810 } 811} 812``` 813 814<!--code_no_check--> 815```ts 816// Index.ets 817import { WaterFlowDataSource } from './WaterFlowDataSource'; 818 819enum FooterState { 820 Loading = 0, 821 End = 1 822} 823 824@Entry 825@Component 826struct WaterFlowDemo { 827 @State minSize: number = 80; 828 @State maxSize: number = 180; 829 @State colors: number[] = [0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F]; 830 @State footerState: FooterState = FooterState.Loading; 831 scroller: Scroller = new Scroller(); 832 dataSource: WaterFlowDataSource = new WaterFlowDataSource(); 833 private itemWidthArray: number[] = []; 834 private itemHeightArray: number[] = []; 835 836 // 计算FlowItem宽/高 837 getSize() { 838 let ret = Math.floor(Math.random() * this.maxSize); 839 return (ret > this.minSize ? ret : this.minSize); 840 } 841 842 // 设置FlowItem的宽/高数组 843 setItemSizeArray() { 844 for (let i = 0; i < 100; i++) { 845 this.itemWidthArray.push(this.getSize()); 846 this.itemHeightArray.push(this.getSize()); 847 } 848 } 849 850 // 组件生命周期:在组件即将出现时初始化尺寸数组 851 aboutToAppear() { 852 this.setItemSizeArray(); 853 } 854 855 @Builder 856 itemFoot() { 857 // 注意:不要直接用IfElse节点作为footer的根节点 858 // 必须在外面使用(Column/Row/Stack等)容器包裹,确保布局正确 859 Column() { 860 if (this.footerState == FooterState.Loading) { 861 Text(`加载中...`) 862 .fontSize(10) 863 .backgroundColor(Color.Red) 864 .width(50) 865 .height(50) 866 .align(Alignment.Center) 867 .margin({ top: 2 }) 868 } else if (this.footerState == FooterState.End) { 869 Text(`到底啦...`) 870 .fontSize(10) 871 .backgroundColor(Color.Red) 872 .width(50) 873 .height(50) 874 .align(Alignment.Center) 875 .margin({ top: 2 }) 876 } else { 877 Text(`Footer`) 878 .fontSize(10) 879 .backgroundColor(Color.Red) 880 .width(50) 881 .height(50) 882 .align(Alignment.Center) 883 .margin({ top: 2 }) 884 } 885 } 886 } 887 888 build() { 889 Column({ space: 2 }) { 890 WaterFlow({ footer: this.itemFoot() }) { 891 LazyForEach(this.dataSource, (item: number) => { 892 FlowItem() { 893 Column() { 894 Text("N" + item).fontSize(12).height('16') 895 // 注意:需要确保对应的jpg文件存在才会正常显示 896 Image('res/waterFlowTest(' + item % 5 + ').jpg') 897 .objectFit(ImageFit.Fill) 898 .width('100%') 899 .layoutWeight(1) 900 } 901 } 902 .width('100%') 903 .height(this.itemHeightArray[item % 100]) 904 .backgroundColor(this.colors[item % this.colors.length]) 905 }, (item: string) => item) 906 } 907 .columnsTemplate("1fr 1fr") // 设置2列等宽布局 908 .columnsGap(10) 909 .rowsGap(5) 910 .backgroundColor(0xFAEEE0) 911 .width('100%') 912 .height('100%') 913 // 触底加载数据:滚动到底部时触发分页加载 914 .onReachEnd(() => { 915 console.info("onReachEnd") 916 917 // 模拟分页加载:当数据超过200条时停止加载 918 if (this.dataSource.totalCount() > 200) { 919 this.footerState = FooterState.End; 920 return; 921 } 922 setTimeout(() => { 923 for (let i = 0; i < 100; i++) { 924 this.dataSource.addLastItem(); 925 } 926 }, 1000) 927 }) 928 .onReachStart(() => { 929 // 滚动到顶部时触发 930 console.info('waterFlow reach start'); 931 }) 932 .onScrollStart(() => { 933 // 开始滚动时触发 934 console.info('waterFlow scroll start'); 935 }) 936 .onScrollStop(() => { 937 // 停止滚动时触发 938 console.info('waterFlow scroll stop'); 939 }) 940 .onScrollFrameBegin((offset: number, state: ScrollState) => { 941 // 滚动帧开始时触发:可以控制滚动行为 942 // offset:滚动偏移量,state:滚动状态 943 console.info('waterFlow scrollFrameBegin offset: ' + offset + ' state: ' + state.toString()); 944 return { offsetRemain: offset }; // 返回开发者期望的实际滚动偏移量 945 }) 946 } 947 } 948} 949``` 950 951 952 953### 示例2(自动计算列数) 954该示例通过auto-fill实现了自动计算列数的效果。 955 956WaterFlowDataSource说明及完整代码参考[示例1使用基本瀑布流](#示例1使用基本瀑布流)。 957 958<!--code_no_check--> 959```ts 960// Index.ets 961import { WaterFlowDataSource } from './WaterFlowDataSource'; 962 963@Entry 964@Component 965struct WaterFlowDemo { 966 @State minSize: number = 80; 967 @State maxSize: number = 180; 968 @State colors: number[] = [0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F]; 969 dataSource: WaterFlowDataSource = new WaterFlowDataSource(); 970 private itemWidthArray: number[] = []; 971 private itemHeightArray: number[] = []; 972 973 // 计算FlowItem宽/高 974 getSize() { 975 let ret = Math.floor(Math.random() * this.maxSize); 976 return (ret > this.minSize ? ret : this.minSize); 977 } 978 979 // 设置FlowItem宽/高数组 980 setItemSizeArray() { 981 for (let i = 0; i < 100; i++) { 982 this.itemWidthArray.push(this.getSize()); 983 this.itemHeightArray.push(this.getSize()); 984 } 985 } 986 987 // 组件生命周期:在组件即将出现时初始化尺寸数组 988 aboutToAppear() { 989 this.setItemSizeArray(); 990 } 991 992 build() { 993 Column({ space: 2 }) { 994 WaterFlow() { 995 LazyForEach(this.dataSource, (item: number) => { 996 FlowItem() { 997 Column() { 998 Text("N" + item).fontSize(12).height('16') 999 // 存在对应的jpg文件才会显示图片 1000 Image('res/waterFlowTest(' + item % 5 + ').jpg') 1001 } 1002 } 1003 .width('100%') 1004 .height(this.itemHeightArray[item % 100]) 1005 .backgroundColor(this.colors[item % this.colors.length]) 1006 }, (item: string) => item) 1007 } 1008 // auto-fill自动计算列数 1009 // 'repeat(auto-fill,80)' 表示:根据容器宽度自动计算能放下多少个80px宽的列 1010 // 例如:容器宽度400px,则自动计算为5列(400÷80=5) 1011 .columnsTemplate('repeat(auto-fill,80)') 1012 .columnsGap(10) 1013 .rowsGap(5) 1014 .padding({left:5}) 1015 .backgroundColor(0xFAEEE0) 1016 .width('100%') 1017 .height('100%') 1018 } 1019 } 1020} 1021``` 1022 1023 1024 1025 1026### 示例3(使用分组) 1027该示例展示了分组的初始化以及splice、push、update、values、length等接口的不同效果。 1028如果配合状态管理V2使用,详情见:[WaterFlow与makeObserved](../../../ui/state-management/arkts-v1-v2-migration-application-and-others.md#滑动组件)。 1029 1030WaterFlowDataSource说明及完整代码参考[示例1使用基本瀑布流](#示例1使用基本瀑布流)。 1031 1032<!--code_no_check--> 1033```ts 1034// Index.ets 1035import { WaterFlowDataSource } from './WaterFlowDataSource'; 1036 1037// 可复用组件:优化性能,减少组件创建销毁开销 1038@Reusable 1039@Component 1040struct ReusableFlowItem { 1041 @State item: number = 0; 1042 1043 // 组件复用生命周期:从复用缓存中取出时调用 1044 // 用于更新组件状态,显示新的内容 1045 aboutToReuse(params: Record<string, number>) { 1046 this.item = params.item; 1047 console.info('Reuse item:' + this.item); 1048 } 1049 1050 // 组件生命周期:初始化尺寸数组和分组配置 1051 aboutToAppear() { 1052 console.info('new item:' + this.item); 1053 } 1054 1055 build() { 1056 Column() { 1057 // 注意:需要确保对应的jpg文件存在才会正常显示 1058 Image('res/waterFlowTest(' + this.item % 5 + ').jpg') 1059 .overlay('N' + this.item, { align: Alignment.Top }) 1060 .objectFit(ImageFit.Fill) 1061 .width('100%') 1062 .layoutWeight(1) 1063 } 1064 } 1065} 1066 1067@Entry 1068@Component 1069struct WaterFlowDemo { 1070 minSize: number = 80; 1071 maxSize: number = 180; 1072 colors: number[] = [0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F]; 1073 scroller: Scroller = new Scroller(); 1074 dataSource: WaterFlowDataSource = new WaterFlowDataSource(); 1075 dataCount: number = this.dataSource.totalCount(); 1076 private itemWidthArray: number[] = []; 1077 private itemHeightArray: number[] = []; 1078 // 分组管理:WaterFlow的核心特性,支持不同区域使用不同列数 1079 @State sections: WaterFlowSections = new WaterFlowSections(); 1080 // 分组边距配置:统一的外边距设置 1081 sectionMargin: Margin = { top: 10, left: 5, bottom: 10, right: 5 }; 1082 1083 oneColumnSection: SectionOptions = { 1084 itemsCount: 4, // 该分组包含4个FlowItem 1085 crossCount: 1, // 使用1列布局 1086 columnsGap: '5vp', 1087 rowsGap: 10, 1088 margin: this.sectionMargin, 1089 // 回调函数:动态设置每个item的高度 1090 onGetItemMainSizeByIndex: (index: number) => { 1091 return this.itemHeightArray[index % 100]; 1092 } 1093 }; 1094 1095 // 第二种分组:双列布局,适合展示列表内容 1096 twoColumnSection: SectionOptions = { 1097 itemsCount: 2, // 该分组包含2个FlowItem 1098 crossCount: 2, // 使用2列布局 1099 // 回调函数:固定高度100px 1100 onGetItemMainSizeByIndex: (index: number) => { 1101 return 100; 1102 } 1103 }; 1104 1105 // 最后一个分组:用于处理剩余数据 1106 lastSection: SectionOptions = { 1107 itemsCount: 20, // 该分组包含20个FlowItem 1108 crossCount: 2, // 使用2列布局 1109 // 回调函数:使用随机高度 1110 onGetItemMainSizeByIndex: (index: number) => { 1111 return this.itemHeightArray[index % 100]; 1112 } 1113 }; 1114 1115 // 计算FlowItem高度 1116 getSize() { 1117 let ret = Math.floor(Math.random() * this.maxSize); 1118 return (ret > this.minSize ? ret : this.minSize); 1119 } 1120 1121 // 设置FlowItem的高度数组 1122 setItemSizeArray() { 1123 for (let i = 0; i < 100; i++) { 1124 this.itemHeightArray.push(this.getSize()); 1125 } 1126 } 1127 1128 // 组件生命周期:初始化数据和恢复上次的列数设置 1129 aboutToAppear() { 1130 this.setItemSizeArray(); 1131 1132 // 初始化瀑布流分组信息:交替使用单列和双列布局 1133 let sectionOptions: SectionOptions[] = []; 1134 let count = 0; // 已分配的FlowItem数量计数 1135 let oneOrTwo = 0; // 用于交替选择分组类型 1136 1137 while (count < this.dataCount) { 1138 // 剩余数据不足20个时,使用最后一个分组处理 1139 if (this.dataCount - count < 20) { 1140 this.lastSection.itemsCount = this.dataCount - count; 1141 sectionOptions.push(this.lastSection); 1142 break; 1143 } 1144 1145 // 交替使用单列和双列布局 1146 if (oneOrTwo++ % 2 == 0) { 1147 sectionOptions.push(this.oneColumnSection); 1148 count += this.oneColumnSection.itemsCount; 1149 } else { 1150 sectionOptions.push(this.twoColumnSection); 1151 count += this.twoColumnSection.itemsCount; 1152 } 1153 } 1154 1155 // 将配置好的分组添加到WaterFlow中 1156 this.sections.splice(0, 0, sectionOptions); 1157 } 1158 1159 build() { 1160 Column({ space: 2 }) { 1161 Row() { 1162 Button('splice') 1163 .height('5%') 1164 .onClick(() => { 1165 // 重要:必须保证LazyForEach中数据数量和新分组itemsCount累计总数保持一致 1166 let totalCount: number = this.dataSource.totalCount(); 1167 let newSection: SectionOptions = { 1168 itemsCount: totalCount, 1169 crossCount: 2, 1170 onGetItemMainSizeByIndex: (index: number) => { 1171 return this.itemHeightArray[index % 100]; 1172 } 1173 }; 1174 let oldLength: number = this.sections.length(); 1175 this.sections.splice(0, oldLength, [newSection]); // 替换所有分组 1176 }) 1177 .margin({ top: 10, left: 20 }) 1178 1179 Button('update') 1180 .height('5%') 1181 .onClick(() => { 1182 // 在第一个分组中增加4个FlowItem 1183 // 重要:必须保证数据源和分组itemsCount同步更新 1184 const sections: Array<SectionOptions> = this.sections.values(); 1185 let newSection: SectionOptions = sections[0]; 1186 1187 // 先在数据源中添加4个新数据 1188 this.dataSource.addItem(this.oneColumnSection.itemsCount); 1189 this.dataSource.addItem(this.oneColumnSection.itemsCount + 1); 1190 this.dataSource.addItem(this.oneColumnSection.itemsCount + 2); 1191 this.dataSource.addItem(this.oneColumnSection.itemsCount + 3); 1192 1193 // 然后更新分组的itemsCount 1194 newSection.itemsCount += 4; 1195 const result: boolean = this.sections.update(0, newSection); 1196 console.info('update:' + result); 1197 }) 1198 .margin({ top: 10, left: 20 }) 1199 1200 Button('delete') 1201 .height('5%') 1202 .onClick(() => { 1203 // 在第一个分组中减少4个FlowItem 1204 // 重要:必须保证数据源和分组itemsCount同步更新 1205 const sections: Array<SectionOptions> = this.sections.values(); 1206 let newSection: SectionOptions = sections[0]; 1207 1208 // 检查是否有足够的item可以删除 1209 if (newSection.itemsCount < 4) { 1210 return; 1211 } 1212 1213 // 先从数据源中删除4条数据 1214 this.dataSource.deleteItem(this.oneColumnSection.itemsCount - 1); 1215 this.dataSource.deleteItem(this.oneColumnSection.itemsCount - 2); 1216 this.dataSource.deleteItem(this.oneColumnSection.itemsCount - 3); 1217 this.dataSource.deleteItem(this.oneColumnSection.itemsCount - 4); 1218 1219 // 更新分组的itemsCount 1220 newSection.itemsCount -= 4; 1221 this.sections.update(0, newSection); 1222 }) 1223 .margin({ top: 10, left: 20 }) 1224 1225 Button('values') 1226 .height('5%') 1227 .onClick(() => { 1228 const sections: Array<SectionOptions> = this.sections.values(); 1229 for (const value of sections) { 1230 console.log(JSON.stringify(value)); 1231 } 1232 console.info('count:' + this.sections.length()); 1233 }) 1234 .margin({ top: 10, left: 20 }) 1235 }.margin({ bottom: 20 }) 1236 1237 WaterFlow({ scroller: this.scroller, sections: this.sections }) { 1238 LazyForEach(this.dataSource, (item: number) => { 1239 FlowItem() { 1240 // 使用可复用组件,提升性能 1241 ReusableFlowItem({ item: item }) 1242 } 1243 .width('100%') 1244 // 注意:同时设置onGetItemMainSizeByIndex和height属性时, 1245 // 主轴大小以onGetItemMainSizeByIndex返回结果为准 1246 .height(this.itemHeightArray[item % 100]) 1247 .backgroundColor(this.colors[item % this.colors.length]) 1248 }, (item: string) => item) 1249 } 1250 .columnsTemplate('1fr 1fr') 1251 .columnsGap(10) 1252 .rowsGap(5) 1253 .backgroundColor(0xFAEEE0) 1254 .width('100%') 1255 .height('100%') 1256 .layoutWeight(1) 1257 .onScrollIndex((first: number, last: number) => { 1258 // 滚动监听:即将触底时提前加载更多数据 1259 if (last + 20 >= this.dataSource.totalCount()) { 1260 // 添加100个新数据到数据源 1261 for (let i = 0; i < 100; i++) { 1262 this.dataSource.addLastItem(); 1263 } 1264 1265 // 重要:更新数据源后必须同步更新sections 1266 // 修改最后一个section的FlowItem数量 1267 const sections: Array<SectionOptions> = this.sections.values(); 1268 let newSection: SectionOptions = sections[this.sections.length() - 1]; 1269 newSection.itemsCount += 100; 1270 this.sections.update(-1, newSection); // -1表示最后一个分组 1271 } 1272 }) 1273 } 1274 } 1275} 1276``` 1277 1278 1279 1280### 示例4(双指缩放改变列数) 1281该示例通过[priorityGesture](ts-gesture-settings.md)和[PinchGesture](ts-basic-gestures-pinchgesture.md)实现了双指缩放改变列数效果。 1282 1283WaterFlowDataSource说明及完整代码参考[示例1使用基本瀑布流](#示例1使用基本瀑布流)。 1284 1285<!--code_no_check--> 1286```ts 1287// Index.ets 1288import { WaterFlowDataSource } from './WaterFlowDataSource'; 1289import { image } from '@kit.ImageKit'; 1290 1291// 可复用组件:优化性能,减少组件创建销毁开销 1292@Reusable 1293@Component 1294struct ReusableFlowItem { 1295 @State item: number = 0; 1296 1297 // 从复用缓存中加入到组件树之前调用,可在此处更新组件的状态变量以展示正确的内容 1298 aboutToReuse(params: Record<string, number>) { 1299 this.item = params.item; 1300 } 1301 1302 build() { 1303 Column() { 1304 Text("N" + this.item).fontSize(12).height('16') 1305 // 注意:需要确保对应的jpg文件存在才会正常显示 1306 Image('res/waterFlow(' + this.item % 5 + ').jpg') 1307 .objectFit(ImageFit.Fill) 1308 .width('100%') 1309 .layoutWeight(1) 1310 } 1311 } 1312} 1313 1314@Entry 1315@Component 1316struct WaterFlowDemo { 1317 minSize: number = 80; 1318 maxSize: number = 180; 1319 colors: number[] = [0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F]; 1320 dataSource: WaterFlowDataSource = new WaterFlowDataSource(); 1321 private itemWidthArray: number[] = []; 1322 private itemHeightArray: number[] = []; 1323 @State columns: number = 2; 1324 @State waterFlowScale: number = 1; 1325 @State imageScale: number = 1; 1326 @State waterFlowOpacity: number = 1; 1327 @State waterFlowSnapshot: image.PixelMap | undefined = undefined; 1328 private columnChanged: boolean = false; 1329 private oldColumn: number = this.columns; 1330 private pinchTime: number = 0; 1331 1332 // 计算FlowItem宽/高 1333 getSize() { 1334 let ret = Math.floor(Math.random() * this.maxSize); 1335 return (ret > this.minSize ? ret : this.minSize); 1336 } 1337 1338 // 设置FlowItem的宽/高数组 1339 setItemSizeArray() { 1340 for (let i = 0; i < 100; i++) { 1341 this.itemWidthArray.push(this.getSize()); 1342 this.itemHeightArray.push(this.getSize()); 1343 } 1344 } 1345 1346 // 组件生命周期:初始化数据和恢复上次的列数设置 1347 aboutToAppear() { 1348 // 读取上次最后切换到到列数 1349 let lastCount = AppStorage.get<number>('columnsCount'); 1350 if (typeof lastCount != 'undefined') { 1351 this.columns = lastCount; 1352 } 1353 this.setItemSizeArray(); 1354 } 1355 1356 // 根据缩放阈值改变列数,触发WaterFlow重新布局 1357 changeColumns(scale: number) { 1358 if (scale > (this.columns / (this.columns - 0.5)) && this.columns > 1) { 1359 this.columns--; 1360 this.columnChanged = true; 1361 } else if (scale < 1 && this.columns < 4) { 1362 this.columns++; 1363 this.columnChanged = true; 1364 } 1365 } 1366 1367 build() { 1368 Column({ space: 2 }) { 1369 Row() { 1370 Text('双指缩放改变列数') 1371 .height('5%') 1372 .margin({ top: 10, left: 20 }) 1373 } 1374 1375 Stack() { 1376 // 用于展示缩放前的WaterFlow截图 1377 Image(this.waterFlowSnapshot) 1378 .width('100%') 1379 .height('100%') 1380 .scale({ 1381 x: this.imageScale, 1382 y: this.imageScale, 1383 centerX: 0, 1384 centerY: 0 1385 }) 1386 1387 WaterFlow() { 1388 LazyForEach(this.dataSource, (item: number) => { 1389 FlowItem() { 1390 // 使用可复用组件,提升性能 1391 ReusableFlowItem({ item: item }) 1392 } 1393 .width('100%') 1394 .aspectRatio(this.itemHeightArray[item % 100] / this.itemWidthArray[item%100]) 1395 .backgroundColor(this.colors[item % this.colors.length]) 1396 }, (item: string) => item) 1397 } 1398 .id('waterflow') // 设置id用于截图 1399 .columnsTemplate('1fr '.repeat(this.columns)) // 动态生成列模板,如:'1fr 1fr 1fr'表示3列等宽 1400 .backgroundColor(0xFAEEE0) 1401 .width('100%') 1402 .height('100%') 1403 .layoutWeight(1) 1404 .opacity(this.waterFlowOpacity) 1405 .scale({ 1406 x: this.waterFlowScale, 1407 y: this.waterFlowScale, 1408 centerX: 0, 1409 centerY: 0 1410 }) 1411 .priorityGesture( 1412 PinchGesture() 1413 .onActionStart((event: GestureEvent) => { 1414 // 双指捏合手势识别成功时截图 1415 this.pinchTime = event.timestamp; 1416 this.columnChanged = false; 1417 this.oldColumn = this.columns; 1418 this.getUIContext().getComponentSnapshot().get('waterflow', (error: Error, pixmap: image.PixelMap) => { 1419 if (error) { 1420 console.info('error:' + JSON.stringify(error)); 1421 return; 1422 } 1423 this.waterFlowSnapshot = pixmap; 1424 }) 1425 }) 1426 .onActionUpdate((event: GestureEvent) => { 1427 // 手势更新:处理缩放逻辑和视觉效果 1428 // 边界限制:防止超出列数范围时继续缩放 1429 if ((this.oldColumn === 1 && event.scale > 1) || (this.oldColumn === 4 && event.scale < 1)) { 1430 return; 1431 } 1432 1433 // 节流处理:避免过于频繁的更新,提升性能 1434 if (event.timestamp - this.pinchTime < 10000000) { 1435 return; 1436 } 1437 this.pinchTime = event.timestamp; 1438 1439 this.waterFlowScale = event.scale; 1440 this.imageScale = event.scale; 1441 // 根据缩放比例设置WaterFlow透明度 1442 this.waterFlowOpacity = (this.waterFlowScale > 1) ? (this.waterFlowScale - 1) : (1 - this.waterFlowScale); 1443 this.waterFlowOpacity *= 3; 1444 if (!this.columnChanged) { 1445 this.changeColumns(event.scale); 1446 } 1447 1448 // 列数改变后的缩放比例调整:避免出现空白区域 1449 if (this.columnChanged) { 1450 this.waterFlowScale = this.imageScale * this.columns / this.oldColumn; 1451 1452 // 限制缩放范围,确保视觉效果自然 1453 if (event.scale < 1) { 1454 this.waterFlowScale = this.waterFlowScale > 1 ? this.waterFlowScale : 1; 1455 } else { 1456 this.waterFlowScale = this.waterFlowScale < 1 ? this.waterFlowScale : 1; 1457 } 1458 } 1459 }) 1460 .onActionEnd((event: GestureEvent) => { 1461 // 手势结束:执行归位动画并保存状态 1462 // 执行归位动画:平滑过渡到正常状态 1463 this.getUIContext()?.animateTo({ duration: 300 }, () => { 1464 this.waterFlowScale = 1; 1465 this.waterFlowOpacity = 1; 1466 }) 1467 1468 // 持久化保存当前列数:下次启动时恢复 1469 AppStorage.setOrCreate<number>('columnsCount', this.columns); 1470 }) 1471 ) 1472 } 1473 } 1474 } 1475} 1476``` 1477 1478 1479 1480### 示例5(设置边缘渐隐效果) 1481该示例通过[fadingEdge](ts-container-scrollable-common.md#fadingedge14)实现了WaterFlow组件开启边缘渐隐效果,并通过fadingEdgeLength参数设置边缘渐隐长度。 1482 1483WaterFlowDataSource说明及完整代码参考[示例1使用基本瀑布流](#示例1使用基本瀑布流)。 1484 1485<!--code_no_check--> 1486```ts 1487// Index.ets 1488import { LengthMetrics } from '@kit.ArkUI'; 1489import { WaterFlowDataSource } from './WaterFlowDataSource'; 1490 1491@Entry 1492@Component 1493struct WaterFlowDemo { 1494 @State minSize: number = 80; 1495 @State maxSize: number = 180; 1496 @State colors: number[] = [0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F]; 1497 dataSource: WaterFlowDataSource = new WaterFlowDataSource(); 1498 scroller: Scroller = new Scroller(); 1499 private itemWidthArray: number[] = []; 1500 private itemHeightArray: number[] = []; 1501 1502 // 计算FlowItem宽/高 1503 getSize() { 1504 let ret = Math.floor(Math.random() * this.maxSize); 1505 return (ret > this.minSize ? ret : this.minSize); 1506 } 1507 1508 // 设置FlowItem宽/高数组 1509 setItemSizeArray() { 1510 for (let i = 0; i < 100; i++) { 1511 this.itemWidthArray.push(this.getSize()); 1512 this.itemHeightArray.push(this.getSize()); 1513 } 1514 } 1515 1516 // 组件生命周期:在组件即将出现时初始化尺寸数组 1517 aboutToAppear() { 1518 this.setItemSizeArray(); 1519 } 1520 1521 build() { 1522 Column({ space: 2 }) { 1523 WaterFlow({ scroller: this.scroller }) { 1524 LazyForEach(this.dataSource, (item: number) => { 1525 FlowItem() { 1526 Column() { 1527 Text("N" + item).fontSize(12).height('16') 1528 } 1529 } 1530 .width('100%') 1531 .height(this.itemHeightArray[item % 100]) 1532 .backgroundColor(this.colors[item % 5]) 1533 }, (item: string) => item) 1534 } 1535 // auto-fill自动计算列数:根据容器宽度自动计算能放下多少个80px宽的列 1536 .columnsTemplate('repeat(auto-fill,80)') 1537 .columnsGap(10) 1538 .rowsGap(5) 1539 .height('90%') 1540 .scrollBar(BarState.On) 1541 // 边缘渐隐效果:在滚动边缘创建渐隐过渡效果 1542 // true:启用渐隐效果 1543 // fadingEdgeLength: LengthMetrics.vp(80):渐隐区域长度为80vp 1544 // 效果:在瀑布流顶部和底部边缘会有80vp的渐隐过渡区域 1545 .fadingEdge(true, { fadingEdgeLength: LengthMetrics.vp(80) }) 1546 } 1547 } 1548} 1549``` 1550 1551 1552 1553### 示例6(单边边缘效果) 1554 1555该示例通过edgeEffect接口,实现了WaterFlow组件设置单边边缘效果。 1556 1557WaterFlowDataSource说明及完整代码参考[示例1使用基本瀑布流](#示例1使用基本瀑布流)。 1558 1559<!--code_no_check--> 1560```ts 1561// Index.ets 1562import { WaterFlowDataSource } from './WaterFlowDataSource'; 1563 1564@Entry 1565@Component 1566struct WaterFlowDemo { 1567 @State minSize: number = 80; 1568 @State maxSize: number = 180; 1569 @State colors: number[] = [0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F]; 1570 dataSource: WaterFlowDataSource = new WaterFlowDataSource(); 1571 scroller: Scroller = new Scroller(); 1572 private itemWidthArray: number[] = []; 1573 private itemHeightArray: number[] = []; 1574 1575 // 计算FlowItem宽/高 1576 getSize() { 1577 let ret = Math.floor(Math.random() * this.maxSize); 1578 return (ret > this.minSize ? ret : this.minSize); 1579 } 1580 1581 // 设置FlowItem宽/高数组 1582 setItemSizeArray() { 1583 for (let i = 0; i < 100; i++) { 1584 this.itemWidthArray.push(this.getSize()); 1585 this.itemHeightArray.push(this.getSize()); 1586 } 1587 } 1588 1589 // 组件生命周期:在组件即将出现时初始化尺寸数组 1590 aboutToAppear() { 1591 this.setItemSizeArray(); 1592 } 1593 1594 build() { 1595 Column({ space: 2 }) { 1596 WaterFlow({ scroller: this.scroller }) { 1597 LazyForEach(this.dataSource, (item: number) => { 1598 FlowItem() { 1599 Column() { 1600 Text("N" + item).fontSize(12).height('16') 1601 } 1602 } 1603 .width('100%') 1604 .height(this.itemHeightArray[item % 100]) 1605 .backgroundColor(this.colors[item % 5]) 1606 }, (item: number) => item.toString()) 1607 } 1608 // auto-fill自动计算列数:根据容器宽度自动计算能放下多少个80px宽的列 1609 .columnsTemplate('repeat(auto-fill,80)') 1610 .columnsGap(10) 1611 .rowsGap(5) 1612 .height('90%') 1613 // 单边边缘效果:设置弹簧效果,仅在顶部生效 1614 // EdgeEffect.Spring:弹簧回弹效果,滑动到边界时会有弹性回弹 1615 // alwaysEnabled: true:始终启用边缘效果,即使内容不足以滚动 1616 // effectEdge: EffectEdge.START:仅在起始边缘(顶部)生效 1617 // 效果:只有向上滑动到顶部时才会有弹簧回弹效果,向下滑动到底部不会有效果 1618 .edgeEffect(EdgeEffect.Spring, { alwaysEnabled: true, effectEdge: EffectEdge.START }) 1619 1620 } 1621 } 1622} 1623``` 1624 1625 1626 1627### 示例7(WaterFlow组件设置和改变尾部组件) 1628 1629该示例通过footerContent接口,实现了WaterFlow组件设置尾部组件。通过ComponentContent的update函数更新尾部组件。 1630 1631WaterFlowDataSource说明及完整代码参考[示例1使用基本瀑布流](#示例1使用基本瀑布流)。 1632 1633<!--code_no_check--> 1634```ts 1635// Index.ets 1636import { ComponentContent, UIContext } from "@kit.ArkUI"; 1637import { WaterFlowDataSource } from './WaterFlowDataSource'; 1638 1639class Params { 1640 text: string = ""; 1641 1642 constructor(text: string) { 1643 this.text = text; 1644 } 1645} 1646 1647// Builder函数:构建尾部组件的UI结构 1648@Builder 1649function buildText(params: Params) { 1650 Column() { 1651 Text(params.text) 1652 .fontSize(20) 1653 .fontWeight(FontWeight.Bold) 1654 .margin(20) 1655 } 1656} 1657 1658@Entry 1659@Component 1660struct Index { 1661 @State message1: string = "已经到底了"; 1662 @State message2: string = "加载更多"; 1663 @State colors: number[] = [0xD5D5D5, 0x7F7F7F, 0xF7F7F7]; 1664 @State minSize: number = 80; 1665 @State maxSize: number = 180; 1666 1667 // UI上下文:用于创建ComponentContent 1668 context: UIContext = this.getUIContext(); 1669 1670 // 动态尾部组件:使用ComponentContent创建可更新的尾部组件 1671 // ComponentContent<Params>:泛型指定参数类型 1672 // wrapBuilder<[Params]>(buildText):包装Builder函数 1673 // new Params(this.message1):初始参数,显示"已经到底了" 1674 footerContent: ComponentContent<Params> = new ComponentContent<Params>( 1675 this.context, 1676 wrapBuilder<[Params]>(buildText), 1677 new Params(this.message1) 1678 ); 1679 1680 dataSource: WaterFlowDataSource = new WaterFlowDataSource(); 1681 private itemWidthArray: number[] = []; 1682 private itemHeightArray: number[] = []; 1683 1684 // 计算FlowItem宽/高 1685 getSize() { 1686 let ret = Math.floor(Math.random() * this.maxSize); 1687 return (ret > this.minSize ? ret : this.minSize); 1688 } 1689 1690 // 设置FlowItem宽/高数组 1691 setItemSizeArray() { 1692 for (let i = 0; i < 100; i++) { 1693 this.itemWidthArray.push(this.getSize()); 1694 this.itemHeightArray.push(this.getSize()); 1695 } 1696 } 1697 1698 // 组件生命周期:在组件即将出现时初始化尺寸数组 1699 aboutToAppear() { 1700 this.setItemSizeArray(); 1701 } 1702 1703 build() { 1704 Row() { 1705 Column() { 1706 Button("更新footer").width('90%').margin(20) 1707 .onClick((event?: ClickEvent) => { 1708 // 调用ComponentContent的update方法更新尾部组件 1709 // 传入新的Params对象,文本内容从"已经到底了"变为"加载更多" 1710 this.footerContent.update(new Params(this.message2)); 1711 }) 1712 WaterFlow({ footerContent: this.footerContent }) { 1713 LazyForEach(this.dataSource, (item: number) => { 1714 FlowItem() { 1715 Column() { 1716 Text("N" + item).fontSize(12).height('16') 1717 } 1718 .width('100%') 1719 .height(this.itemHeightArray[item % 100]) 1720 .backgroundColor(this.colors[item % 3]) 1721 .justifyContent(FlexAlign.Center) 1722 .alignItems(HorizontalAlign.Center) 1723 } 1724 }, (item: number) => item.toString()) 1725 } 1726 .columnsTemplate('1fr') 1727 .height("90%") 1728 } 1729 .width('100%') 1730 .height('100%') 1731 } 1732 .height('100%') 1733 } 1734} 1735``` 1736 1737 1738 1739### 示例8(WaterFlow组件实现下拉刷新) 1740 1741该示例通过Refresh组件和WaterFlow组件,实现了下拉刷新瀑布流组件数据源。 1742 1743WaterFlowDataSource说明及完整代码参考[示例1使用基本瀑布流](#示例1使用基本瀑布流)。 1744 1745<!--code_no_check--> 1746```ts 1747// Index.ets 1748import { WaterFlowDataSource } from './WaterFlowDataSource'; 1749 1750@Entry 1751@Component 1752struct WaterFlowDemo { 1753 @State minSize: number = 80; 1754 @State maxSize: number = 180; 1755 @State colors: number[] = [0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F]; 1756 @State isRefreshing: boolean = false; 1757 dataSource: WaterFlowDataSource = new WaterFlowDataSource(); 1758 scroller: Scroller = new Scroller(); 1759 private itemWidthArray: number[] = []; 1760 private itemHeightArray: number[] = []; 1761 1762 // 计算FlowItem宽/高 1763 getSize() { 1764 let ret = Math.floor(Math.random() * this.maxSize); 1765 return (ret > this.minSize ? ret : this.minSize); 1766 } 1767 1768 // 设置FlowItem宽/高数组 1769 setItemSizeArray() { 1770 for (let i = 0; i < 100; i++) { 1771 this.itemWidthArray.push(this.getSize()); 1772 this.itemHeightArray.push(this.getSize()); 1773 } 1774 } 1775 1776 // 组件生命周期:在组件即将出现时初始化尺寸数组 1777 aboutToAppear() { 1778 this.setItemSizeArray(); 1779 } 1780 1781 build() { 1782 Column({ space: 2 }) { 1783 // refreshing: $$this.isRefreshing:双向绑定刷新状态 1784 Refresh({ refreshing: $$this.isRefreshing }) { 1785 WaterFlow({ scroller: this.scroller }) { 1786 LazyForEach(this.dataSource, (item: number) => { 1787 FlowItem() { 1788 Column() { 1789 Text('N' + item).fontSize(12).height('16') 1790 } 1791 } 1792 .width('100%') 1793 .height(this.itemHeightArray[item % 100]) 1794 .backgroundColor(this.colors[item % this.colors.length]) 1795 }, (item: number) => item.toString()) 1796 } 1797 // auto-fill自动计算列数:根据容器宽度自动计算能放下多少个80px宽的列 1798 .columnsTemplate('repeat(auto-fill,80)') 1799 .columnsGap(10) 1800 .rowsGap(5) 1801 .height('90%') 1802 // 边缘效果:弹簧回弹效果 1803 .edgeEffect(EdgeEffect.Spring, { alwaysEnabled: true }) 1804 .onReachEnd(() => { 1805 // 触底加载更多数据:滚动到底部时触发 1806 setTimeout(() => { 1807 this.dataSource.addNewItems(100); 1808 }, 1000) 1809 }) 1810 } 1811 .onStateChange((refreshStatus: RefreshStatus) => { 1812 // 刷新状态变化监听:处理不同的刷新状态 1813 if (refreshStatus === RefreshStatus.Done) { 1814 // 刷新完成时:调用数据源的刷新方法,更新所有数据 1815 this.dataSource.refreshItems(); 1816 } 1817 }) 1818 .onRefreshing(() => { 1819 // 正在刷新时的回调:模拟刷新过程 1820 setTimeout(() => { 1821 this.isRefreshing = false; 1822 }, 1000) 1823 }) 1824 } 1825 } 1826} 1827``` 1828 1829