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