{"id":19528946,"url":"https://github.com/lianjiatech/keframe","last_synced_at":"2025-05-16T06:07:59.501Z","repository":{"id":37751141,"uuid":"381559560","full_name":"LianjiaTech/keframe","owner":"LianjiaTech","description":"Components that optimize Flutter fluency.（Flutter 流畅度优化的通用方案，轻松解决卡顿问题）","archived":false,"fork":false,"pushed_at":"2023-04-14T06:37:06.000Z","size":59467,"stargazers_count":966,"open_issues_count":9,"forks_count":109,"subscribers_count":32,"default_branch":"master","last_synced_at":"2025-05-16T06:07:48.821Z","etag":null,"topics":["flutter","optimization"],"latest_commit_sha":null,"homepage":"","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/LianjiaTech.png","metadata":{"files":{"readme":"README-ZH.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-06-30T03:05:39.000Z","updated_at":"2025-05-14T05:57:14.000Z","dependencies_parsed_at":"2024-06-18T17:12:48.844Z","dependency_job_id":null,"html_url":"https://github.com/LianjiaTech/keframe","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LianjiaTech%2Fkeframe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LianjiaTech%2Fkeframe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LianjiaTech%2Fkeframe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/LianjiaTech%2Fkeframe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/LianjiaTech","download_url":"https://codeload.github.com/LianjiaTech/keframe/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254478193,"owners_count":22077676,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["flutter","optimization"],"created_at":"2024-11-11T01:21:01.988Z","updated_at":"2025-05-16T06:07:54.469Z","avatar_url":"https://github.com/LianjiaTech.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Flutter 流畅度优化组件 Keframe\n\n![image](https://user-images.githubusercontent.com/40540394/123200939-50503d80-d4e4-11eb-8db0-afcc3c32423a.png)\n\n\n   + [列表流畅度优化](#列表流畅度优化)\n   + [页面切换流畅度提升：](#页面切换流畅度提升)\n   + [如何使用？](#如何使用)\n     - [项目依赖：](#项目依赖)\n     - [快速上手](#快速上手)\n   + [构造函数说明：](#构造函数说明)\n   + [Example 示例说明：](#Example示例说明)\n     - [1、列表中实际 item 尺寸已知的情况](#1列表中实际-item-尺寸已知的情况)\n     - [2、列表中实际 item 高度未知的情况](#2列表中实际-item-高度未知的情况)\n     - [3、预估一屏 item 的数量](#3预估一屏-item-的数量)\n     - [4、非列表场景](#4非列表场景)\n   + [分帧的成本](#分帧的成本)\n   + [优化前后对比演示](#优化前后对比演示)\n   + [相关原理分析：](#相关原理分析)\n   \nLanguage: [English](README.md) | 中文简体\n\n[![null-safe](https://img.shields.io/badge/nullsafe-2.0.6-brightgreen)](https://pub.dev/packages/keframe)\n[![null-safe](https://img.shields.io/badge/normal-1.0.3-brightgreen)](https://pub.dev/packages/keframe)\n[![GitHub stars](https://img.shields.io/github/stars/LianjiaTech/keframe)](https://github.com/LianjiaTech/keframe/stargazers)\n[![GitHub license](https://img.shields.io/github/license/LianjiaTech/keframe)](https://github.com/LianjiaTech/keframe/blob/master/LICENSE)\n\n\n### 列表流畅度优化\n\n这是一个通用的流畅度优化方案，通过分帧渲染优化由构建导致的卡顿，例如页面切换或者复杂列表快速滚动的场景。\n\n代码中 [example](app-profile.apk)(可以直接下载运行) 运行在 VIVO X23（骁龙 660），相同操作下优化前后 200 帧采集数据指标对比（gif 在文章最后）：\n\n| 优化前 |  优化后 |\n| --- | --- |\n| \u003cimg src=\"https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4233166557ec4b4da0133fe8a9b17325~tplv-k3u1fbpfcp-watermark.image\" alt=\"优化前\" style=\"zoom:50%;\" /\u003e | \u003cimg src=\"https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6db4a156eaa14bb8b6acb27549788b63~tplv-k3u1fbpfcp-watermark.image\" alt=\"优化后\" style=\"zoom:50%;\" /\u003e |\n\n监控工具来自：[fps_monitor](https://github.com/Nayuta403/fps_monitor)，指标详细信息：[页面流畅度不再是谜！调试神器开箱即用，Flutter FPS检测工具](https://juejin.cn/post/6947911434424549384)\n\n- 流畅：一帧耗时低于 18ms\n- 良好：一帧耗时在 18ms-33ms 之间\n- 轻微卡顿：一帧耗时在 33ms-67ms 之间\n- 卡顿：一帧耗时大于 66.7ms\n\n采用分帧优化后，卡顿次数从 **平均 33.3 帧出现了一帧**，降低到 **200 帧中仅出现了一帧**，峰值也**从 188ms 降低到 90ms**。卡顿现象大幅减轻，流畅帧占比显著提升，整体表现更流畅。下方是详细数据。\n\n|                            | 优化前  | 优化后 |\n| -------------------------- | ------- | ------ |\n| 平均多少帧出现一帧卡顿     | 33.3    | 200    |\n| 平均多少帧出现一帧轻微卡顿 | 8.6     | 66.7   |\n| 最大耗时                   | 188.0ms | 90.0ms |\n| 平均耗时                   | 27.0ms  | 19.4ms |\n| 流畅帧占比                 | 40%     | 64.5%  |\n\n****\n\n### 页面切换流畅度提升：\n\n在打开一个页面或者 Tab 切换时，系统会渲染整个页面并结合动画完成页面切换。对于复杂页面，同样会出现卡顿掉帧。借助分帧组件，将页面的构建逐帧拆解，通过 DevTools 中的性能工具查看。切换时一帧的峰值由 **112.5ms 降低到 30.2 ms**，整体切换过程更加流畅。\n\n| ![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c0ce341f0a2d4fceb0ad123fd4834ce2~tplv-k3u1fbpfcp-watermark.image) | ![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0c571a755ac84f39b52d57a13856a243~tplv-k3u1fbpfcp-watermark.image) |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n\n三位开发者的实际使用效果 [Keframe 优化实践合集](https://juejin.cn/post/6984606303641403406)\n\n***\n\n### 如何使用？\n\n#### 项目依赖：\n\n在 `pubspec.yaml` 中添加 `keframe` 依赖 \n\n```yaml\ndependencies: \n  keframe: version\n```\n\n组件仅区分非空安全与空安全版本\n\n非空安全使用： `1.0.2`\n\n空安全版本使用： `2.0.6`\n\n#### 快速上手\n\n如下图所示\n\n![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/83d16f3b2a3e45b79fc73d7a52774696~tplv-k3u1fbpfcp-watermark.image)\n\n假如现在页面由 A、B、C、D 四部分组成，每部分耗时 10ms，在页面时构建为 40ms。使用分帧组件  `FrameSeparateWidget` 嵌套每一个部分。页面构建时会在第一帧渲染简单的占位，在后续四帧内分别渲染 A、B、C、D。\n\n对于列表，在每一个 item 中嵌套 `FrameSeparateWidget`，并将 `ListView` 嵌套在 `SizeCacheWidget` 内。\n\n![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ffecd49bf9ba4379984a22ef79663104~tplv-k3u1fbpfcp-watermark.image)\n\n***\n\n### 构造函数说明：\n\nFrameSeparateWidget ：分帧组件，将嵌套的 widget 单独一帧渲染\n\n| 类型   | 参数名      | 是否必填 | 含义                                                         |\n| ------ | ----------- | -------- | ------------------------------------------------------------ |\n| Key    | key         | 否       |                                                              |\n| int    | index       | 否       | 分帧组件 id，使用 SizeCacheWidget 的场景必传，SizeCacheWidget 中维护了 index 对应的 Size 信息 |\n| Widget | child       | 是       | 实际需要渲染的 widget                                        |\n| Widget | placeHolder | 否       | 占位 widget，尽量设置简单的占位，不传默认是 Container()      |\n\nSizeCacheWidget：缓存子节点中，分帧组件嵌套的**实际 widget 的尺寸信息**\n\n| 类型   | 参数名        | 是否必填 | 含义                                                   |\n| ------ | ------------- | -------- | ------------------------------------------------------ |\n| Key    | key           | 否       |                                                        |\n| Widget | child         | 是       | 子节点中如果包含分帧组件，则缓存**实际的 widget 尺寸** |\n| int    | estimateCount | 否       | 预估屏幕上子节点的数量，提高快速滚动时的响应速度       |\n\n***\n\n### Example 示例说明：\n\n卡顿的页面往往都是由多个复杂 widget 同时渲染导致。通过为复杂的 widget 嵌套分帧组件 `FrameSeparateWidget`。渲染时，分帧组件会在第一帧同时渲染多个 `palceHolder`，之后连续的多帧内依次渲染复杂子项，以此提升页面流畅度。\n\n例如 example 中的优化前示例：\n\n```dart\nListView.builder(\n              itemCount: childCount,\n              itemBuilder: (c, i) =\u003e CellWidget(\n                color: i % 2 == 0 ? Colors.red : Colors.blue,\n                index: i,\n              ),\n            )\n```\n\n其中 `CellWidget` 高度为 60，内部嵌套了三个 `TextField` 的组件（整体构建耗时在 9ms 左右）。\n\n优化仅需为每一个 item 嵌套分帧组件，并为其设置 `placeHolder`（placeHolder 尽量简单，样式与实际 item 接近即可）。\n\n在列表情况下，给 ListView 嵌套 `SizeCacheWidget`，同时建议将预加载范围 `cacheExtent` 设置大一点，例如 500（该属性默认为 250），提升慢速滑动时候的体验。\n\n例如：\n\n```dart\nSizeCacheWidget(\n              child: ListView.builder(\n                cacheExtent: 500,\n                itemCount: childCount,\n                itemBuilder: (c, i) =\u003e FrameSeparateWidget(\n                  index: i,\n                  placeHolder: Container(\n                    color: i % 2 == 0 ? Colors.red : Colors.blue,\n                    height: 60,\n                  ),\n                  child: CellWidget(\n                    color: i % 2 == 0 ? Colors.red : Colors.blue,\n                    index: i,\n                  ),\n                ),\n              ),\n            ),\n```\n\n下面是几种场景说明：\n\n#### 1、列表中实际 item 尺寸已知的情况\n\n实际 item 高度已知的情况下（每个 item 高度为 60），将占位设置与实际 item 高度一致即可，查看 example 中 分帧优化 1。\n\n```dart\nFrameSeparateWidget(\n                index: i,\n                placeHolder: Container(\n                  color: i % 2 == 0 ? Colors.red : Colors.blue,\n                  height: 60,// 与实际 item 高度保持一致\n                ),\n                child: CellWidget(\n                  color: i % 2 == 0 ? Colors.red : Colors.blue,\n                  index: i,\n                ),\n              )\n```\n\n#### 2、列表中实际 item 高度未知的情况\n\n现实场景中，列表往往是根据数据下发展示，无法一开始预知 item 的尺寸。\n\n例如，example 中 分帧优化 2， `placeHolder` （高度40）与实际 item （高度60）尺寸不一致，\n由于每一个 item 分在不同帧完成渲染，因此会出现列表「抖动」的情况。\n\n这时可以给 placeholder 设置一个近似的高度。并且在将 ListView 嵌套在 SizeCacheWidget 中。对于已渲染过的 widget 会强制设置 `palceHolder` 的尺寸，同时将`cacheExtent`调大。这样在来回滑动过程中，已经渲染过的 item 将不会出现跳动情况。\n\n例如，example 中 分帧优化 3\n\n```dart\nSizeCacheWidget(\n              child: ListView.builder(\n                cacheExtent: 500,\n                itemCount: childCount,\n                itemBuilder: (c, i) =\u003e FrameSeparateWidget(\n                  index: i,\n                  placeHolder: Container(\n                    color: i % 2 == 0 ? Colors.red : Colors.blue,\n                    height: 40,\n                  ),\n                  child: CellWidget(\n                    color: i % 2 == 0 ? Colors.red : Colors.blue,\n                    index: i,\n                  ),\n                ),\n              ),\n            ),\n```\n\n实际效果如下：\n\n\u003cimg src=\"https://user-images.githubusercontent.com/40540394/123904966-fa780b80-d9a3-11eb-9afe-c1023e265a75.gif\" alt=\"Screenrecording_20210611_194905.gif\" style=\"zoom:50%;\" /\u003e\n\n\n\n#### 3、预估一屏 item 的数量\n\n如果能粗略估计一屏能展示的实际 item 的最大数量，例如 10。将 SizeCacheWidget 的 `estimateCount ` 属性设置为 10*2。快速滚动场景构建响应更快，并且内存更稳定。例如，example 中的 分帧优化4\n\n```dart\n...\nSizeCacheWidget(\n              estimateCount: 20,\n              child: ListView.builder(\n...\n```\n\n此外，也可以给 item 嵌套透明度/位移等动画，优化视觉上的效果。\n\n效果如下图：\n\n| ![Screenrecording_20210315_133310.gif](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bb7d1361ae7842df954bb1c559e2ec54~tplv-k3u1fbpfcp-watermark.image) | ![Screenrecording_20210315_133848.gif](https://user-images.githubusercontent.com/40540394/123905372-c9e4a180-d9a4-11eb-94d0-4190710828f5.gif) |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n\n#### 4、非列表场景\n\n对于非列表场景，一般不存在流畅度问题，不过在初次进入的时候任然可能出现卡顿。同样的，可以将复杂的模块分到不同帧渲染，避免初次进入的卡顿。例如，我们将为优化例子中底部的操作区域嵌套分帧组件：\n\n```dart\nFrameSeparateWidget(\n    child: operateBar(),\n    index: -1,\n)\n```\n\n****\n\n\n\n### 分帧的成本\n\n当然分帧方案也非十全十美，在我看来主要有两点成本：\n\n1、额外的构建开销：整个构建过程的构建消耗由「n * widget消耗 」变成了「n *（ widget + 占位）消耗 + 系统调度 n 帧消耗」。可以看出，额外的开销主要由占位的复杂度决定。如果占位只是简单的 Container，测试后发现整体构建耗时大概提升在 15 % 左右。这种额外开销对于当下的移动设备而言，成本几乎可以不计。\n\n2、视觉上的变化：如同上面的演示，组件会将 item 分帧渲染，页面在视觉上出现占位变成实际 widget 的过程。但其实由于列表存在缓存区域（建议将缓存区调大），在高端机或正常滑动情况下用户并无感知。而在中低端设备上快速滑动能感觉到切换的过程，但比严重顿挫要好。\n\n***\n### 如果项目对你有所帮助，并且你愿意分享。可以发送你优化前后的对比到我的邮箱 762579473@qq.com 非常感谢！\n### 如果有任何问题，欢迎与我联系；如果对你有所启发，不要忘了 start ✨✨✨✨ Thanks~\n### 优化前后对比演示 \n\n注：gif 帧率只有20\n\n| 优化前 |  优化后 |\n| --- | --- |\n| ![优化前](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2f20f593cc144b72a1df4bdae57a165c~tplv-k3u1fbpfcp-watermark.image) | ![优化后](https://user-images.githubusercontent.com/40540394/123905087-3c08b680-d9a4-11eb-9485-4cdf21c38ad2.gif) |\n\n***\n\n### 相关原理分析：\n\n[ListView流畅度翻倍！！Flutter卡顿分析和通用优化方案](https://juejin.cn/post/6940134891606507534)\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flianjiatech%2Fkeframe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flianjiatech%2Fkeframe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flianjiatech%2Fkeframe/lists"}