概述
本示例仿照小红书界面实现无限瀑布流,并对瀑布流进行性能上的优化
ps:使用的是死数据
瀑布流优化方向
- 实现懒加载:当加载大量数据的时候,对列表加载进行优化
- 缓存数据项:合理缓存数据可以提升性能
- 组件复用:实现组件复用机制可以减少内存优化性能
- 无限滑动:合理使用加载数据触发条件提升用户体验
- 固定宽高:可以避免整个数据源的刷新,减少整个瀑布流的频闪
实现效果
实现基础功能和懒加载
懒加载的优点是当插入大量的数据时,使用Foreach会将所有的数据加载完成后再显现,而使用懒加载则只加载部分用到的,会更快的展现内容,提升用户体验。
结构是层叠布局+一个瀑布流,然后使用懒加载循环渲染
在实现基础功能需要注意的是,当你去实现点赞功能让点赞数加1的时候,并没有实时渲染,而是组件再销毁后再创造才会渲染
解决方法是通过@Oberserve来观测内部变化进行实时的渲染
实现缓存数据项
WaterFlow(){
//优化1:懒加载
LazyForEach(this.dataSource,(item:waterFlowItem)=>{
FlowItem(){
//...
}
//...
//屏幕显示数目n/2为理论上性能最佳,具体根据实际情况
.cachedCount(3)
}
})
}
}
懒加载自带.cachedCount()属性设置缓存的数据量,经测试,在屏幕显示数目n/2为理论上性能最佳,具体根据实际情况,如果网络较弱可以多缓存一些,如果需要加载大图或者视频可以少缓存一些。
组件复用
HarmonyOS应用框架提供了组件复用能力,可复用组件从组件树上移除时,会进入到一个回收缓存区。后续创建新组件节点时,会复用缓存区中的节点,节约组件重新创建的时间。尤其在列表等场景下,其自定义子组件具有相同的组件布局结构,列表更新时仅有状态变量等数据差异。通过组件复用可以提高列表页面的加载速度和响应速度。
@Reusable
@Component
export struct WaterFlowItem{
//...
aboutToReuse(params: Record<string, waterFlowItem>): void {
console.info(`该组件复用,title:${this.item.title}`)
}
build() {
//...
}
}
无限滑动
.onScrollIndex((first:number,last:number)=>{
console.log('这是最后一个:'+last)
if(last+15>=this.dataSource.totalCount()){
this.dataSource.onDatasetChange(last)
}
})
瀑布流提供滑动API,一般来说有两种,一种是检测屏幕触到底部的时候加载数据,而本示例要实现无限瀑布流,检测在屏幕显示最后一行的卡片的index+15大于等于所有数据量总和时开始提前加载三十条数据,(说白点就是当快刷到底部的时候就加载数据,刷到底部的时候就已经加载完了可以继续往下刷)这样就会让用户察觉不到数据的加载不会卡顿。
这里有个注意点就是,懒加载去加载批量数据的时候,建议使onDatasetChange()这个api,用于加载批量数据,如果一条条插后使用reload会造成整个列表的频闪
固定卡片宽高
固定宽高可以让布局边界范围变小从而提升性能
卡片宽度计算
getWaterFlowItemWidth(){
let windowWidth:number=px2vp(display.getDefaultDisplaySync().width)
this.waterFlowItemWidth=(windowWidth-this.marginLeft*2-this.columnGap)/2
}
首先要获取屏幕宽度:
let windowWidth:number=px2vp(display.getDefaultDisplaySync().width)
注意这个api返回的单位是px,而我们平时默认的单位是vp,需要px2vp()来转换
两列瀑布流卡片的宽度 = (屏幕宽度 - 2 * 组件外边距(margin) - 瀑布流组件内边距(gap))/ 2。
卡片高度计算:
在这里我遇到的问题是计算出高度后每次图片生成的时候都会有两次刷新,当我固定好宽度后,一开始是自适应的图片,然后是我计算好的高度,会突然变大,目前没找到原因
但是如果不设高度的话,图片会根据宽度自适应高度,这块的性能优化未能实现。
实现中遇到的问题
- 动态计算宽高时,每次生成的时候才计算,导致每次生成组件的时候才开始生成
- 动态预加载属于网络请求中弱网状态下的优化
- 实现点赞功能时需要额外使用@Oberserve通过状态管理渲染,不然它不会实时刷新,而是等组件销毁后再创建的时候才刷新
总结
懒加载 | 使适用于瀑布流需要一次性加载并渲染大量数据而造成性能瓶颈问题的场景。 | 长列表加载性能优化、 使用懒加载优化性能、 数据懒加载 |
缓存列表项 | 适用于加载列表项数据请求比较耗时的场景。比如,瀑布流列表中含有短视频、高清图片等数据量比较大的资源。 | 长列表加载性能优化 |
组件复用 | 适用于瀑布流中存在大量结构相同的组件频繁创建与销毁的场景而造成性能瓶颈问题的场景。 | 组件复用最佳实践 |
固定宽高 | 适用于瀑布流页面组件高度不一的场景。 | 利用布局边界减少布局计算 |
布局优化 | 错误的布局方式可能会导致组件树和嵌套层数过多,在创建和布局绘制阶段产生较大的性能开销,所以可以通过布局优化提升性能。 | 合理使用布局 |
状态管理 | 在ArkUI的开发过程中,如果没有选择合适的装饰器或合理的控制状态更新范围,会导致非必要的UI视图刷新,造成性能浪费。 | 状态管理最佳实践 |
完整代码
//ets/pages/Index
import {
myDataSource,waterFlowItem} from '../model/WaterFlowDataSourse'
import {
WaterFlowItem} from '../View/WaterFlowItem'
import {
display } from '@kit.ArkUI'
@Entry
@Component
struct myWaterFlow{
@State dataSource:myDataSource=new myDataSource()
@State num:number=100
@State waterFlowItemWidth:number=0
@State marginLeft:number=6
@State columnGap:number=5
@State imgWidth:number=0
@State imgHeight:number=0
getWaterFlowItemWidth(){
let windowWidth:number=px2vp(display.getDefaultDisplaySync().width)
this.waterFlowItemWidth=(windowWidth-this.marginLeft*2-this.columnGap)/2
}
pushItem(num:number){
for (let i=0;i<=num;i++){
let x:number=i%6+1
let obj1:waterFlowItem=new waterFlowItem(`app.media.fengjing_${
x}`,'id:'+i+'这是一段文本',0,false)
let obj2:waterFlowItem=new waterFlowItem(`app.media.fengjing_${
x}`,'id:'