已开源!Flutter 流畅度优化组件 Keframe
The following article is from 进击的Flutter Author Nayuta
列表流畅度优化
这是一个通用的流畅度优化方案,通过分帧渲染优化由构建导致的卡顿,例如页面切换或者复杂列表快速滚动的场景。
优化前 | 优化后 |
---|---|
流畅:一帧耗时低于 18ms
良好:一帧耗时在 18ms-33ms 之间
轻微卡顿:一帧耗时在 33ms-67ms 之间
卡顿:一帧耗时大于 66.7ms
采用分帧优化后,卡顿次数从 平均 33.3 帧出现了一帧,降低到 200 帧中仅出现了一帧,峰值也从 188ms 降低到 90ms。卡顿现象大幅减轻,流畅帧占比显著提升,整体表现更流畅。下方是详细数据。
优化前 | 优化后 | |
---|---|---|
平均多少帧出现一帧卡顿 | 33.3 | 200 |
平均多少帧出现一帧轻微卡顿 | 8.6 | 66.7 |
最大耗时 | 188.0ms | 90.0ms |
平均耗时 | 27.0ms | 19.4ms |
流畅帧占比 | 40% | 64.5% |
页面切换流畅度提升
在打开一个页面或者 Tab 切换时,系统会渲染整个页面并结合动画完成页面切换。对于复杂页面,同样会出现卡顿掉帧。借助分帧组件,将页面的构建逐帧拆解,通过 DevTools 中的性能工具查看。切换过程的峰值由 112.5ms 降低到 30.2 ms,整体切换过程更加流畅。
如何使用?
项目依赖:
在 pubspec.yaml
中添加 keframe
依赖
dependencies:
keframe: version
组件仅区分非空安全与空安全版本
非空安全使用:1.0.1
空安全版本使用:2.0.0
github 地址:https://github.com/LianjiaTech/keframe
pub 查看:https://pub.dev/packages/keframe
快速上手:
FrameSeparateWidget
嵌套每一个部分。页面构建时会在第一帧渲染简单的占位,在后续四帧内分别渲染 A、B、C、D。对于列表,在每一个 item 中嵌套 FrameSeparateWidget
,并将 ListView
嵌套在 SizeCacheWidget
内即可。
构造函数说明
FrameSeparateWidget :分帧组件,将嵌套的 widget 单独一帧渲染
类型 | 参数名 | 是否必填 | 含义 |
---|---|---|---|
Key | key | 否 | |
int | index | 否 | 分帧组件 id,使用 SizeCacheWidget 的场景必传,SizeCacheWidget 中维护了 index 对应的 Size 信息 |
Widget | child | 是 | 实际需要渲染的 widget |
Widget | placeHolder | 否 | 占位 widget,尽量设置简单的占位,不传默认是 Container() |
SizeCacheWidget:缓存子节点中,分帧组件嵌套的实际 widget 的尺寸信息
类型 | 参数名 | 是否必填 | 含义 |
---|---|---|---|
Key | key | 否 | |
Widget | child | 是 | 子节点中如果包含分帧组件,则缓存实际的 widget 尺寸 |
int | estimateCount | 否 | 预估屏幕上子节点的数量,提高快速滚动时的响应速度 |
Example 示例说明:
卡顿的页面往往都是由多个复杂 widget 同时渲染导致。通过为复杂的 widget 嵌套分帧组件 FrameSeparateWidget
。渲染时,分帧组件会在第一帧同时渲染多个 palceHolder
,之后连续的多帧内依次渲染复杂子项,以此提升页面流畅度。
例如 example 中的优化前示例:
ListView.builder(
itemCount: childCount,
itemBuilder: (c, i) => CellWidget(
color: i % 2 == 0 ? Colors.red : Colors.blue,
index: i,
),
)
其中 CellWidget
高度为 60,内部嵌套了三个 TextField
的组件(整体构建耗时在 9ms 左右)。
优化仅需为每一个 item 嵌套分帧组件,并为其设置 placeHolder
(placeHolder 尽量简单,样式与实际 item 接近即可)。
在列表情况下,给 ListView 嵌套 SizeCacheWidget
,同时建议将预加载范围 cacheExtent
设置大一点,例如 500(该属性默认为 250),提升慢速滑动时候的体验。
例如:
SizeCacheWidget(
child: ListView.builder(
cacheExtent: 500,
itemCount: childCount,
itemBuilder: (c, i) => FrameSeparateWidget(
index: i,
placeHolder: Container(
color: i % 2 == 0 ? Colors.red : Colors.blue,
height: 60,
),
child: CellWidget(
color: i % 2 == 0 ? Colors.red : Colors.blue,
index: i,
),
),
),
),
下面是几种场景说明:
1、列表中实际 item 尺寸已知的情况
实际 item 高度已知的情况下(每个 item 高度为 60),将占位设置与实际 item 高度一致即可,查看 example 中 分帧优化 1。
FrameSeparateWidget(
index: i,
placeHolder: Container(
color: i % 2 == 0 ? Colors.red : Colors.blue,
height: 60,// 与实际 item 高度保持一致
),
child: CellWidget(
color: i % 2 == 0 ? Colors.red : Colors.blue,
index: i,
),
)
2、列表中实际 item 高度未知的情况
现实场景中,列表往往是根据数据下发展示,无法一开始预知 item 的尺寸。
例如,example 中 分帧优化 2, placeHolder
(高度40)与实际 item (高度60)尺寸不一致, 由于每一个 item 分在不同帧完成渲染,因此会出现列表「抖动」的情况。
这时可以给 placeholder 设置一个近似的高度。并且在将 ListView 嵌套在 SizeCacheWidget 中。对于已渲染过的 widget 会强制设置 palceHolder
的尺寸,同时将 cacheExtent
调大。这样在来回滑动过程中,已经渲染过的 item 将不会出现跳动情况。
例如,example 中 分帧优化 3
SizeCacheWidget(`
child: ListView.builder(
cacheExtent: 500,
itemCount: childCount,
itemBuilder: (c, i) => FrameSeparateWidget(
index: i,
placeHolder: Container(
color: i % 2 == 0 ? Colors.red : Colors.blue,
height: 40,
),
child: CellWidget(
color: i % 2 == 0 ? Colors.red : Colors.blue,
index: i,
),
),
),
),
3、预估一屏 item 的数量
如果能粗略估计一屏能展示的实际 item 的最大数量,例如 10。将 SizeCacheWidget 的 estimateCount
属性设置为 10*2。快速滚动场景构建响应更快,并且内存更稳定。例如,example 中的 分帧优化4
SizeCacheWidget(
estimateCount: 20,
child: ListView.builder(
此外,也可以给 item 嵌套透明度/位移等动画,优化视觉上的效果。
4、非列表场景
对于非列表场景,一般不存在流畅度问题,不过在初次进入的时候任然可能出现卡顿。同样的,可以将复杂的模块分到不同帧渲染,避免初次进入的卡顿。例如,我们将为优化例子中底部的操作区域嵌套分帧组件:
FrameSeparateWidget(
child: operateBar(),
index: -1,
分帧的成本
当然分帧方案也非十全十美,在我看来主要有两点成本:
1、额外的构建开销:整个构建过程的构建消耗由「n * widget消耗 」变成了「n *( widget + 占位)消耗 + 系统调度 n 帧消耗」。可以看出,额外的开销主要由占位的复杂度决定。如果占位只是简单的 Container,测试后发现整体构建耗时大概提升在 15 % 左右。这种额外开销对于当下的移动设备而言,成本几乎可以不计。
2、视觉上的变化:如同上面的演示,组件会将 item 分帧渲染,页面在视觉上出现占位变成实际 widget 的过程。但其实由于列表存在缓存区域(建议将缓存区调大),在高端机或正常滑动情况下用户并无感知。而在中低端设备上快速滑动能感觉到切换的过程,但比严重顿挫要好。
优化前后对比演示
注:gif 帧率只有20
优化前 | 优化后 |
---|---|
- EOF -
看完本文有收获?请分享给更多人
推荐关注「安卓开发精选」,提升安卓开发技术
点赞和在看就是最大的支持❤️