diff --git a/packages/flutter/lib/src/widgets/scrollable.dart b/packages/flutter/lib/src/widgets/scrollable.dart index ac854f7de8f..fdeba1a88f5 100644 --- a/packages/flutter/lib/src/widgets/scrollable.dart +++ b/packages/flutter/lib/src/widgets/scrollable.dart @@ -786,11 +786,13 @@ class ScrollableState extends State switch (widget.axis) { case Axis.vertical: _gestureRecognizers = { - VerticalDragGestureRecognizer: - GestureRecognizerFactoryWithHandlers( - () => VerticalDragGestureRecognizer(supportedDevices: _configuration.dragDevices), - (VerticalDragGestureRecognizer instance) { + _VerticalDragGestureRecognizer: + GestureRecognizerFactoryWithHandlers<_VerticalDragGestureRecognizer>( + () => + _VerticalDragGestureRecognizer(supportedDevices: _configuration.dragDevices), + (_VerticalDragGestureRecognizer instance) { instance + ..isDyAllowed = _isDyAllowed ..onDown = _handleDragDown ..onStart = _handleDragStart ..onUpdate = _handleDragUpdate @@ -859,6 +861,10 @@ class ScrollableState extends State Drag? _drag; ScrollHoldController? _hold; + bool _isDyAllowed(double dy) { + return position.viewportDimension - dy > 30; + } + void _handleDragDown(DragDownDetails details) { assert(_drag == null); assert(_hold == null); @@ -2553,3 +2559,39 @@ class _HorizontalInnerDimensionState extends ScrollableState { return _configuration.buildOverscrollIndicator(context, child, details); } } + +typedef IsDyAllowed = bool Function(double dy); + +class _VerticalDragGestureRecognizer extends VerticalDragGestureRecognizer { + _VerticalDragGestureRecognizer({ + super.debugOwner, + super.supportedDevices, + super.allowedButtonsFilter, + }); + + IsDyAllowed? isDyAllowed; + + bool _isDyAllowed = true; + + @override + bool isPointerAllowed(PointerEvent event) { + _isDyAllowed = isDyAllowed?.call(event.localPosition.dy) ?? true; + return super.isPointerAllowed(event); + } + + @override + T? invokeCallback( + String name, + RecognizerCallback callback, { + String Function()? debugReport, + }) { + if (!_isDyAllowed) return null; + return super.invokeCallback(name, callback, debugReport: debugReport); + } + + @override + void dispose() { + isDyAllowed = null; + super.dispose(); + } +}