1e41f4b71Sopenharmony_ci 2e41f4b71Sopenharmony_ci 3e41f4b71Sopenharmony_ci# WaterFlow高性能开发指导 4e41f4b71Sopenharmony_ci 5e41f4b71Sopenharmony_ci## 背景 6e41f4b71Sopenharmony_ci 7e41f4b71Sopenharmony_ci瀑布流常用于展示图片信息,如多用于购物、资讯类应用。下面通过对[WaterFlow](../reference/apis-arkui/arkui-ts/ts-container-waterflow.md)组件示例代码的逐步改造,介绍优化WaterFlow性能的方法。 8e41f4b71Sopenharmony_ci 9e41f4b71Sopenharmony_ci## 使用懒加载 10e41f4b71Sopenharmony_ci 11e41f4b71Sopenharmony_ci先看一下组件示例代码中瀑布流的基本用法: 12e41f4b71Sopenharmony_ci 13e41f4b71Sopenharmony_ci```ts 14e41f4b71Sopenharmony_ci build() { 15e41f4b71Sopenharmony_ci Column({ space: 2 }) { 16e41f4b71Sopenharmony_ci WaterFlow() { 17e41f4b71Sopenharmony_ci LazyForEach(this.dataSource, (item: number) => { 18e41f4b71Sopenharmony_ci FlowItem() { 19e41f4b71Sopenharmony_ci Column() { 20e41f4b71Sopenharmony_ci Text("N" + item).fontSize(12).height('16') 21e41f4b71Sopenharmony_ci Image('res/waterFlowTest (' + item % 5 + ').jpg') 22e41f4b71Sopenharmony_ci .objectFit(ImageFit.Fill) 23e41f4b71Sopenharmony_ci .width('100%') 24e41f4b71Sopenharmony_ci .layoutWeight(1) 25e41f4b71Sopenharmony_ci } 26e41f4b71Sopenharmony_ci } 27e41f4b71Sopenharmony_ci .width('100%') 28e41f4b71Sopenharmony_ci // 提前设定FlowItem高度,避免自适应图片高度 29e41f4b71Sopenharmony_ci .height(this.itemHeightArray[item]) 30e41f4b71Sopenharmony_ci .backgroundColor(this.colors[item % 5]) 31e41f4b71Sopenharmony_ci }, (item: string) => item) 32e41f4b71Sopenharmony_ci } 33e41f4b71Sopenharmony_ci .columnsTemplate("1fr 1fr") 34e41f4b71Sopenharmony_ci .columnsGap(10) 35e41f4b71Sopenharmony_ci .rowsGap(5) 36e41f4b71Sopenharmony_ci .backgroundColor(0xFAEEE0) 37e41f4b71Sopenharmony_ci .width('100%') 38e41f4b71Sopenharmony_ci .height('80%') 39e41f4b71Sopenharmony_ci } 40e41f4b71Sopenharmony_ci } 41e41f4b71Sopenharmony_ci``` 42e41f4b71Sopenharmony_ci 43e41f4b71Sopenharmony_ci示例代码已经使用了[LazyForEach](../quick-start/arkts-rendering-control-lazyforeach.md)进行数据懒加载,WaterFlow布局时会根据可视区域按需创建FlowItem组件,并在FlowItem滑出可视区域外时销毁以降低内存占用。 44e41f4b71Sopenharmony_ci 45e41f4b71Sopenharmony_ci另外,由于Image组件默认异步加载,建议提前根据图片大小设定FlowItem的高度,避免图片加载成功后高度变化触发瀑布流刷新布局。 46e41f4b71Sopenharmony_ci 47e41f4b71Sopenharmony_ci## 无限滚动 48e41f4b71Sopenharmony_ci 49e41f4b71Sopenharmony_ci示例代码中FlowItem数量是固定的,不能满足无限滚动的场景。 50e41f4b71Sopenharmony_ci 51e41f4b71Sopenharmony_ci基于WaterFlow本身提供的能力,可以在onReachEnd时给LazyForEach数据源增加新数据,并将footer做成正在加载新数据的样式(使用[LoadingProgress](../reference/apis-arkui/arkui-ts/ts-basic-components-loadingprogress.md)组件)。 52e41f4b71Sopenharmony_ci 53e41f4b71Sopenharmony_ci```ts 54e41f4b71Sopenharmony_ci build() { 55e41f4b71Sopenharmony_ci Column({ space: 2 }) { 56e41f4b71Sopenharmony_ci WaterFlow({ footer: this.itemFoot.bind(this) }) { 57e41f4b71Sopenharmony_ci LazyForEach(this.dataSource, (item: number) => { 58e41f4b71Sopenharmony_ci FlowItem() { 59e41f4b71Sopenharmony_ci Column() { 60e41f4b71Sopenharmony_ci Text("N" + item).fontSize(12).height('16') 61e41f4b71Sopenharmony_ci Image('res/waterFlowTest (' + item % 5 + ').jpg') 62e41f4b71Sopenharmony_ci .objectFit(ImageFit.Fill) 63e41f4b71Sopenharmony_ci .width('100%') 64e41f4b71Sopenharmony_ci .layoutWeight(1) 65e41f4b71Sopenharmony_ci } 66e41f4b71Sopenharmony_ci } 67e41f4b71Sopenharmony_ci .width('100%') 68e41f4b71Sopenharmony_ci .height(this.itemHeightArray[item % 100]) 69e41f4b71Sopenharmony_ci .backgroundColor(this.colors[item % 5]) 70e41f4b71Sopenharmony_ci }, (item: string) => item) 71e41f4b71Sopenharmony_ci } 72e41f4b71Sopenharmony_ci // 触底加载数据 73e41f4b71Sopenharmony_ci .onReachEnd(() => { 74e41f4b71Sopenharmony_ci console.info("onReachEnd") 75e41f4b71Sopenharmony_ci setTimeout(() => { 76e41f4b71Sopenharmony_ci this.dataSource.addNewItems(100) 77e41f4b71Sopenharmony_ci }, 1000) 78e41f4b71Sopenharmony_ci }) 79e41f4b71Sopenharmony_ci .columnsTemplate("1fr 1fr") 80e41f4b71Sopenharmony_ci .columnsGap(10) 81e41f4b71Sopenharmony_ci .rowsGap(5) 82e41f4b71Sopenharmony_ci .backgroundColor(0xFAEEE0) 83e41f4b71Sopenharmony_ci .width('100%') 84e41f4b71Sopenharmony_ci .height('80%') 85e41f4b71Sopenharmony_ci } 86e41f4b71Sopenharmony_ci } 87e41f4b71Sopenharmony_ci 88e41f4b71Sopenharmony_ci // 在数据尾部增加count个元素 89e41f4b71Sopenharmony_ci public addNewItems(count: number): void { 90e41f4b71Sopenharmony_ci let len = this.dataArray.length 91e41f4b71Sopenharmony_ci for (let i = 0; i < count; i++) { 92e41f4b71Sopenharmony_ci this.dataArray.push(this.dataArray.length) 93e41f4b71Sopenharmony_ci } 94e41f4b71Sopenharmony_ci this.notifyDatasetChange([{ type: DataOperationType.ADD, index: len, count: count }]); 95e41f4b71Sopenharmony_ci } 96e41f4b71Sopenharmony_ci``` 97e41f4b71Sopenharmony_ci 98e41f4b71Sopenharmony_ci此处需要通过在尾部增加元素的方式新增数据,不能使用直接修改dataArray后通过LazyForEach的onDataReloaded()通知瀑布流重新加载数据。 99e41f4b71Sopenharmony_ci 100e41f4b71Sopenharmony_ci由于瀑布流布局子组件高度不相等的特点,下面节点的位置依赖上面的节点,重新加载所有数据会触发整个瀑布流重新计算布局导致卡顿。而在数据末尾增加数据后使用notifyDatasetChange([{ type: [DataOperationType.ADD](../quick-start/arkts-rendering-control-lazyforeach.md#dataaddoperation), index: len, count: count }])通知,瀑布流就知道有新增数据可以继续加载,同时又不会重复处理已有数据。 101e41f4b71Sopenharmony_ci 102e41f4b71Sopenharmony_ci 103e41f4b71Sopenharmony_ci 104e41f4b71Sopenharmony_ci## 提前新增数据 105e41f4b71Sopenharmony_ci 106e41f4b71Sopenharmony_ci虽然在onReachEnd()触发时新增数据可以实现无限加载,但在滑动到底部时,会有明显的停顿加载新数据的过程。 107e41f4b71Sopenharmony_ci 108e41f4b71Sopenharmony_ci想要流畅的进行无限滑动,还需要调整下增加新数据的时机。比如可以在LazyForEach还剩若干个数据就迭代到结束的情况下提前增加一些新数据。 109e41f4b71Sopenharmony_ci 110e41f4b71Sopenharmony_ci```ts 111e41f4b71Sopenharmony_ci build() { 112e41f4b71Sopenharmony_ci Column({ space: 2 }) { 113e41f4b71Sopenharmony_ci WaterFlow() { 114e41f4b71Sopenharmony_ci LazyForEach(this.dataSource, (item: number) => { 115e41f4b71Sopenharmony_ci FlowItem() { 116e41f4b71Sopenharmony_ci Column() { 117e41f4b71Sopenharmony_ci Text("N" + item).fontSize(12).height('16') 118e41f4b71Sopenharmony_ci Image('res/waterFlowTest (' + item % 5 + ').jpg') 119e41f4b71Sopenharmony_ci .objectFit(ImageFit.Fill) 120e41f4b71Sopenharmony_ci .width('100%') 121e41f4b71Sopenharmony_ci .layoutWeight(1) 122e41f4b71Sopenharmony_ci } 123e41f4b71Sopenharmony_ci } 124e41f4b71Sopenharmony_ci .onAppear(() => { 125e41f4b71Sopenharmony_ci // 即将触底时提前增加数据 126e41f4b71Sopenharmony_ci if (item + 20 == this.dataSource.totalCount()) { 127e41f4b71Sopenharmony_ci this.dataSource.addNewItems(100) 128e41f4b71Sopenharmony_ci } 129e41f4b71Sopenharmony_ci }) 130e41f4b71Sopenharmony_ci .width('100%') 131e41f4b71Sopenharmony_ci .height(this.itemHeightArray[item % 100]) 132e41f4b71Sopenharmony_ci .backgroundColor(this.colors[item % 5]) 133e41f4b71Sopenharmony_ci }, (item: string) => item) 134e41f4b71Sopenharmony_ci } 135e41f4b71Sopenharmony_ci .columnsTemplate("1fr 1fr") 136e41f4b71Sopenharmony_ci .columnsGap(10) 137e41f4b71Sopenharmony_ci .rowsGap(5) 138e41f4b71Sopenharmony_ci .backgroundColor(0xFAEEE0) 139e41f4b71Sopenharmony_ci .width('100%') 140e41f4b71Sopenharmony_ci .height('80%') 141e41f4b71Sopenharmony_ci } 142e41f4b71Sopenharmony_ci } 143e41f4b71Sopenharmony_ci``` 144e41f4b71Sopenharmony_ci 145e41f4b71Sopenharmony_ci此处通过在FlowItem的onAppear中判断距离数据终点的数量,提前增加数据的方式实现了无停顿的无限滚动。 146e41f4b71Sopenharmony_ci 147e41f4b71Sopenharmony_ci 148e41f4b71Sopenharmony_ci 149e41f4b71Sopenharmony_ci## 组件复用 150e41f4b71Sopenharmony_ci 151e41f4b71Sopenharmony_ci现在,得到了一个无限滚动且没有显式等待加载的瀑布流,还能不能进一步优化性能呢? 152e41f4b71Sopenharmony_ci 153e41f4b71Sopenharmony_ci考虑到滑动场景存在FlowItem及其子组件的频繁创建和销毁,可以将FlowItem中的组件封装成自定义组件,并使用@Reusable装饰器修饰,使其具备组件复用能力,减少ArkUI框架内部反复创建销毁节点的开销。组件复用的详细介绍可以参考[组件复用最佳实践](component-recycle.md)。 154e41f4b71Sopenharmony_ci 155e41f4b71Sopenharmony_ci```ts 156e41f4b71Sopenharmony_ci build() { 157e41f4b71Sopenharmony_ci Column({ space: 2 }) { 158e41f4b71Sopenharmony_ci WaterFlow() { 159e41f4b71Sopenharmony_ci LazyForEach(this.dataSource, (item: number) => { 160e41f4b71Sopenharmony_ci FlowItem() { 161e41f4b71Sopenharmony_ci // 使用可复用自定义组件 162e41f4b71Sopenharmony_ci ResuableFlowItem({ item: item }) 163e41f4b71Sopenharmony_ci } 164e41f4b71Sopenharmony_ci .onAppear(() => { 165e41f4b71Sopenharmony_ci // 即将触底时提前增加数据 166e41f4b71Sopenharmony_ci if (item + 20 == this.dataSource.totalCount()) { 167e41f4b71Sopenharmony_ci this.dataSource.addNewItems(100) 168e41f4b71Sopenharmony_ci } 169e41f4b71Sopenharmony_ci }) 170e41f4b71Sopenharmony_ci .width('100%') 171e41f4b71Sopenharmony_ci .height(this.itemHeightArray[item % 100]) 172e41f4b71Sopenharmony_ci .backgroundColor(this.colors[item % 5]) 173e41f4b71Sopenharmony_ci }, (item: string) => item) 174e41f4b71Sopenharmony_ci } 175e41f4b71Sopenharmony_ci .columnsTemplate("1fr 1fr") 176e41f4b71Sopenharmony_ci .columnsGap(10) 177e41f4b71Sopenharmony_ci .rowsGap(5) 178e41f4b71Sopenharmony_ci .backgroundColor(0xFAEEE0) 179e41f4b71Sopenharmony_ci .width('100%') 180e41f4b71Sopenharmony_ci .height('80%') 181e41f4b71Sopenharmony_ci } 182e41f4b71Sopenharmony_ci } 183e41f4b71Sopenharmony_ci@Reusable 184e41f4b71Sopenharmony_ci@Component 185e41f4b71Sopenharmony_cistruct ResuableFlowItem { 186e41f4b71Sopenharmony_ci @State item: number = 0 187e41f4b71Sopenharmony_ci 188e41f4b71Sopenharmony_ci // 从复用缓存中加入到组件树之前调用,可在此处更新组件的状态变量以展示正确的内容 189e41f4b71Sopenharmony_ci aboutToReuse(params) { 190e41f4b71Sopenharmony_ci this.item = params.item; 191e41f4b71Sopenharmony_ci } 192e41f4b71Sopenharmony_ci 193e41f4b71Sopenharmony_ci build() { 194e41f4b71Sopenharmony_ci Column() { 195e41f4b71Sopenharmony_ci Text("N" + this.item).fontSize(12).height('16') 196e41f4b71Sopenharmony_ci Image('res/waterFlowTest (' + this.item % 5 + ').jpg') 197e41f4b71Sopenharmony_ci .objectFit(ImageFit.Fill) 198e41f4b71Sopenharmony_ci .width('100%') 199e41f4b71Sopenharmony_ci .layoutWeight(1) 200e41f4b71Sopenharmony_ci } 201e41f4b71Sopenharmony_ci } 202e41f4b71Sopenharmony_ci} 203e41f4b71Sopenharmony_ci 204e41f4b71Sopenharmony_ci``` 205e41f4b71Sopenharmony_ci 206e41f4b71Sopenharmony_ci## 总结 207e41f4b71Sopenharmony_ci 208e41f4b71Sopenharmony_ciWaterFlow配合LazyForEach渲染控制语法、提前加载数据和组件复用可以达到无限滚动场景性能最优效果。 209