信任关于 Flutter 开发的大家来说, ListView 的 shrinkWrap 装备都不会生疏,如下图所示,每当遇到类似的 unbounded error 的时分,总会有第一反应便是给 ListView 加上 shrinkWrap: true 就能够解决问题,那为什么现在会说 shrinkWrap 即将被“扔掉”呢?
其实说彻底“扔掉”也不大严谨,从现在官方的规划来看, shrinkWrap 装备将从滑动控件里弃用,由于团队觉得现阶段的开发人员大多数时分不知道它的实际意义,仅仅单纯运用它解决问题,在运用过程中容易呈现过错的功能损耗而不自知。
当然,这个提议并不是说彻底废除 shrinkWrap 的支持,而且类似经过全新的 Widget 来代替,用更形象的命名,例如 NonLazyListView 等。
现在这个提议的等级是 P1 ,所以假如不意外的话,它的推动会很快。
那么 shrinkWrap 为什么会带来功能问题?它常用在什么场景?为什么会需要被提高到 P1 来进行调整?
首要咱们需要简略了解 Flutter 滑动列表的完结和 shrinkWrap 的作用,在《带你了解 Flutter 中的滑动列表完结》里咱们介绍过,Flutter 里的滑动列表是由 Viewport 、Scrollable 和相应的 Sliver 三部分组成。
以 ListView 为例,如下图所示是 ListView 滑动过程的改变,其中:
- 绿色的
Viewport便是咱们看到的列表窗口巨细; - 紫色部分便是处理手势的
Scrollable,让黄色部分SliverList在Viewport里发生滑动; - 黄色的部分便是
SliverList, 当咱们滑动时其实便是它在Viewport里的方位发生了改变;
所以 ListView 之所以能够“无限”滑动,便是由于首要有一个固定巨细「窗口」, 只要在进入和接近「窗口」的 Item 才会被布局烘托,然后保证了列表的功能。
可是这也带来了一个问题,如下图 1 的代码所示,它就由于 Column 的特性,没办法直接核算得到 Viewport 的巨细,所以会抛出过错。
![]() |
![]() |
![]() |
|---|
有时分咱们会如上图 2 所示,经过给 ListView 加一个 Expanded 来解决,这样 ListView 会充溢 Column 的剩下空间,然后得到一个固定的 Viewport 巨细。
可是当咱们希望此刻 ListView 不充溢,还能够居中显示的时分,就会选用如上图 3 所示那样,添加一个 shrinkWrap: true 。
虽然这个比如没有意义,可是它展现了
shrinkWrap的“首要”场景,另外shrinkWrap也常被用于ListView嵌套ListView这种不规范运用的场景中。
那 shrinkWrap 的完结原理是什么?简略来说,现阶段 shrinkWrap:true 的时分,在滑动控件内部会选用一个特别的 ShrinkWrappingViewport 「窗口」进行完结。
ShrinkWrappingViewport 和 Viewport 的不同之处在于 :
-
Viewport是填充溢主轴方向的巨细 -
ShrinkWrappingViewport是调整自身巨细去匹配主轴方向中 Item 的巨细,而这种“缩短”的行为成本会变高,由于窗口巨细需要经过 child 去“确定”。
例如,如下图所示,在 ListView 里,咱们将 itemCount 修改为 400 ,然后打印每个 Item 的 build ,由于 shrinkWrap 的作用,能够看到 400 个 child 都被输出。
相同,在 Inspector 的 Widget Tree 里能够看到 400 个 child 都构建完结,虽然他们还远没有在 ViewPort 展现出来,所以 shrinkWrap 让 ListView 失去了懒加载的作用。
相反,如下图代码所示,假如去掉 shrinkWrap ,在 Expand 的作用下 ListView 有了固定巨细的 ViewPort ,此刻就算是 itemCount 是 400 ,可是也只会依据 ViewPort 构建所需的 19 个 child 。
就算是由于滑动发生改变,正常情况下的 ListView 也保持着「固定」的长度,例如滑动到 160 的 index 的时分,此刻开始的 ListTitle 的 index 是 135 ,而不会像 shrinkWrap 一样保持着全员 child 的构建。
怎么要深究的话,其中关键点之一就在于 updateOutOfBandData 方法完结的不同,在一般 Viewport 里, updateOutOfBandData 方法仅仅用于核算 maxScrollExtent ,而如下图 1 所示,ShrinkWrappingViewport 里会对每个 child 的 maxPaintExtent 进行累计。
![]() |
![]() |
|---|
累计之后的得到的 _shrinkWrapExtent 最终会转化为 ShrinkWrappingViewport 自己的 size ,这也是 ShrinkWrappingViewport 为什么能够依据 child 调整「窗口」巨细的原因。
所以,在此之前或许开发者经常经过简略的 shrinkWrap 来解决问题,而比较少考虑 shrinkWrap 的完结原理,或许说缺少了解它的作用,然后带来了一些隐形的功能问题而不自知,所以这也是为什么这次会有该调整的原因:
将
shrinkWrap迁移到全新控件能够更直观让大家了解其作用,而其实大部分运用shrinkWrap的场景能够被其他完结代替。
-
例如前面提到的
ListView嵌套ListView的场景,与其对经过装备shrinkWrap来完结,不如经过CustomScrollView结合不同SliverList或许其他Sliver组建完结组合。 -
而假如 child 并不多,其实也能够直接经过
SingleChildScrollView+Column来完结,它在必定程度上作用和shrinkWrap类似。
所以,到这里你应该知道了 shrinkWrap 的完结逻辑和作用,其实本次首要也是想经过这个 new feature 变动,带大家重新认识下 shrinkWrap ,由于接下来,它就不再叫 shrinkWrap 了,或许你以后也应该很少用到它。













