[Flutter] sliver: 从BoxConstraint到SliverConstraint

Flutter\]BoxConstraint与Sliver Sliver,在Flutter基础组件中,算是一个「进阶型」的组件了,它被官方直接内置在组件库中,提供给开发者使用,但是看着又不如ListView那样「直接、易用」,初学的时候非常纠结于一件事情: > 什么是Sliver。 Sliver直接翻译为「条」、「薄片」。 > ~~银是silver,别搞混了~~ 何为薄片?在一个可滑动布局中,一个children item所占据的区域,就是一个「薄片」,多个sliver薄片共同构成了ScrollView的children数组,如下图: ![](https://file.jishuzhan.net/article/1729097301881786369/c21ffc859c5f206fde58f1fbfbd7e2ec.webp) 从上图中,我们可以看到三个Sliver: 1. SliverPersistent Header; 2. SliverPersistent Header2,它是被「钉」在顶部的,不会像第一个SliverPersistentHeader一样会被其它的元素推走,也就是我们常说的吸顶效果: ![](https://file.jishuzhan.net/article/1729097301881786369/47376e83bf38d3a8fb7f92f59988c050.webp) 3. SliverList,一个Sliver的List,它有很多的子item,但是只有最外层的SliverList才是一个Sliver,其内部的item,最终受到的还是BoxConstraint: ![](https://file.jishuzhan.net/article/1729097301881786369/a8b0d7d72345693e37a3061529d10457.webp) 这样一来,Sliver的概念似乎就更清晰了一些,就是可滑动布局中的这些「条状物」,就是可滑动布局中的一个构成单元,也就是一个Widget。 # 一、从ListView说起 老样子,我们从我们比较熟悉的ListView说起,ListView继承自BoxScrollView,ScrollView,而ScrollView最终是一个StatelessWidget,显然,ScrollView作为一个(StatelessWidget)本身并不管理Render阶段的事情,因此通过阅读它的build方法,我们可以看到一个StatefulWidget,也就是Scrollable。 ## 1.1 Scrollable 简单来看看Scrollable,阅读一个StatefulWidget的代码,阅读它对应的State显然是更重要的事情。`ScrollableState`,值得注意的是,Scrollable本身暴露了一个静态的**of**方法,方便我们在子Widget中获取到ScrollableState对象,有助于在子Widget中向上控制父组件的滑动: ```ini ScrollableState scrollable = Scrollable.of(context) ``` 我们可以借助ScrollableState实现一个点击后在列表上进行跳转的操作: ```ini List _build100Children() { List result = []; for (int i = 0; i < 100; i++) { result.add(Builder(builder: (context) { return SizedBox( height: 100, child: TextButton( onPressed: () { ScrollableState? state = Scrollable.of(context); // 例如点击index = 11的列表项目,就滚动到第89个列表项 state?.position.jumpTo((100 - i) * 100); }, child: commonText(index: i)), ); })); } return result; } ``` ## 1.2 ScrollView的继承树 ScrollView作为一个StatelessWidget,它便是可滑动布局的继承树上是一个相对来说的根节点: ```rust StatelessWidget -> ScrollView -> BoxScrollView -> ListView -> GridView -> CustomScrollView -> _NestedScrollViewCustomScrollView ``` 可以看到,ScrollView在派生子类的时候,产生了两个不同的分支:BoxScrollView和CustomScrollView,前者和我们常用的ListView、GridView相关;而后者,如果使用过Sliver相关工具的开发者,一定对CustomScrollView和NestedScrollView(_NestedScrollViewCustomScrollView)不陌生。 ![image.png](https://file.jishuzhan.net/article/1729097301881786369/a9845758d1766a77473781e75513ffe3.webp) ## 1.3 ScrollView#build ![](https://file.jishuzhan.net/article/1729097301881786369/728aa28f06344c6b0423b9cb490b78d2.webp) 我们提到过Sliver就是可滚动布局中的children中的一个个item,所以它们肯定也是Widget。 其中有一个关键的方法:buildSlivers,它的实现是抽象的,需要被两个子类:BoxScrollView和CustomScrollView来实现。 2. # BoxScrollView与CustomScrollView实现对比 ## 2.1 BoxScrollView 我们直接看二者对于buildSlivers的方法实现,就已经非常直观了,首先我们看看我们常用的ListView对应的BoxScrollView,它内部所做的事情,实际上是为我们在Padding不为空的情况下,包裹了一个SliverPadding。 ![](https://file.jishuzhan.net/article/1729097301881786369/c61b3ca2bb6dffc4cd1a9664919b1920.webp) 这也是为什么,我们在一些异形屏幕上,ListView总会在顶部预留出一部分的Padding,特别是ListView不在顶部的时候(例如在Scaffold中,不使用Appbar,且上方有个Text的时候): ![](https://file.jishuzhan.net/article/1729097301881786369/adb498a23964720794db8da7276c2f5a.webp) 它就是我们通过MediaQuery.of(...).padding查询得到的一个屏幕的内边距,一般就是SafeArea隔开的高度。 但是,如果在Scaffold的外层使用了SafeArea以后,这个Padding一般会是`EdgeInsets.ZERO`,因为SafeArea会默认地将这个Padding移除掉,并在自己内部进行消化。 而Scaffold这个脚手架也会在一定的条件下为我们移除掉这个Padding,例如Appbar存在的情况下,这个Padding就不会被应用到body之上,而是被应用在顶部的Appbar上: ![](https://file.jishuzhan.net/article/1729097301881786369/41797879a0dc5ad36bff896f89f189cc.webp) > 也就是说,如果不使用SafeArea,我们也可以手动地用一个MediaQuery.removePadding包裹一下ListView,来移除这个Padding。 回到话题,返回的sliver对象,如果不是buildChildLayout的返回值,那就是SliverPadding套着一个buildChildLayout的返回值。 前者的实现会根据子类细分,例如ListView中: ```csharp @override Widget buildChildLayout ( BuildContext context ) { if (itemExtent != null ) { return SliverFixedExtentList ( delegate : childrenDelegate, itemExtent : itemExtent!, ); } else if (prototypeItem != null ) { return SliverPrototypeExtentList ( delegate : childrenDelegate, prototypeItem : prototypeItem!, ); } return SliverList(delegate : childrenDelegate); } ``` 总结下来,ListView内部实际上也是用了Sliver的方法,只不过封装了一层,这些东西对上层的开发者是无感的,我们始终在操作Widget对象,处理Widget对象的约束。 但是作为一类通用、常用的方法,ListView限定了这些东西,在场景上必然有它的局限性。因此,作为可定制的ScrollView------CustomScrollView更加灵活。 ## 2.2 CustomScrollView 而CustomScrollView的实现就简单的多: ```swift /// The slivers to place inside the viewport. final List slivers; @override List buildSlivers(BuildContext context) => slivers; ``` 是的,我们需要自己去构建sliver,然后填入,但是,令人困惑的点就在于Sliver。 他并不支持普通的Widget,例如我们想在CustomScrollView中,填入一个Text,它就会直接抛出异常: ![](https://file.jishuzhan.net/article/1729097301881786369/fa741091f9bbc19abd6bfde02e54beea.webp) 直译下来就是:RenderViewport(CustomScrollView的Viewport)只允许RenderSliver,而不允许RenderParagraph。**RenderSliver和RenderBox并不是一套布局协议(For example, a RenderSliver cannot be the child of a RenderBox because a RenderSliver does not understand the RenderBox layout protocol)** 。 借鉴ListView的实现,在ListView中,children一定会被包裹在一个`SliverFixedExtentList`、`SliverPrototypeExtentList`或者是`SliverList`当中,这就意味着ScrollView的children,必须是一个Sliver容器,如果我们要使用Text,就需要将一个Text包裹在一个Sliver容器中,比如: ```less CustomScrollView( slivers: [ SliverToBoxAdapter(child: commonText(index: 0),) ], ) ``` # 三、Sliver Layout Protocal 我们知道Flutter的「约束布局」的布局方式是:从根布局向下传递约束(Constraint);然后子组件向上返回尺寸(Size)。 但是,**RenderSliver和RenderBox之间并不是一套布局协议,** 所以会导致无法直接在CustomScrollView中使用BoxConstraint进行布局。 取而代之的,则是另外一对东西:SliverConstraints和SliverGeometry,我们可以在文章开头的例子中,打开Android Studio右侧的Flutter Inspector看到RenderObject中的这些配置信息: ![](https://file.jishuzhan.net/article/1729097301881786369/5ac50c2690b6f013618b7d92a46f8061.webp) ## 3.1 SliverConstraints 用于SliverLayout的不可变约束信息 给出了指定的视图,从当前视图(Sliver)出发,受到可滑动视图的所对应的Viewport受到的约束信息。 例如,一个为0的scrollOffset就表示当前的这一块Sliver的(leading边缘)在这个Viewport中是可见的。而并不意味着viewport自身具有0的滚动偏移。 > leading,竖直滑动情况下一般是上边缘;左右滑动情况下一般是左边缘。 有如下的布局: ![](https://file.jishuzhan.net/article/1729097301881786369/5346c07bc0b42dc763b944ec933b906b.webp) 以SliverToBoxAdapter为例,分别统计了它在全部可见/部分可见/全部不可见情况下的SliverConstraint约束数值: ![](https://file.jishuzhan.net/article/1729097301881786369/a9de2de08e82c82d7942fa3bace434cb.webp) 通过图片和数值的结合,我们就很可以更加清晰地理解这个scrollOffset的含义,也点明了SliverConstraint本身的定义:**从当前(Sliver)所对应的Viewport受到的约束信息**。 其他的参数描述戳这:[SliverConstraints class - rendering library - Dart API](https://link.juejin.cn?target=https%3A%2F%2Fapi.flutter-io.cn%2Fflutter%2Frendering%2FSliverConstraints-class.html "https://api.flutter-io.cn/flutter/rendering/SliverConstraints-class.html") ## 3.2 SliverGeometry 官方的定义就是:描述一个RenderSliver所占据的空间。 但是一个Sliver可能会在不同的方面需要计算占据的空间,所以这里的返回值就不像Size一样只有width和height两个属性了,而且因为主轴存在的关系,Sliver的父布局,也就是ScrollView,通常只要处理主轴上的约束关系,副轴一般会直接将上层的约束直接传递给下层。因此,SliverGeometry相比较于Box布局模型的2维的Size多了亿些描述参数,但是都是描述同一个方向的: ![](https://file.jishuzhan.net/article/1729097301881786369/a536234002b5a038b0b0031fcdb853b9.webp) 具体的参数说明戳这:[SliverGeometry class - rendering library - Dart API](https://link.juejin.cn?target=https%3A%2F%2Fapi.flutter-io.cn%2Fflutter%2Frendering%2FSliverGeometry-class.html "https://api.flutter-io.cn/flutter/rendering/SliverGeometry-class.html") # 四、ListView中的Sliver > SliverFixedExtentList、SliverPrototypeExtentList和SliverList 总之,Flutter依靠Sliver来完成可滑动布局的创建,无论是常用的ListView,还是不常用的CustomScrollView,它们出自同一个源头:ScrollView。而ScrollView在操作的对象一定都是RenderSliver。 ListView在此基础上做了相应的封装,提供了一个builder参数,进行了数据源(Data) -\> Item Widget的转换。得益于ListView在自身和ItemWidget之间提供的**Sliver组件中间层**,我们可以在ItemWidget中使用BoxConstraint来完成我们的列表项布局,ListView提供的三个Sliver中间层分别是: 1. SliverFixedExtentList 2. SliverPrototypeExtentList 3. SliverList 它会根据如下的逻辑来选择具体的哪一种Sliver组件被使用: ```csharp @override Widget buildChildLayout(BuildContext context) { if (itemExtent != null) { return SliverFixedExtentList ( delegate: childrenDelegate, itemExtent: itemExtent!, ); } else if (prototypeItem != null) { return SliverPrototypeExtentList ( delegate: childrenDelegate, prototypeItem: prototypeItem!, ); } return SliverList ( delegate : childrenDelegate); } ``` 这里就涉及到两个参数:`itemExtent`和`prototypeItem`了。 ## 4.1 itemExtent和SliverFixedExtentList 前者表示item在主轴上的大小,默认就是高度,一但指定了item的高度其实能解决很多问题,比如说之前提到的ListView内Column中的Expanded无法确定高度的问题,在加上itemExtent参数就能解决: ```less Widget _buildItemExtentWidget() => ListView( itemExtent: 300, children: [ Column( children: [ Expanded( child: Container( color: ColorUtil.getRandomColor(allowTransparent: false), height: 100, child: TextButton( onPressed: () {}, child: commonText( title: "Fixed height Expanded Item in ListView")), ), ) ], ) ], ); ``` 他背后其实是**SliverFixedExtentList**的作用,它为所有的子item提供了相同的主轴高度。这和你直接在Column外面套一个SizedBox非常相似,也有相似的局限性,如果含有不确定高度的item,显然不应该使用这种方案。 ## 4.2 prototypeItem与SliverPrototypeExtentList prototype直译为原型,prototypeItem就是原型item,在提供原型item的时候,会强制所有的children和原型item具有一致的高度,显然,这个和itemExtent非常相似,但是前者需要我们在开发的时候就确定数值的大小,而后者可以稍微地将这个过程延迟一点,只提供一个Widget作为原型item,将它的尺寸设置为其他item的高度。 prototypeItem会在其他的sliver元素被布局之前进行布局,并且不会被展示出来绘制、也不会接受任何事件。 在原型item传入之后,会成为SliverPrototypeExtentList对应RenderObject:_RenderSliverPrototypeExtentList的一个属性:child,这个child在布局之后,会获得它自己的size属性,然后可以通过itemExtent获取它的尺寸属性。 显然,这个child就不能是sliver了,应该是一个普通的RenderBox(要不然没有size属性)。 ```arduino @override double get itemExtent { assert (child != null && child!. hasSize ); return constraints. axis == Axis . vertical ? child!. size . height : child!. size . width ; } ``` 相比较第一种SliverFixedExtentList,局限性小了一些,你要是写成这样: ```scss Widget _buildPrototypeListViewWidget() => ListView( prototypeItem: const SizedBox( height: 300, ), ...... ) ``` 那和SliverFixedExtentList差别就不大了,但是无论是SliverFixedExtentList还是SliverPrototypeExtentList,它们虽然能够给与子Widget一个主轴上的非infinity约束,但是都需要我们手动去指定。 > 为什么要特地提供这两种方法来构建ListView? > > (以纵向滚动为例)首先这两种方法通常只针对高度确定的item有效,一般只有一些特定的列表、纵向的图片封面流等等场景时使用的。 > > 其次,ListView自身会具有Lazy Loading,即懒加载的功能,它只会加载Viewport和Viewport之外和附近的一些不可见Item,很明显就是为了去提高性能。但是在高速滑动的场景中,由于距离Viewport比较『远』的item是懒加载的,因此ListView需要重新去渲染它们,并得到它们的高度,这样以确定滚动的偏移量效果。 > > 例如此时展示的最后一个item的index = 4,Viewport以外,下方额外渲染了index=5的item。如果此时通过上面提到的ScrollableState获取控制器,直接滚动到index = 100的item,此时需要跨越95个ListView的Item,**但是此时ListView并不知道这95个item的高度**,它需要在很短的滚动时间内将它们计算出来,然后得到高度,最终得到一个滚动的偏移量 = 这95个item的高度之和。 > > 显然,这种操作是有性能问题的,核心的点就在于,ListView被命令滑动到index = 100的item,但是它并不知道自己要滚动多少,而滚动的偏移量又由一个个的item所占据的高度所决定,这样一来,问题就变成了item的高度不确定,显然,SliverPrototypeExtentList和SliverFixedExtentList都在尝试去解决这个不确定高度的问题。 ## 4.3 SliverList与SliverFillRemaining 既不传itemExtent,又不传prototypeItem,那么此时ListView给与子Widget,在主轴上的约束就会是0,infinity,如果Column受到这个约束限制,它的Expanded属性就会失效。它其实没有什么好说的,我们要着重说说上一篇中提到的另一个内容:SliverFillRemaining。 只要在把ListView换成CustomScrollView,并在Column外,套上一层:SliverFillRemaining,Column就可以神奇的获取到CustomScrollView的剩余高度约束了,这也让Column中的Expanded能够正常工作。 SliverFillRemaining就好像为CustomScrollView定制的Expanded: ```less CustomScrollView( slivers: [ SliverFillRemaining( child: Column( children: [ Expanded( child: Container( ``` 从源码中,我们可以得到它的布局方式,其实就是通过拿到它所受到的SliverConstraints信息,计算得到一个剩余的尺寸,并且将这个剩余的尺寸作为一个强制约束施加给子Widget(这里会根据**SliverFillRemaining#child** 是否可以滑动区分不同的情况 **,不同情况代码稍有不同**) ```java @override void performLayout() { final SliverConstraints constraints = this.constraints; final double extent = constraints.remainingPaintExtent - math.min(constraints.overlap, 0.0); if (child != null) child!.layout(constraints.asBoxConstraints( minExtent: extent, maxExtent: extent, )); ...... } ``` 比如在CustomScrollView的Sliver中,只有一个**SliverFillRemaining**的时候,参数的取值会是这样的: ![](https://file.jishuzhan.net/article/1729097301881786369/3418783778b6b8d3f9791661924e9aca.webp) 此时我们在**SliverFillRemaining**上方加一个高度为100的Widget,此时的参数就变成了这样: ![](https://file.jishuzhan.net/article/1729097301881786369/5f97959a7205890404b780f293acb1a6.webp) 此时我们在**SliverFillRemaining** 下方**再**加一个高度为100的Widget,此时的参数并没有发生变化: ![](https://file.jishuzhan.net/article/1729097301881786369/4a753014bba3865d97040869fa4b3ca6.webp) 显然,和Column + Expanded的组合还是有不同的,**SliverFillRemaining会强制子Widget的高度大小为可以展示的剩余空间,如1中的剩余空间就是屏幕高度:932。** **2中的剩余空间就是832,但是,要注意的是,如果此时发生了上滑,那么剩余空间就会从832慢慢又回到932,因为顶部的SliverToBoxAdapter被挤出去了,剩余的空间会慢慢变多;也就是说,SliverFillRemaining会让child的高度约束发生变化:** ```ini flutter: flutter_experiment_log: Column's constraints:BoxConstraints(w=430.0, h=832.0) Reloaded 2 of 587 libraries in 189ms. flutter: flutter_experiment_log: Column's constraints:BoxConstraints(w=430.0, h=833.0) 省略号省略号省略号 flutter: flutter_experiment_log: Column's constraints:BoxConstraints(w=430.0, h=927.0) flutter: flutter_experiment_log: Column's constraints:BoxConstraints(w=430.0, h=932.0) ``` > 如果你在Text外部套一个FittedBox,并且把CustomScrollView的滚动方向改为横向: > > ```less > SliverToBoxAdapter( > ...... > ), > SliverFillRemaining( > child: FittedBox( > child: Text("HelloWorld"), > ) > ), > ``` > > 那么此时会有一个不错的效果: > > ![](https://file.jishuzhan.net/article/1729097301881786369/842e5c8e16674ee4348cfd0c2aa8862d.webp) 而第二次在SliverFillRemaining下方添加的第二个高度为100的Widget并不会使得SliverFillRemaining内容的高度从832缩小为732,因为它并不影响CustomScrollView正在显示的剩余空间: ![](https://file.jishuzhan.net/article/1729097301881786369/cf2944154bc48b71ee6ee659d5031c59.webp) 所以,通过SliverFillRemaining获得的约束大小,会随着滚动状态的变化而改变。SliverFillRemaining给定的约束,其实是排除SliverFillRemaining leading方向元素之后,当前CustomScrollView对应的viewport剩余空间的约束。 假设此时CustomScrollView中,一共有两个Sliver组件,Sliver1和SliverFillRemaining: * 如果另一个Sliver 1排在CustomScrollView的slivers数组中,SliverFillRemaining项目**之前**,假设此时屏幕的高度是932,而该Sliver 1的高度也为932(恰好撑满屏幕),虽然一开始我们的SliverFillRemaining是不可见的,但是随着手指向上滑动,视图整体向下,Sliver 1的一部分被推出屏幕,假设被推出部分的高度为200(Sliver1任有732的部分是可见的),那么此时的SliverFillRemaining所能占满的viewport的剩余空间就是200,并且随着滑动的过程,这个viewport的剩余空间会从0逐渐变大,最大可以达到viewport的高度,这里的例子中就是932(屏幕高度,因为CustomScrollView受到的约束假定是直接使用的屏幕高度,没有其他约束的影响)。 * 如果另一个Sliver 1排在CustomScrollView的slivers数组中,SliverFillRemaining项目**之后**,不会影响CustomScrollView当前viewport的剩余可展示空间; > 当然,这个剩余空间的最大值其实是CustomScrollView受到的约束的最大值,如果你在CustomScrollView外面套一个SizedBox,高度为300,这样CustomScrollView受到的是一个高度为300的强制约束,这样一来,viewport的剩余空间最大也就只能为300,伴随着滑动,**SliverFillRemaining**的高度会在0\~300之间变动。 显然,SliverFillRemaining和Expanded还是不同的,虽然他们解决的问题有一点相似: * SliverFillRemaining:**占满除开SliverFillRemaining上方sliver后** \* ,当前**Viewport**的剩余空间; * Expanded:**占满除开其他Widget后** ,**Column**的剩余空间; > SliverFillRemaining会先占满剩余空间,这就意味着SliverFillRemaining下方的slivers们并没有机会在SliverFillRemaining占满剩余剩余空间之前就被显示出来。 SliverFillRemaining的特殊之处在于,CustomScrollView的Slivers们不一定会同时在viewport中可见,伴随着滚动,SliverFillRemaining上方(leading方向)的其他Sliver移出viewport,Viewport的剩余空间会频繁地发生变动。 ## 4.4 SliverFillViewport 除此之外,还有个组件SliverFillViewport,它和FillRemaining的区别在于,它会在一开始就将Delegate中,child的boxConstraint设置为Viewport的尺寸,**简单来说就是会独占一个Viewport尺寸区域,并且不会发生改变**,如果Viewport是撑满整个屏幕的,那么对应的BoxConstraint的数值就是屏幕约束. (横向滑动)的过程中,FillViewPort的约束并不会改变,而一直是一开始设定的381.5和831.8,而FillRemaining则由于Leading方向(横向滑动就是左边)的可用空间不断增大而导致可占据的宽度也不断增大。 ![image.png](https://file.jishuzhan.net/article/1729097301881786369/ed5e0f8c8b5ad387051ae4fbe30df574.webp) # 五、 总结 ## 5.1 ListView与Sliver > 那么为什么更为常见的ListView中,我们仍然是面向Widget开发,而不是面向Sliver开发呢? 首先,Slivers,仍然是Widget,但是它是一个很特殊的Widget,它被专用于描述ScrollView中的这种条状物,即一个item,它会构建RenderSliver来完成Sliver相关的布局。 ListView所解决的是只是超长子视图(例如很长或者很宽的Column/Row)的滚动问题,它的具名构造方法:ListView.builder,所解决的是一类更为广泛的问题:**列表**。 即包装了一层SliverList(也可能是SliverFixedExtentList或者SliverPrototypeExtentList),并将所有的子Widget在SliverList中进行布局,因此,会形成如下的结构: ```rust ListVIew -> SliverList -> Widget item1 -> Widget item2 -> Widget item3 ...... ``` 所以ListView本身也是在操作SliverList,只不过它为我们屏蔽了Sliver相关的细节,我们可以直接聚焦于数据和视图之间的关系,并使用BoxConstraint在SliverList中进行布局。 > ScrollView中并不支持BoxConstriants,但是具体到某个具体的Sliver中,都是BoxConstraints的。因此Sliver本身只是特定于ScrollView子Widget的产物。我们使用CustomScrollView的时候,Sliver通常被用来实现一些滑动特性相关的内容,而具体的内容实现,更多地还是在写BoxConstraints布局。 ## 5.2 Constraint和SliverConstraints 前者是我们在Flutter中更为常用的一类布局协议,它通过向下传递约束:BoxConstraint,向上传递尺寸(Size)来完成布局。 后者则是特定在ScrollView中的一类布局,由于ScrollView通常只针对具体的scrollDirection进行滑动,所以通常来说我们只需要聚焦于滚动方向上的状态进行提供约束,它描述了当前的Sliver在ScrollView中所受到的约束状态,会频繁地发生变动。 Sliver约束传递完成后,会生成一个SliverGeometry,作为Size的对应产物,同样是对尺寸数据进行的描述,同样地,也只有主轴的尺寸数据,但是描述信息中会有更多维度。 Flutter的主要布局方式还是Constraint,即约束布局,你会发现,无论是CustomScrollView受到的约束,还是Sliver组件给与Child施加的约束,仍然都是BoxConstraint,只有在可滑动布局(ScrollView)存在时,BoxConstraint才会被SliverConstraint所取代,但是child在具体布局的时候,又不乏有SliverConstraints 转换为BoxConstraint的这一种操作,譬如:SliverToBoxAdapter中,对子Widget布局的处理: ![image.png](https://file.jishuzhan.net/article/1729097301881786369/f2ca9c3e6c13db2d1232a4c1a7bb278d.webp) asBoxConstraints本身的处理,也就是根据SliverConstraint一些参数的定义,将一些细节给出,重新组成BoxConstraint: ![image.png](https://file.jishuzhan.net/article/1729097301881786369/7cbdf32d585069858ccac337f0472b4b.webp) 所以,Sliver的布局本身是一个从BoxConstraint到SliverConstraint,然后又回到BoxConstraint的过程。 end\~

相关推荐
火柴就是我8 小时前
flutter 之真手势冲突处理
android·flutter
Speed1238 小时前
`mockito` 的核心“打桩”规则
flutter·dart
法的空间8 小时前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
恋猫de小郭8 小时前
Android 将强制应用使用主题图标,你怎么看?
android·前端·flutter
玲珑Felone9 小时前
从flutter源码看其渲染机制
android·flutter
ALLIN1 天前
Flutter 三种方式实现页面切换后保持原页面状态
flutter
Dabei1 天前
Flutter 国际化
flutter
Dabei1 天前
Flutter MQTT 通信文档
flutter
Dabei1 天前
Flutter 中实现 TCP 通信
flutter
孤鸿玉1 天前
ios flutter_echarts 不在当前屏幕 白屏修复
flutter