又到了小技巧系列更新时间,今天咱们共享一个比较轻松的内容:Flutter 里的代码优化,优化的意图主要是为了进步功能和可维护性,放心,本篇咱们不讲深化的源码剖析,便是共享最最最基础的布局代码优化。
咱们先从一个简单的比如开始,信任大家关于 Flutter 的 UI 构建不会生疏,那么如下代码所示,日常开发进程中 A
和 B
这两种代码安排办法,你更常用的是哪一种?
A (函数办法) | B (Component Class 办法) |
---|---|
假如是从代码运行之后的 UI 作用来看,这两个办法运行之后的布局作用并不会有什么差异,而一般由于能够写更少代码和参数调用更便利等原因,咱们或许在编写页面的内部控件时,会更常常运用 A (函数办法)
这种写法,也有称之为 Helper Method 的叫法。
那运用函数办法构建 UI 有没有问题?答案肯定是没问题,可是某些场景下,比照运用 B (Component Class 办法)
,或许功能表现上相对没那么优秀。
举个比如,如下代码所示,在 renderA
函数里咱们经过点击按键修正 count
,在修正之后触发 UI 烘托时就需求用到 setState
,也便是咱们每点一下,当时整个页面便是触发一次 rebuild ,可是咱们仅仅想要改动当时 renderA
里的 count
文本而已。
这便是运用函数构建内部控件最常见的问题之一,由于子控件更新时是经过父容器的 setState
,所以每次子控件比方 renderA
发生改变时,就会触发整个 Widget 都呈现 rebuild ,这其实并不是特别符合咱们的预期。
科普一个众所周知的知识点,
setState
其实便是调用StatefulWidget
对应的StatefulElement
里的markNeedsBuild
办法,也便是对Element
(BuildContext
) 里的_dirty
标识为设置为true
,仅此而已, 然后等待下次烘托更新。
当然,你说像 renderA
这种写法会引起很严峻的功能问题吗?事实上并不会,由于众所周知 Flutter 里的 UI 构建是经过多个不同的树来完结的,而 Widget 并不是真实的控件,所以一般状况下 renderA
这种写法导致的 rebuild 是不会发生严峻的功能缺点。
可是,假如同级下你的 renderB
是如下所示这样的状况呢?尽管这段代码毫无意义,可是咱们在 renderA
点击改动 count
的时分,其实并没有改动 renderB
的用到的 status
参数,可是由于 renderA
里调用了 setState
,导致 renderB
每次都会进行重复进行浮点核算。
当然你能够说我写个变量进行缓存提前判断也能够处理,但这并不是这个比如的要害,那假如把上面这个比如变成 Component Class 的办法会有什么优点:
- A 在点击更新
count
时不会影响其他控件 - B 控件经过
didUpdateWidget
能够用更优雅的办法决议更新条件
这样看起来是不是更合理一些?另外 Component Class 的完成办法,也能在必定层度处理代码层级嵌套的问题,有时分完成一些 Component Class 的模版也能够成为 Flutter 里进步功率的工具,这个后边咱们会聊到。
当然运用 Component Class 在无形之中会需求你写更多的代码,同时控件之间的状况联动本钱也会有所进步,例如你需求在 B 控件关联 A 的 count
改变去改动高度,这时分或许就需求加入 InheritedWidget
或许 ValueNotifier
等办法来完成。
例如 Flutter 里 DefaultTabController
配合 TabBar
和 TabBarView
的完成便是一个很好的参阅。
Widget build(BuildContext context) {
return DefaultTabController(
length: myTabs.length,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
tabs: myTabs,
),
),
body: TabBarView(
children: myTabs.map((Tab tab) {
final String label = tab.text.toLowerCase();
return Center(
child: Text(
'This is the $label tab',
style: const TextStyle(fontSize: 36),
),
);
}).toList(),
),
),
);
}
所以到这儿咱们理解一个小技巧:在不偷闲的状况下,运用 Component Class 的办法完成子控件会比运用函数办法或许得到更好的功能和代码结构。
当然,运用 Component Class 完成的办法,在调试时也会比函数办法更便利,如下图所示,当运用函数办法布局时,你在 Flutter Inspector 里看到的 Widget Tree 和 Details Tree 是完全铺平的状况,也没办法定制调试参数。
可是当你 Component Class 安排布局的时分,你就能够经过 override debugFillProperties
办法来可视化一些参数状况,例如 ItemA
里能够把 count 添加到 debugFillProperties
里,这样在 Details Tree 里也能够直观看到目前的 count
状况信息。
所以这儿又有一个小技巧:经过
override debugFillProperties
,能够定制一些 Debug 时的可视化参数来协助咱们更好调试布局。
既然讲到利用 Component Class 安排布局,那就不得不聊一个典型的控件:AnimatedBuilder
。
AnimatedBuilder
能够是最常说到的一个功能优化的比如, 一般状况下在页面的子控件里运用动画,特别是循环动画的话,咱们都会建议运用前面介绍的 Component Class 办法,不然动画导致当时页面不停 rebuild 肯定会导致功能影响。
可是有时分我就不想用 Component Class 该怎么办?我便是想写在当时 Page 里,那就能够运用 AnimatedBuilder
,你只要把需求履行动画的部分放到 builder
办法里就好了。
由于 AnimatedBuilder
的内部会有一个 _AnimatedState
用于独立触发 setState
,从而履行外部 builder 办法履行动画作用。
相似 AnimatedBuilder
的模版完成,能够在必定程度上处理运用 Component Class 的痛点,当然,在运用 AnimatedBuilder
还是有一些需求留意, 比方 child 假如不需求跟从动画进行其他改变,一般是要放到 AnimatedBuilder
的 child
装备里,由于假如直接放在 builder
办法里,那就会呈现 child 也跟从动画重新 rebuild 的状况,可是假如是放到 child
装备项里,那便是调用了 child
的对象缓存。
不正确运用 | 正确运用 |
---|---|
假如关于这个缓存概念不理解,能够参阅 《MediaQuery 和 build 优化你不知道的秘密》 里的“缓存区域不随帧改变,以便得到最小化的构建”。
当然相似 AnimatedBuilder
的构建办法还要留意 context
问题,不要拿错 context
,这也是许多时分会犯的潜在错误,特别是在调用 of(context)
的时分。
那有的人或许到这儿会觉得,那你之前一直说 Widget 很轻,Widget 不是真正的控件,那 rebuild 多几回有什么问题?
一般状况下的确不会有太大问题,可是当你的控件有 Opacity
、ColorFilter
、 ShaderMash
或许 ClipRect
(Clip.antiAliasWithSaveLayer
)时,就或许会有较大的功能影响,由于他们都是或许会触发 saveLayer
的操作。
为什么
saveLayer
对功能影响很大?由于需求在 GPU 制作是需求增加额定的缓冲区域,粗俗点说便是需求做图层的保存和组成,这就会对 GPU 烘托时发生较大影响的耗时。
而这儿面最常遇到的应该便是 Opacity
带来的功能问题,由于它看起来是那么的轻便,可是从官方的介绍里,除非真的有必要,不然能够运用作用相似的完成去做场景代替,例如:
你需求对图片做透明度相关的动画是,那么运用 AnimatedOpacity
或 FadeInImage
代替 Opacity
会对功能更有协助。
AnimatedOpacity
和Opacity
不一样吗?某种程度上还真不大一样,Opacity
的内部是pushOpacity
的操作,而AnimatedOpacity
里尽管有OpacityLayer
,可是变化时是updateCompositedLayer
;而FadeInImage
会运用 GPU 的 fragment shader 去处理透明度的问题,所以功能也会更好一些。
或许在相似有颜色透明度的场景时,能够经过 Color.fromRGBO
来代替 Opacity
,除非你需求将不透明度应用到一大组较为复杂的 child 里,你才会需求运用 Opacity
。
/// no
Opacity(opacity: 0.5, child: Container(color: Colors.red))
/// yes
Container(color: Color.fromRGBO(255, 0, 0, 0.5))
另外还有 IntrinsicHeight
/ IntrinsicWidth
的场景,由于它们是能够经过 child 的内部宽高来调整 child 的巨细,可是这个计算布局的进程会比较费时,或许会到 O(N),尽管 Flutter 里针对这部分核算结果做了缓存,可是不妨碍它的耗时。
这么说或许有点笼统,举一个官方介绍过的比如,如下代码所示,当你在 ListView
里对 Row
的 children
进行 Align
排列时,你或许会发现它没有作用,由于此时经过 Border
能够看到,绿色和蓝色方框的父容器巨细一致。
可是在加上 IntrinsicHeight
之后, 由于经过 IntrinsicHeight
的测算之后再返回 size,Row
里的三个 Item 现在高度一致,,这时分 Align
就能够收效了,可是正如前面所说,这个操作性对功能来说相对贵重,尽管体系有缓存参数,可是假如呈现动画 rebuild ,也会对功能形成影响。
对这部分感兴趣的能够看 : 《带你了解不一样的 Flutter》
到这儿咱们就理解了 (函数办法) 和 (Component Class 办法)安排布局的不同之处,同时也知道了 Component Class 办法能够协助咱们更好地调试布局代码,也举例了一些 UI 布局里常见的耗时场景。
那本篇的小技巧到这儿就结束了,假如你还有什么感兴趣或许有疑问的,欢迎留言评论~