其中第 2 步条件检查:
1. width >= RenderView.width/2
2. height >= RenderView.height/2
3. 类型是 RenderViewportBase
4. axis == Axis.vertical
实现源码如下:
RenderViewportBase? findTopVerticalScrollRenderObject(RenderView? root) {
Size rootSize = size(root, Size.zero);
// if (root != ) {
// _debugGetRenderTree(root, 0);
// }
RenderViewportBase? result = _recursionFindTopVerticalScrollRenderObject(root, rootSize);
if (_hitTest(root, result)) {
return result;
}
return ;
}
RenderViewportBase? _recursionFindTopVerticalScrollRenderObject(
RenderObject? renderObject, Size rootSize) {
if (renderObject == ) {
return ;
}
///get RenderObject Size
if (_tooSmall(rootSize, size(renderObject, rootSize))) {
return ;
}
if (renderObject is RenderViewportBase) {
if (renderObject.axis == Axis.vertical) {
return renderObject;
}
}
final ListQueue<RenderObject> children = ListQueue<RenderObject>;
if (renderObject.runtimeType.toString == '_RenderTheatre') {
renderObject.visitChildrenForSemantics((RenderObject? child) {
if (child != ) {
children.addLast(child);
}
});
} else {
renderObject.visitChildren((RenderObject? child) {
if (child != ) {
children.addLast(child);
}
});
}
for (var child in children) {
RenderViewportBase? viewport =
_recursionFindTopVerticalScrollRenderObject(child, rootSize);
if (viewport != ) {
return viewport;
}
}
return ;
}
找到首个满足条件的 RenderViewportBase 并不一定是我们需要的对象,如下图所示:闲鱼详情页通过上述方法能找到红色框的 RenderViewportBase,在左图情况下,能满足滚动截图要求;但在右图情况下,留言面板遮挡了长列表,此时红色框 RenderObject 并不是我们想要的。
此刻我们需要检测 Widget 可见性/可交互检测能力。查看 Flutter 官方 visibility_detector 组件并不满足我们的要求,其通过在子 Widget 上放置一个 Layer 来间接检测可见状态,但因为通过在屏幕内的宽高判断,无法检测 Widget 被遮挡的情况。
左图长列表没有被遮挡,可以被操作;右图被留言面板遮挡,事件无法传递到长列表,无法被操作;为此,我们模拟用户的点击能否被触达来检测 RenderViewportBase 是否被遮挡,能否用来做长截屏滚动。
特别注意的是,当 Widget 被 Listener 包装,事件消费会被 RenderPointerListener 拦截,如下图所示。
查看 Flutter Framework 源码,Scrollable Widget 包装了 Listener,Semantics,IgnorePointer;闲鱼 PowerScrollView 使用了 ShrinkWrappingViewPort。为此,递归找到的 RenderSliverList 和点击测试找到的 RenderPointerListener 的距离为 5,如上图所示。