一、概述

瀑布流是应用开发中相当常见的开发场景。它通过容器自身的布局规则,将元素项目自上而下排列,在整体界面的呈现上,多列参差不齐、不停加载的形式使其内容看着像瀑布一样从上而下倾泻。借助其特点,瀑布流通常被用于展示图片资讯、购物商品、直播视频等多种形式的数据。当瀑布流上下滑动时,由于无限加载的特性,其能展示的数目非常多;大小不一的子元素,也带来了测量绘制的性能消耗。 针对瀑布流这种场景进行性能优化,可以在加快渲染速度、提升滑动帧率、降低内存占用等方面,增强应用的运行效率,进而提升用户的操作体验。 下面分别介绍优化瀑布流性能的主要方法。

二、懒加载

示例代码中已经使用了LazyForEach进行数据懒加载,WaterFlow布局时会根据可视区域按需创建FlowItem组件,并在FlowItem滑出可视区域时销毁以降低内存占用。

瀑布流的开发,也属于长列表加载的一种场景,其LazyForEach懒加载原理及性能分析可参考:《长列表加载性能优化-懒加载》

三、缓存数据项

LazyForEach懒加载可以通过设置cachedCount来指定缓存数量,在设置cachedCount后,除屏幕内显示的Item组件外,还会预先将屏幕可视区域外指定数量的数据缓存。这样当一个屏幕数据加载完成后,再次向下滑动时,会先加载上一次请求的数据,加载完成后,再加载本次请求的数据。

详细原理及性能分析参考:《长列表加载性能优化-缓存列表项》

四、组件复用

考虑到滑动场景存在FlowItem及其子组件的频繁创建和销毁,可以将FlowItem中的组件封装成自定义组件,并使用@Reusable装饰器修饰,使其具备组件复用能力,减少ArkUI框架内部反复创建销毁节点的开销。

详细原理及性能分析参考:《长列表加载性能优化-组件复用》

五、无限滑动

实际开发过程中,瀑布流布局里的数据一般不会被一次性加载完,而是每次加载一定量的数据。这样的话,就需要在应用滑动时加载数据。同时,如果在滑动过程中就加载了新的数据,那么配合懒加载与缓存列表项的话,缓存中的数据在未使用完就能得到补充,因此用户滑动中就不会感觉到数据加载,从而实现无限滑动的效果(具体效果参见实践总结章节)。同时,由于提前加载了数据,因此不会在某一时刻存在大量组件需要创建渲染的情况,减少了因同一时间创建大量组件而导致丢帧的情况。

对应场景下,需通过在一些事件中加载数据的方式来实现无限滑动。例如:瀑布流触底方法(onReachEnd)、瀑布流组件滑动index变化事件(onScrollIndex)。

  • onReachEnd加载数据时,应用会在数据源中的数据全部渲染展示完了之后再加载新数据。这样就会有明显的加载动画出现,用户需等待新数据加载完成后才能再接着浏览瀑布流。
  • onScrollIndex加载数据时,瀑布流组件滑动可视区index变化时执行任务,此时则需限制加载数据的条件(例:当前可视区最后一个组件的index + 20 === 数据源数量时),否则每个瀑布流组件滑动index变化时,都会加载数据。推荐在onScrollIndex里做操作,让瀑布流未到达底部时就开始加载数据,这样用户使用时就感知不到数据加载过程,从而提升体验。

相关流程如下:

瀑布流组件加载流程图

img

示例代码中使用的是onScrollIndex加载数据。

六、固定宽高

与长列表不同的,瀑布流布局中各个卡片的高度是不同的,这就对瀑布流布局绘制提出了新的挑战。瀑布流的卡片高度是由瀑布流卡片自适应瀑布流的宽度得到的,因此卡片的高度无法直接指定。这就会使卡片渲染以后得到的高度与占位符的高度不一致,从而造成卡片的闪烁效果。

