ArkTS 瀑布流 WaterFlow 总结
ArkTS瀑布流(WaterFlow)组件总结 WaterFlow是ArkTS中实现瀑布流布局的容器组件,具有自适应布局、高性能和灵活配置等特点。主要功能包括: 支持列/行布局,通过columnsTemplate/rowsTemplate设置模板 可配置间距(columnsGap/rowsGap)和滚动方向(layoutDirection) 内置滚动控制(Scroller),支持滚动到指定位置或索
·
ArkTS 瀑布流 WaterFlow 总结
📋 概述
WaterFlow 是 ArkTS 中用于实现瀑布流布局的容器组件。它可以按照列或行的方式排列子组件,自动根据子组件高度或宽度进行布局,常用于图片墙、商品列表、信息流等场景。
主要特点
- ✅ 自适应布局 - 自动计算子项位置,适应不同尺寸
- ✅ 高性能 - 支持懒加载,优化大数据场景
- ✅ 灵活配置 - 支持自定义列数、间距、滚动方向
- ✅ 滚动控制 - 内置滚动控制器,支持滚动到指定位置
- ✅ 多种布局模式 - 支持按列、按行布局
🎯 基本用法
语法结构
WaterFlow(options?: WaterFlowOptions)
interface WaterFlowOptions {
footer?: CustomBuilder // 尾部组件
scroller?: Scroller // 滚动控制器
}
简单示例
@Entry
@Component
struct WaterFlowExample {
@State items: number[] = Array.from({ length: 20 }, (_, i) => i)
build() {
WaterFlow() {
ForEach(this.items, (item: number) => {
FlowItem() {
Column() {
Text(`Item ${item}`)
.fontSize(16)
.textAlign(TextAlign.Center)
}
.width('100%')
.height(100 + Math.random() * 100) // 随机高度
.backgroundColor('#E0E0E0')
.borderRadius(8)
}
})
}
.columnsTemplate('1fr 1fr') // 2列布局
.columnsGap(10)
.rowsGap(10)
.padding(10)
}
}
⚙️ 常用属性
1. columnsTemplate - 列模板
columnsTemplate(value: string)
说明: 设置瀑布流的列数和宽度分配方式
示例:
// 2列,等宽
WaterFlow().columnsTemplate("1fr 1fr");
// 3列,等宽
WaterFlow().columnsTemplate("1fr 1fr 1fr");
// 2列,第一列是第二列的2倍宽
WaterFlow().columnsTemplate("2fr 1fr");
// 固定宽度列
WaterFlow().columnsTemplate("100vp 1fr 100vp");
2. rowsTemplate - 行模板
rowsTemplate(value: string)
说明: 设置瀑布流的行数和高度分配方式(用于横向滚动)
示例:
// 2行,等高
WaterFlow().rowsTemplate("1fr 1fr");
// 3行,等高
WaterFlow().rowsTemplate("1fr 1fr 1fr");
3. columnsGap - 列间距
columnsGap(value: Length)
示例:
WaterFlow().columnsGap(10); // 列间距 10vp
WaterFlow().columnsGap(16); // 列间距 16vp
4. rowsGap - 行间距
rowsGap(value: Length)
示例:
WaterFlow().rowsGap(10); // 行间距 10vp
WaterFlow().rowsGap(16); // 行间距 16vp
5. layoutDirection - 布局方向
layoutDirection(value: FlexDirection)
enum FlexDirection {
Row, // 主轴为水平方向,子项从左到右排列
Column, // 主轴为垂直方向,子项从上到下排列(默认)
RowReverse, // 主轴为水平方向,子项从右到左排列
ColumnReverse // 主轴为垂直方向,子项从下到上排列
}
示例:
// 垂直滚动(默认)
WaterFlow().layoutDirection(FlexDirection.Column);
// 横向滚动
WaterFlow().layoutDirection(FlexDirection.Row);
6. enableScrollInteraction - 启用滚动交互
enableScrollInteraction(value: boolean)
示例:
WaterFlow().enableScrollInteraction(true); // 允许滚动(默认)
WaterFlow().enableScrollInteraction(false); // 禁用滚动
7. nestedScroll - 嵌套滚动
nestedScroll(value: NestedScrollOptions)
interface NestedScrollOptions {
scrollForward: NestedScrollMode // 向前滚动时的嵌套模式
scrollBackward: NestedScrollMode // 向后滚动时的嵌套模式
}
enum NestedScrollMode {
SELF_ONLY, // 只自己滚动
SELF_FIRST, // 自己先滚动
PARENT_FIRST, // 父组件先滚动
PARALLEL // 自己和父组件同时滚动
}
示例:
WaterFlow().nestedScroll({
scrollForward: NestedScrollMode.PARENT_FIRST,
scrollBackward: NestedScrollMode.SELF_FIRST,
});
8. friction - 滚动摩擦系数
friction(value: number | Resource)
示例:
WaterFlow().friction(0.6); // 默认值 0.6,值越大滚动越慢
9. cachedCount - 缓存数量
cachedCount(value: number)
说明: 设置预加载的子项数量,提升滚动性能
示例:
WaterFlow().cachedCount(2); // 预加载前后各2个子项
🎮 滚动控制器 Scroller
基本用法
@Entry
@Component
struct ScrollerDemo {
private scroller: Scroller = new Scroller()
@State items: number[] = Array.from({ length: 50 }, (_, i) => i)
build() {
Column() {
// 控制按钮
Row() {
Button('滚动到顶部')
.onClick(() => {
this.scroller.scrollEdge(Edge.Top)
})
Button('滚动到底部')
.onClick(() => {
this.scroller.scrollEdge(Edge.Bottom)
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceAround)
.padding(10)
// 瀑布流
WaterFlow({ scroller: this.scroller }) {
ForEach(this.items, (item: number) => {
FlowItem() {
Text(`Item ${item}`)
.width('100%')
.height(80 + Math.random() * 80)
.backgroundColor('#E0E0E0')
.textAlign(TextAlign.Center)
.borderRadius(8)
}
})
}
.columnsTemplate('1fr 1fr')
.columnsGap(10)
.rowsGap(10)
.padding(10)
.layoutWeight(1)
}
.height('100%')
}
}
Scroller 常用方法
// 1. 滚动到指定位置
scroller.scrollTo({
xOffset: 0,
yOffset: 100,
animation: { duration: 300, curve: Curve.Smooth },
});
// 2. 滚动到边缘
scroller.scrollEdge(Edge.Top); // 顶部
scroller.scrollEdge(Edge.Bottom); // 底部
// 3. 滚动指定距离
scroller.scrollBy(0, 100); // 向下滚动 100vp
// 4. 滚动到指定索引
scroller.scrollToIndex(10); // 滚动到第10个子项
// 5. 获取当前滚动偏移
let offset = scroller.currentOffset();
🔄 事件回调
1. onReachStart - 到达起始位置
onReachStart(callback: () => void)
示例:
WaterFlow().onReachStart(() => {
console.log("已到达顶部");
});
2. onReachEnd - 到达结束位置
onReachEnd(callback: () => void)
示例:
WaterFlow().onReachEnd(() => {
console.log("已到达底部");
// 可以在这里加载更多数据
this.loadMore();
});
3. onScrollFrameBegin - 滚动帧开始
onScrollFrameBegin(callback: (offset: number, state: ScrollState) => ScrollFrameResult)
interface ScrollFrameResult {
offsetRemain: number // 剩余滚动距离
}
enum ScrollState {
Idle, // 空闲状态
Scroll, // 滚动中
Fling // 惯性滚动
}
示例:
WaterFlow().onScrollFrameBegin((offset: number, state: ScrollState) => {
console.log(`滚动距离: ${offset}, 状态: ${state}`);
return { offsetRemain: offset };
});
4. onScroll - 滚动时触发
onScroll(callback: (scrollOffset: number, scrollState: ScrollState) => void)
示例:
WaterFlow().onScroll((scrollOffset: number, scrollState: ScrollState) => {
console.log(`当前滚动偏移: ${scrollOffset}`);
});
💡 实战案例
案例 1:图片瀑布流
interface ImageItem {
id: number
url: string
width: number
height: number
}
@Entry
@Component
struct ImageWaterFlowDemo {
@State images: ImageItem[] = [
{ id: 1, url: 'https://example.com/img1.jpg', width: 200, height: 300 },
{ id: 2, url: 'https://example.com/img2.jpg', width: 200, height: 200 },
{ id: 3, url: 'https://example.com/img3.jpg', width: 200, height: 400 },
{ id: 4, url: 'https://example.com/img4.jpg', width: 200, height: 250 },
{ id: 5, url: 'https://example.com/img5.jpg', width: 200, height: 350 },
{ id: 6, url: 'https://example.com/img6.jpg', width: 200, height: 280 }
]
build() {
WaterFlow() {
ForEach(this.images, (item: ImageItem) => {
FlowItem() {
Column() {
Image(item.url)
.width('100%')
.aspectRatio(item.width / item.height)
.borderRadius({ topLeft: 8, topRight: 8 })
Text(`ID: ${item.id}`)
.fontSize(14)
.padding(8)
.width('100%')
}
.width('100%')
.backgroundColor(Color.White)
.borderRadius(8)
.shadow({ radius: 4, color: '#00000020' })
}
})
}
.columnsTemplate('1fr 1fr')
.columnsGap(12)
.rowsGap(12)
.padding(12)
.backgroundColor('#F5F5F5')
}
}
案例 2:商品列表(下拉刷新 + 上拉加载)
interface Product {
id: number
name: string
price: number
image: string
}
@Entry
@Component
struct ProductListDemo {
private scroller: Scroller = new Scroller()
@State products: Product[] = []
@State isLoading: boolean = false
@State hasMore: boolean = true
aboutToAppear() {
this.loadProducts()
}
// 加载商品数据
loadProducts() {
const newProducts: Product[] = Array.from({ length: 10 }, (_, i) => ({
id: this.products.length + i,
name: `商品 ${this.products.length + i + 1}`,
price: Math.floor(Math.random() * 1000) + 100,
image: `https://example.com/product${i}.jpg`
}))
this.products = [...this.products, ...newProducts]
}
// 加载更多
loadMore() {
if (this.isLoading || !this.hasMore) return
this.isLoading = true
console.log('加载更多...')
// 模拟网络请求
setTimeout(() => {
this.loadProducts()
this.isLoading = false
// 假设加载3次后没有更多数据
if (this.products.length >= 30) {
this.hasMore = false
}
}, 1000)
}
@Builder
ProductCard(product: Product) {
Column() {
Image(product.image)
.width('100%')
.height(150)
.objectFit(ImageFit.Cover)
.borderRadius({ topLeft: 8, topRight: 8 })
Column() {
Text(product.name)
.fontSize(14)
.fontWeight(FontWeight.Bold)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(`¥${product.price}`)
.fontSize(16)
.fontColor('#FF4500')
.fontWeight(FontWeight.Bold)
.margin({ top: 4 })
}
.width('100%')
.padding(8)
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.backgroundColor(Color.White)
.borderRadius(8)
.shadow({ radius: 4, color: '#00000015' })
}
build() {
Column() {
// 标题栏
Row() {
Text('商品列表')
.fontSize(20)
.fontWeight(FontWeight.Bold)
}
.width('100%')
.height(56)
.padding({ left: 16, right: 16 })
.backgroundColor(Color.White)
// 商品瀑布流
WaterFlow({ scroller: this.scroller }) {
ForEach(this.products, (product: Product) => {
FlowItem() {
this.ProductCard(product)
}
})
// 加载更多提示
if (this.isLoading) {
FlowItem() {
Row() {
LoadingProgress()
.width(24)
.height(24)
.margin({ right: 8 })
Text('加载中...')
.fontSize(14)
.fontColor('#999999')
}
.width('100%')
.height(60)
.justifyContent(FlexAlign.Center)
}
.columnStart(0)
.columnEnd(1) // 跨2列
}
// 没有更多数据提示
if (!this.hasMore) {
FlowItem() {
Text('没有更多数据了')
.fontSize(14)
.fontColor('#999999')
.width('100%')
.height(60)
.textAlign(TextAlign.Center)
}
.columnStart(0)
.columnEnd(1)
}
}
.columnsTemplate('1fr 1fr')
.columnsGap(10)
.rowsGap(10)
.padding(10)
.backgroundColor('#F5F5F5')
.layoutWeight(1)
.onReachEnd(() => {
this.loadMore()
})
}
.width('100%')
.height('100%')
}
}
案例 3:动态列数(响应式布局)
@Entry
@Component
struct ResponsiveWaterFlowDemo {
@State items: number[] = Array.from({ length: 30 }, (_, i) => i)
@State columnsTemplate: string = '1fr 1fr'
@State currentBreakpoint: string = 'sm'
// 根据屏幕宽度更新列数
updateColumnsTemplate(width: number) {
if (width < 600) {
this.columnsTemplate = '1fr 1fr' // 小屏:2列
this.currentBreakpoint = 'sm'
} else if (width < 900) {
this.columnsTemplate = '1fr 1fr 1fr' // 中屏:3列
this.currentBreakpoint = 'md'
} else {
this.columnsTemplate = '1fr 1fr 1fr 1fr' // 大屏:4列
this.currentBreakpoint = 'lg'
}
}
build() {
Column() {
// 信息栏
Text(`当前断点: ${this.currentBreakpoint}`)
.fontSize(16)
.padding(16)
.backgroundColor('#E0E0E0')
WaterFlow() {
ForEach(this.items, (item: number) => {
FlowItem() {
Column() {
Text(`Item ${item}`)
.fontSize(16)
.fontWeight(FontWeight.Bold)
Text(`列模板: ${this.columnsTemplate}`)
.fontSize(12)
.fontColor('#666666')
.margin({ top: 4 })
}
.width('100%')
.height(100 + (item % 3) * 50)
.padding(16)
.backgroundColor(Color.White)
.borderRadius(8)
.justifyContent(FlexAlign.Center)
}
})
}
.columnsTemplate(this.columnsTemplate)
.columnsGap(12)
.rowsGap(12)
.padding(12)
.backgroundColor('#F5F5F5')
.layoutWeight(1)
.onAreaChange((oldValue: Area, newValue: Area) => {
const width = Number(newValue.width)
this.updateColumnsTemplate(width)
})
}
.width('100%')
.height('100%')
}
}
案例 4:横向瀑布流
@Entry
@Component
struct HorizontalWaterFlowDemo {
@State items: number[] = Array.from({ length: 20 }, (_, i) => i)
build() {
Column() {
Text('横向瀑布流')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.padding(16)
WaterFlow() {
ForEach(this.items, (item: number) => {
FlowItem() {
Column() {
Text(`Item ${item}`)
.fontSize(14)
.textAlign(TextAlign.Center)
}
.width(80 + Math.random() * 80) // 随机宽度
.height('100%')
.backgroundColor('#E0E0E0')
.borderRadius(8)
.justifyContent(FlexAlign.Center)
}
})
}
.rowsTemplate('1fr 1fr 1fr') // 3行
.rowsGap(10)
.columnsGap(10)
.layoutDirection(FlexDirection.Row) // 横向布局
.padding(10)
.backgroundColor('#F5F5F5')
.height(400)
}
.width('100%')
.height('100%')
}
}
案例 5:带分组的瀑布流
interface GroupData {
title: string
items: string[]
}
@Entry
@Component
struct GroupedWaterFlowDemo {
@State groups: GroupData[] = [
{ title: '分组 A', items: ['A1', 'A2', 'A3', 'A4', 'A5'] },
{ title: '分组 B', items: ['B1', 'B2', 'B3', 'B4'] },
{ title: '分组 C', items: ['C1', 'C2', 'C3', 'C4', 'C5', 'C6'] }
]
@Builder
GroupHeader(title: string) {
Text(title)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.width('100%')
.padding({ left: 16, top: 16, bottom: 8 })
.backgroundColor('#F5F5F5')
}
build() {
WaterFlow() {
ForEach(this.groups, (group: GroupData) => {
// 分组标题
FlowItem() {
this.GroupHeader(group.title)
}
.columnStart(0)
.columnEnd(1) // 跨2列
// 分组内容
ForEach(group.items, (item: string) => {
FlowItem() {
Column() {
Text(item)
.fontSize(16)
}
.width('100%')
.height(80 + Math.random() * 60)
.backgroundColor(Color.White)
.borderRadius(8)
.justifyContent(FlexAlign.Center)
}
})
})
}
.columnsTemplate('1fr 1fr')
.columnsGap(10)
.rowsGap(10)
.padding(10)
.backgroundColor('#F5F5F5')
}
}
🎯 最佳实践
1. 合理设置列数
// ✅ 推荐:根据屏幕尺寸动态设置列数
@State columnsTemplate: string = '1fr 1fr'
WaterFlow()
.columnsTemplate(this.columnsTemplate)
.onAreaChange((oldValue, newValue) => {
const width = Number(newValue.width)
if (width < 600) {
this.columnsTemplate = '1fr 1fr'
} else if (width < 900) {
this.columnsTemplate = '1fr 1fr 1fr'
} else {
this.columnsTemplate = '1fr 1fr 1fr 1fr'
}
})
// ❌ 不推荐:固定列数不适配
WaterFlow()
.columnsTemplate('1fr 1fr') // 所有屏幕都是2列
2. 使用懒加载优化性能
// ✅ 推荐:使用 LazyForEach 懒加载
class MyDataSource implements IDataSource {
private dataArray: number[] = []
public totalCount(): number {
return this.dataArray.length
}
public getData(index: number): number {
return this.dataArray[index]
}
public addData(data: number[]): void {
this.dataArray = [...this.dataArray, ...data]
}
registerDataChangeListener(listener: DataChangeListener): void {
// 实现注册逻辑
}
unregisterDataChangeListener(listener: DataChangeListener): void {
// 实现注销逻辑
}
}
@State dataSource: MyDataSource = new MyDataSource()
WaterFlow() {
LazyForEach(this.dataSource, (item: number) => {
FlowItem() {
Text(`Item ${item}`)
}
})
}
// ❌ 不推荐:大数据量使用 ForEach(性能差)
WaterFlow() {
ForEach(this.largeArray, (item) => {
// 大量数据一次性渲染
})
}
3. 设置合理的间距
// ✅ 推荐:设置适中的间距
WaterFlow()
.columnsGap(10) // 10-16vp 适中
.rowsGap(10);
// ❌ 不推荐:间距过大或过小
WaterFlow()
.columnsGap(2) // 太小,视觉拥挤
.rowsGap(50); // 太大,浪费空间
4. 合理使用缓存
// ✅ 推荐:设置合理的缓存数量
WaterFlow().cachedCount(2); // 预加载前后各2个子项
// ❌ 不推荐:缓存过多占用内存
WaterFlow().cachedCount(10); // 缓存过多
5. 上拉加载更多
// ✅ 推荐:使用 onReachEnd 实现加载更多
@State isLoading: boolean = false
@State hasMore: boolean = true
WaterFlow()
.onReachEnd(() => {
if (!this.isLoading && this.hasMore) {
this.loadMore()
}
})
loadMore() {
this.isLoading = true
// 加载数据
fetchData().then(newData => {
this.items = [...this.items, ...newData]
this.isLoading = false
this.hasMore = newData.length > 0
})
}
6. 跨列布局
// ✅ 推荐:标题、提示等元素使用跨列
FlowItem() {
Text('分组标题')
.width('100%')
}
.columnStart(0)
.columnEnd(1) // 跨2列(假设总共2列)
// 适用于:
// - 分组标题
// - 加载提示
// - 空状态提示
⚠️ 注意事项
1. FlowItem 必须是直接子组件
// ✅ 正确:FlowItem 是 WaterFlow 的直接子组件
WaterFlow() {
FlowItem() {
Text('内容')
}
}
// ❌ 错误:FlowItem 不是直接子组件
WaterFlow() {
Column() {
FlowItem() { // 错误!
Text('内容')
}
}
}
2. columnsTemplate 和 rowsTemplate 不能同时设置
// ✅ 正确:只设置 columnsTemplate(垂直滚动)
WaterFlow().columnsTemplate("1fr 1fr").layoutDirection(FlexDirection.Column);
// ✅ 正确:只设置 rowsTemplate(横向滚动)
WaterFlow().rowsTemplate("1fr 1fr").layoutDirection(FlexDirection.Row);
// ❌ 错误:同时设置
WaterFlow().columnsTemplate("1fr 1fr").rowsTemplate("1fr 1fr"); // 错误!
3. 子项高度/宽度必须明确
// ✅ 正确:明确设置高度
FlowItem() {
Column() {
Text('内容')
}
.width('100%')
.height(150) // 明确高度
}
// ❌ 错误:高度不确定
FlowItem() {
Column() {
Text('内容')
}
.width('100%')
// 缺少 height,布局可能异常
}
4. 性能优化
// ✅ 推荐:使用 LazyForEach + 虚拟滚动
WaterFlow() {
LazyForEach(dataSource, (item) => {
FlowItem() {
// 内容
}
})
}
.cachedCount(2)
// ❌ 不推荐:ForEach 渲染大量数据
WaterFlow() {
ForEach(this.largeArray, (item) => {
// 大量内容一次性渲染
})
}
5. 状态管理
// ✅ 正确:使用 @State 管理数据
@State items: number[] = []
// ❌ 错误:使用普通变量
private items: number[] = [] // UI 不会响应变化
📊 常见场景配置
| 场景 | columnsTemplate | columnsGap | rowsGap | cachedCount |
|---|---|---|---|---|
| 图片墙(手机) | ‘1fr 1fr’ | 8-12 | 8-12 | 2 |
| 图片墙(平板) | ‘1fr 1fr 1fr’ | 12-16 | 12-16 | 3 |
| 商品列表 | ‘1fr 1fr’ | 10 | 10 | 2 |
| 信息流 | ‘1fr’ | 0 | 12 | 3 |
| 横向滚动 | - | 10 | 10 | 2 |
📚 总结
核心要点
-
基本结构
- WaterFlow 作为容器
- FlowItem 作为子项(必须是直接子组件)
- 使用 columnsTemplate 或 rowsTemplate 定义布局
-
布局模式
- 垂直瀑布流:设置
columnsTemplate - 横向瀑布流:设置
rowsTemplate+layoutDirection(FlexDirection.Row)
- 垂直瀑布流:设置
-
关键属性
columnsTemplate/rowsTemplate- 定义列/行数columnsGap/rowsGap- 设置间距cachedCount- 优化性能enableScrollInteraction- 控制滚动
-
滚动控制
- 使用
Scroller控制滚动位置 onReachEnd实现加载更多onScroll监听滚动状态
- 使用
-
性能优化
- 使用
LazyForEach懒加载 - 设置合理的
cachedCount - 避免过度渲染
- 明确子项尺寸
- 使用
-
响应式设计
- 根据屏幕宽度动态调整列数
- 使用
onAreaChange监听尺寸变化 - 跨列布局用于标题和提示
📖 参考资料
最后更新时间: 2025-10-22
更多推荐

所有评论(0)