效果图
当组件在滚动容器Leading方向不可见时自动滚动到顶部和容器对齐
当组件在滚动容器Trailing方向不可见时自动滚动到底部和容器对齐
当组件完全可见时不滚动
代码实现
void _makeVisible() {
///_visibleKey是GlobalKey类型,绑定到测试组件
var object = _visibleKey.currentContext.findRenderObject();
var position = Scrollable.of(_visibleKey.currentContext).position;
final RenderAbstractViewport viewport = RenderAbstractViewport.of(object);
double max = viewport
.getOffsetToReveal(object, 0.0)
.offset
.clamp(position.minScrollExtent, position.maxScrollExtent) as double;
double min = viewport
.getOffsetToReveal(object, 1.0)
.offset
.clamp(position.minScrollExtent, position.maxScrollExtent) as double;
if (position.pixels >= min && position.pixels <= max) {
Scaffold.of(_visibleKey.currentContext)
.showSnackBar(SnackBar(content: Text("无需滚动")));
return;
}
///滚动到完全可见
position.jumpTo(position.pixels.clamp(min, max));
}
分析:
-
无论是
ListView还是CustomScrollView,它们最终都会生成Scrollable类型的Widget -
Scrollable组件最终会生成Viewport或者ShrinkWrapViewport组件 -
Viewport类组件会生成RenderViewport类型的RenderObject,也就是RenderAbstractViewport的子类型
RenderAbstractViewport.getOffsetToReveal(RenderObject object,double alignment)方法:
- alignment为0.0时代表object和容器顶部对齐需要滚动的距离TOP
- alignment为0.5代表object和容器居中对齐需要滚动的距离CENTER
- alignment为1.0代表object和容器底部对齐需要滚动的距离BOTTOM
很明显BOTTOM<CENTER<TOP, p = ScrollPosition.pixels:
- 如果p当前小于BOTTOM代表在容器下方且不完全可见
- 如果p大于TOP代表在容器上方且不完全可见
- 如果p在BOTTOM和TOP之间代表完全可见
上述代码是笔者摘抄自Scrollable.ensureVisible()方法,哈哈