固定宽高后,就可以在UI描述时指定组件的宽高数值。如果组件本身的宽高是固定的,理论上来讲,该组件在布局阶段不会参与measure阶段,其节点中保存了对应的大小信息,当瀑布流内容较多时,由于避免了组件整体的测算过程,性能会带来一定的提升。

另外,由于Image组件默认异步加载,提前设定FlowItem的高度,可以避免图片加载成功后高度变化触发的瀑布流布局刷新。

详细内容请参考:利用布局边界减少布局计算

瀑布流页面卡片宽高计算逻辑示意图

img

如上图所示,两列瀑布流卡片的宽度 = (屏幕宽度 - 2 * 组件外边距(margin) - 瀑布流组件内边距(gap))/ 2。

瀑布流卡片中图片的高度imageHeight = 图片原始高度 / 图片原始宽度 * 瀑布流卡片宽度。

瀑布流卡片中描述性的高度titleHeight根据标题长度不同需设置不同的高度,计算逻辑可参考示例代码

瀑布流卡片的高度 = imageHeight + titleHeight。

七、实践总结

本文以一个瀑布流案例,使用上述提到的部分优化手段,实现了页面的流畅滑动。本案例页面包含SearchBar + Swiper + WaterFlow + 底部导航栏等功能,顶部功能区由Swiper嵌套Grid组件实现,瀑布流使用WaterFlow组件实现,FlowItem组件内容由图文卡片、视频卡片、直播卡片构成,每个列表项中标题文本和用户信息结构是相同的,对相同UI结构进行了复用,避免了无用的层级嵌套。

整体效果图

img

下表为通过网络请求500条数据加载渲染,测试获得的数据(数据测试方式采用技术从左向右累加测试的):

性能指标ForEachLazyForEach缓存数据项组件复用固定宽高
首次渲染时间1346ms756ms752ms760ms761ms
丢帧率2.20%1.00%0.50%0.2%0.00%
独占内存485M122M136.6M106.8M103.3M

从测试结果可以看出,使用LazyForEach懒加载后,相关指标已经得到大幅度提高,尤其是内存和首次渲染时间。其他优化方式可以进一步减少滑动过程的中丢帧率,优化交互体验。缓存数据项,因为缓存加载的数据未在首屏展示,所以对首次渲染时间有影响,但是因为需要加载多余的数据,所以内存会比没使用缓存数据项的多。因为UI组件的复用,所以内存比没有使用组件复用少很多。

最后,对于瀑布流性能优化方式,整体与长列表优化类似,都可以使用懒加载、缓存列表项、动态预加载、组件复用、布局优化等方式优化性能。固定宽高作为瀑布流特有的优化性能手段能够进一步提升瀑布流的性能,同时可以避免新加载元素瞬间刷新带来的"闪烁"问题。在滑动过程中加载数据,可以避免同一时间创建大量组件而导致丢帧的情况。相应技术总结见下表:

技术名称适用场景参考文章
懒加载使适用于瀑布流需要一次性加载并渲染大量数据而造成性能瓶颈问题的场景。长列表加载性能优化使用懒加载优化性能数据懒加载
缓存列表项适用于加载列表项数据请求比较耗时的场景。比如,瀑布流列表中含有短视频、高清图片等数据量比较大的资源。长列表加载性能优化
组件复用适用于瀑布流中存在大量结构相同的组件频繁创建与销毁的场景而造成性能瓶颈问题的场景。组件复用最佳实践
固定宽高适用于瀑布流页面组件高度不一的场景。利用布局边界减少布局计算
布局优化错误的布局方式可能会导致组件树和嵌套层数过多,在创建和布局绘制阶段产生较大的性能开销,所以可以通过布局优化提升性能。合理使用布局
状态管理在ArkUI的开发过程中,如果没有选择合适的装饰器或合理的控制状态更新范围,会导致非必要的UI视图刷新,造成性能浪费。状态管理最佳实践

八、示例代码

九、最佳实践

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