【Flutter】熊孩子拆组件系列之拆ListView(八)—— SliverList的运作机制
theme: condensed-night-purple
前言
前面一篇对SLiverList的结构做了解析,去了解它的大概运行步骤这篇来分析下具体是如何运行起来的;
SliverList 如何运行起来的
上篇简单分析了一下SliverList的组成、各个组成的大体功能,可以确定下来:
具体驱动SliverList整体部分的,就放在RenderSliverList的performLayout方法中,现在逐行分析一下代码:
开始layout
首先是眼熟的 SliverConstraints ,ViewPort 所创建的SliverConstraints 在这里正式开始生效;
接下来是childManager.didStartLayout ;但是这个方法仅仅做了个assert,并无实际逻辑;或许仅仅当作一个生命周期的形式?
childManager.setDidUnderflow 方法更改了一个名为_didUnderflow
的布尔值;这个布尔值仅在Element,也就是childManager 中起作用,按照注释的说明:这里是为了确保能更新最大偏移量
回到RenderSliverList , 接下来是一堆数据的初始化,跳过~
一段让人在意的注释
接下来是个比较长的注释,好像很重要的样子:
用我的工地英语大概瞄了一眼,应该是个踩坑心得,大体意思是:
本来想着直接根据ScrollOffset去找到对应的child,然后往后或者往前铺满ViewPort就完事了;但是用这种方式会导致布局的时候,没布局的child被删掉了的时候,列表会不一致,进而导致对不上的节点被重新创建;所以解决方式是直接从当前第一个child开始,逐个判断铺满ViewPort;
这个是什么样的情况?好像没太理解~
layout启动前确保有内容初始节点
这段是保险起见,在刚开始的时候去判断一下有没有初始节点,没有的话加一个,加不上就结束;
设置追踪边界
之后定义了两个边界child,在这俩范围内的会被追踪;
这两个变量,一个表示开头已经layout过的child;一个表示结尾已经Layout过的变量
去除不需要绘制的节点
这段是判断一下当前的child是否有scrollOffset,如果没有的话,就遍历一下,直到找到一个ScroollOffset不为空的,并将循环次数传给CollectGarbage方法;最后将那个scrollOffset不为空的尝试添加并设置为初始节点;
CollectGarbage会根据传入的数据,不断调用 _destroyOrCacheChild 方法;
_destroyOrCacheChild 方法所做的事就是调用childManager.removeChild 方法,更新Element树,保证Element树对应正确;
removeChild方法会标记dirty,然后更新_childElements;
回到RenderObject层:
找到规定要求的ScrollOffset对应的child节点
接下来这个有点长的方法,但所做的事也就一件:不断往firstChild前插View,直到达到符合constraint中规定的ScrollOffset 的地步
盲猜是为了 scrollTo 之类的方法;
先分析前面部分;
首先是一个for循环,将firstChild的scrollOffset作为初始值,每次判断是否大于 constant.ScrollOffset 是的话,会调用 insertAndLayoutLeadingChild 方法,更新最新child,如果最新child为空,那么判断是不是到顶了,是的话layout firstChild,中断循环;否则返回,并要求修正ScrollOffset;
inserAndLayoutLeadingChild方法中,也是调用了childManager来更新Element:
后半部分:
所做的事,一个是因为可能存在双精度的错误,进行一个修正;另一个就是最后不断更新ParentData中的数据,并更新leading和trailing 这两个范围标识;
确保如果ScrollOffset在0的位置,那么firstChild即是内容首节点,偏移量也是0
这部分所做的事,正如注释所述:
让0位置的一定是整个ListView的开头,如果不是,就找到第一个child,更新其geometry,要求修正;
准备开始填充满ViewPort
经过重重检测、判断,能到这里的部分,说明 earliestUsefulChild 对应的是firstChild,当前内容的第一个child,同时也不会超过scrollOffset;可以以此为开头,去填满Viewport了;
当然加了两个assert去检测一下;
确保第一个节点经过了测量
如果的检测都没有经过,比如说,scrollOffset本身就为0的那种,那么不符合前面的任何一个检测,自然首节点没有经过layout,leadingChildWithLayout 也自然为空,在这里做下初始化;
定义变量、方法
在这里根据firstChild,初始化了一些变量;不过重点还是这个 advance 检测方法:
- 首先判断一下当前待检测的child是否是结尾已经布局好了的child,如果是的话,标记一个不在范围内;
- 获取链表中下一个child
- 下一个child为空,标记为不在范围内;
- 如果发现不在范围内
- 如果下一个child为空,尝试添加一个,添加失败的话,直接返回,没必要再检测了;
- 如果链表中已经有了,那么直接layout
- 更新 trailingChildWithLayout ;
- 更新 parentData;
- 更新 EndScrollOffset ;
填充
按照注释说明,这里是寻找到第一个偏移量小于要求的ScrollOffset的位置;
同时如果endScrollOffset 小的话,那说明已经过了缓存区,该回收了;
如果无法再增加下一个child,那么要求修正;
这里就是真正填满的地方;
如果就是无法添加,那是到头了;
回收
上面填充过程一开始,记录和计算了了一个leadingGarbage
,另一个结尾的trailingGarbage
还没计算,所以下面计算这块:
因为经过advance 方法,child已经设置为规定缓存区中的最后一个,如果最后一个还有nextChild,说明nextChild已经移出缓存区,该被回收了。
最后,将上面两个计算后的数字放到collectGarbage方法中,进行回收;
构建geometry
将包括可滑动范围、绘制偏移量之类的信息填到geometry中,返回给父类;
结束layout
总结:
SliverList 运行的原理其实也不复杂,一句话说就是:
根据ViewPort构造的SliverConstraint中的ScrollOffset和cache范围;先计算定位到第一个view在哪,然后不断插入,直到填满;
转载自:https://juejin.cn/post/7027032862939414559