前言

有时候我们需要监听 list 里面的 child 的位置,这个需求很常见,但是在 flutter 如何操作呢?下面会说明,为此我还开源了一个自用的库(为了 api 易用反而改了很久…)

思路

方法一

获取 child 的位置最方便的方法就是直接增加 Key,然后在外部遍历 Key,从而获取 render,然后就可以获取 position 了。
但是这种方法没有目的的方法去遍历,不太优雅。

方法二

RenderSliver 入手,通过 SliverConstraints 可以很方便的获取关于 widget 所有数据,比如 size,offset,axis 等等。
但是,自定义一个 render 进行回调貌似和 render 定义有点冲突。

方法三

有没有方法可以直接回调知道,当前增加的 widget?有的,就是通过 Element,重写 mount and unmount,然后回调,就可以清楚知道有当前有多少个 widget 了。
但是这个回调是包含 cache 的,所以也需要从 element 中获取 render 去判断。

判断

通过 RenderAbstractViewport.of(RenderObject) 获取 viewport,然后得到具体位置,还有 axis,viewport 所占空间大小,就可以判断出 widget 是否显示,也可以根据特定需求去判断,比如 存在 sliverPinnedHeader 的时候,起始位置就不是 0 了,变成 header.height。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
final RenderBox box = element.renderObject;
viewport ??= RenderAbstractViewport.of(box);

RevealedOffset offsetToReveal =
viewport.getOffsetToReveal(box, 0);
final double reveal = offsetToReveal.offset;
double start = reveal - viewport.offset.pixels;

bool vertical = viewport.axis == Axis.vertical;
var itemSize = vertical
? offsetToReveal.rect.size.height
: offsetToReveal.rect.size.width;

var viewportSize =
vertical ? viewport.size.height : viewport.size.width;

var end = start + itemSize;

滑动

根据上面的代码,已经可以做到判断新的 child 显示问题,但是不够准确,因为触发时机是 cache 产生的时候,所以要增加滑动触发判断位置。
监听滑动有两种方法,一种是使用 ScrollController.addListener,另一种是通过 NotificationListener<ScrollNotification>(...) 的方式,由于希望组件的颗粒度影响较小,选择了后者。
ps: 其实两者源码角度是一样的,都是从 ScrollPositionWithSingleContext.jumpTo 开始分发。

优化

由于触发事件有 mount and unmont,还有滑动事件,重复性太高,我这边使用了 SchedulerBinding.instance.addPostFrameCallback(...) 来保证多次触发一帧只计算一次。

最终代码

https://github.com/YeungKC/widgets_visibility_provider

总结

由此可见方法有多种,如何选择和优化都是为了更好的使用,这个库我引入了 bloc 方便回调,更符合我平时使用习惯,也封装了不需要 bloc 的组件。
无论什么 scrollview,都可以使用,而且入侵性很小,绑定数据方便。

在原有的代码scrollview 增加 WidgetsVisibilityProvider 包裹。
child 使用 VisibleNotifierWidget 包裹,既可以直接从 VisibleNotifierWidget 获取回调 build,也可以 listen 回调使用。
如有需要一次过获取所有显示中的数据,也可以使用 WidgetsVisibilityListener or WidgetsVisibilityBuilder
如果项目使用了 bloc,那么直接使用 WidgetsVisibilityProviderBloc 也可~

推荐