文章参考:docs.flutter.cn/perf/best-p…
-
控制 build() 方法的耗时
-
避免在
build()
方法中进行重复且耗时的工作,因为当父 widget 重建时,子 Wdiget 的build()
方法会被频繁地调用。- 网络请求 fetchData()
- 频繁解析JSON jsonDecode(hugeJsonString);
- 复杂计算,频繁创建大对象和集合 List.generate(10000, (index) => index);
-
避免在一个超长的
build()
方法中返回一个过于庞大的 widget。把它们分拆成不同的 widget,并进行封装-
局部刷新: 当在
State
对象上调用setState()
时,所有后代 widget 都将重建。因此,将setState()
的调用转移到其 UI 实际需要更改的 widget 子树部分。 如果改变的部分仅包含在 widget 树的一小部分中,请避免在 widget 树的更高层级中调用setState()
。- 将状态推送到叶子节点。 例如,如果你的页面上有一个滴答作响的时钟,与其将状态放在页面顶部,并在时钟每次滴答作响时重建整个页面,不如创建一个专门的时钟小部件,使其仅更新自身。
-
Child缓存:当重新遇到与前一帧相同的子 widget 实例时,将停止遍历。这种技术在框架内部大量使用,用于优化动画不影响子树的动画。请参阅
TransitionBuilder
模式,理解把Child缓存起来。- 如果子树没有变化,则缓存代表该子树的 Widget,并在每次可以使用时重用它。 为此,请将 Widget 分配给
final
状态变量,并在 build 方法中重用它。重用 Widget 比创建一个新的(但配置相同的)Widget 效率更高。另一种缓存策略是将 Widget 的可变部分提取到 接受 child 参数的StatefulWidget中。
- 如果子树没有变化,则缓存代表该子树的 Widget,并在每次可以使用时重用它。 为此,请将 Widget 分配给
-
尽可能使用
const
小部件。(这相当于缓存小部件并重复使用。)这将让 Flutter 的 widget 重建时间大幅缩短。要自动提醒使用const
。 -
在构建可复用的 UI 代码时,最好使用
StatelessWidget
而不是函数。 -
最小化重建有状态小部件
- 尽量减少 build 方法及其创建的任何 Widget 所传递创建的节点数量。 理想情况下,有状态 Widget 只会创建一个 Widget,并且该 Widget 应该是一个RenderObjectWidget。(当然,这并不总是可行的,但 Widget 越接近理想状态,效率就越高。)
- 避免更改任何已创建子树的深度,或更改子树中任何控件的类型。 例如,与其返回子控件本身或包装在IgnorePointer中的子控件,不如始终将子控件包装在IgnorePointer中并控制IgnorePointer.ignoring 属性。这是因为更改子树的深度需要重建、布局和绘制整个子树,而仅更改属性则对渲染树的更改尽可能少(例如,对于IgnorePointer,根本不需要布局或重绘)。
- 如果由于某种原因必须更改深度,请考虑将子树的公共部分包装到具有 GlobalKey 的 Widget 中,该 GlobalKey 在有状态 Widget 的整个生命周期内保持一致。
-
-
-
谨慎使用 saveLayer()
- 为什么代价会大?触发了离屏渲染
- 为什么时候需要?在运行时,如果你需要动态地显示各种形状效果(例如),每个形状都有一定地透明度,可能(或可能不)重叠,那么你几乎必须使用
saveLayer()
。 - 尽量减少 saveLayer 的调用
-
尽量减少使用不透明度和裁剪
-
使用透明的颜色比透明的
Opacity
更快:- 例如,
Container(color: Color.fromRGBO(255, 0, 0, 0.5))
比快得多Opacity(opacity: 0.5, child: Container(color: Colors.red))
。
- 例如,
-
Clipping 不会调用
saveLayer()
(除非明确使用Clip.antiAliasWithSaveLayer
),因此这些操作没有Opacity
那么耗时,但仍然很耗时,所以请谨慎使用。 -
能不用
Opacity
widget,就尽量不要用。有关将透明度直接应用于图像的示例,请查看 Transparent image,这比使用Opacity
widget 更快。 -
要在图像中实现淡入淡出,请考虑使用
FadeInImage
widget,该 widget 使用 GPU 的片段着色器应用渐变不透明度。 -
要创建带圆角的矩形,而不是裁剪矩形来达到圆角的效果,请考虑使用很多 widget 都提供的
borderRadius
属性。 -
陷进:
- 避免使用
Opacity
widget,尤其是在动画中避免使用。可以使用AnimatedOpacity
或FadeInImage
代替该操作。 - 使用
AnimatedBuilder
时,请避免在不依赖于动画的 widget 的构造方法中构建 widget 树,不然,动画的每次变动都会重建这个 widget 树,应当将这部分子树作为 child 传递给AnimatedBuilder
,从而只构建一次。更多内容 - 避免在动画中裁剪,尽可能的在动画开始之前预先裁剪图像。
- 避免在
Widget
对象上重写operator ==
。虽然这看起来有助于避免不必要的重建,但在实践中,它实际上损害了性能,因为这是 O(N²) 的行为。比较 widget 的属性可能比重建 widget 更加有效,也能更少改变 widget 的配置。即使在这种情况下,最好还要缓存 widget,因为哪怕有一次对operator ==
进行覆盖也会导致全面性能的下降,编译器也会因此不再认为调用总是静态的
- 避免使用
-
-
谨慎使用网格列表和列表
- 懒加载:如果大多数
children widget
在屏幕上不可见,请避免使用返回具体列表的构造函数(例如Column()
或ListView()
),以避免构建成本。使用ListView.build() - 不要使用shrinkWrap:当列表数据超过100条,就会卡顿。内部组件列表从 ListView 改为 SliverList,用 SliverChildBuilderDelegate 委托
- 懒加载:如果大多数
-
避免内部传递
-
不要先算子widget的大小,再去计算父组件的高度。例如轮询计算高度
- 例如,你想要所有单元格都具有或大或小的效果 (或类似需要轮询所有单元格的计算) 时,就会发生内部传递。
- 例如,考虑一个大型的
卡片
网格列表时。一个网格列表应该有统一大小的单元格,所以布局代码执行了一次传递,从网格列表的根部开始(在 widget 树中),要求网格列表中的 每个 卡片(不仅仅是可见的卡片)来返回 内部 尺寸—假设没有任何限制,widget 更喜欢这样的尺寸。有了这些信息,底层框架就确定了一个统一的单元格尺寸,并再次重新访问所有的网格单元,告诉每个卡片应该使用什么尺寸。
-
-
隔离重绘区域:自定义的绘制使用RepaintBoundary包裹