Commit dac80aac authored by Hans Muller's avatar Hans Muller Committed by GitHub

Make min/max fling velocity and min fling distance ScrollPhysics properties (#8928)

parent 14933de9
...@@ -204,12 +204,6 @@ typedef void GestureDragEndCallback(DragEndDetails details); ...@@ -204,12 +204,6 @@ typedef void GestureDragEndCallback(DragEndDetails details);
/// See [DragGestureRecognizer.onCancel]. /// See [DragGestureRecognizer.onCancel].
typedef void GestureDragCancelCallback(); typedef void GestureDragCancelCallback();
bool _isFlingGesture(Velocity velocity) {
assert(velocity != null);
final double speedSquared = velocity.pixelsPerSecond.distanceSquared;
return speedSquared > kMinFlingVelocity * kMinFlingVelocity;
}
/// Recognizes movement. /// Recognizes movement.
/// ///
/// In contrast to [MultiDragGestureRecognizer], [DragGestureRecognizer] /// In contrast to [MultiDragGestureRecognizer], [DragGestureRecognizer]
...@@ -256,10 +250,30 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { ...@@ -256,10 +250,30 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
/// The pointer that previously triggered [onDown] did not complete. /// The pointer that previously triggered [onDown] did not complete.
GestureDragCancelCallback onCancel; GestureDragCancelCallback onCancel;
/// The minimum distance an input pointer drag must have moved to
/// to be considered a fling gesture.
///
/// This value is typically compared with the distance traveled along the
/// scrolling axis. If null then [kTouchSlop] is used.
double minFlingDistance;
/// The minimum velocity for an input pointer drag to be considered fling.
///
/// This value is typically compared with the magnitude of fling gesture's
/// velocity along the scrolling axis. If null then [kMinFlingVelocity]
/// is used.
double minFlingVelocity;
/// Fling velocity magnitudes will be clamped to this value.
///
/// If null then [kMaxFlingVelocity] is used.
double maxFlingVelocity;
_DragState _state = _DragState.ready; _DragState _state = _DragState.ready;
Point _initialPosition; Point _initialPosition;
Offset _pendingDragOffset; Offset _pendingDragOffset;
bool _isFlingGesture(VelocityEstimate estimate);
Offset _getDeltaForDetails(Offset delta); Offset _getDeltaForDetails(Offset delta);
double _getPrimaryValueFromOffset(Offset value); double _getPrimaryValueFromOffset(Offset value);
bool get _hasSufficientPendingDragDeltaToAccept; bool get _hasSufficientPendingDragDeltaToAccept;
...@@ -345,11 +359,10 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { ...@@ -345,11 +359,10 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
final VelocityTracker tracker = _velocityTrackers[pointer]; final VelocityTracker tracker = _velocityTrackers[pointer];
assert(tracker != null); assert(tracker != null);
Velocity velocity = tracker.getVelocity(); final VelocityEstimate estimate = tracker.getVelocityEstimate();
if (velocity != null && _isFlingGesture(velocity)) { if (estimate != null && _isFlingGesture(estimate)) {
final Offset pixelsPerSecond = velocity.pixelsPerSecond; final Velocity velocity = new Velocity(pixelsPerSecond: estimate.pixelsPerSecond)
if (pixelsPerSecond.distanceSquared > kMaxFlingVelocity * kMaxFlingVelocity) .clampMagnitude(minFlingVelocity ?? kMinFlingVelocity, maxFlingVelocity ?? kMaxFlingVelocity);
velocity = new Velocity(pixelsPerSecond: (pixelsPerSecond / pixelsPerSecond.distance) * kMaxFlingVelocity);
invokeCallback<Null>('onEnd', () => onEnd(new DragEndDetails( // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504 invokeCallback<Null>('onEnd', () => onEnd(new DragEndDetails( // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504
velocity: velocity, velocity: velocity,
primaryVelocity: _getPrimaryValueFromOffset(velocity.pixelsPerSecond), primaryVelocity: _getPrimaryValueFromOffset(velocity.pixelsPerSecond),
...@@ -379,6 +392,13 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { ...@@ -379,6 +392,13 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
/// ///
/// * [VerticalMultiDragGestureRecognizer] /// * [VerticalMultiDragGestureRecognizer]
class VerticalDragGestureRecognizer extends DragGestureRecognizer { class VerticalDragGestureRecognizer extends DragGestureRecognizer {
@override
bool _isFlingGesture(VelocityEstimate estimate) {
final double minVelocity = minFlingVelocity ?? kMinFlingVelocity;
final double minDistance = minFlingDistance ?? kTouchSlop;
return estimate.pixelsPerSecond.dy.abs() > minVelocity && estimate.offset.dy.abs() > minDistance;
}
@override @override
bool get _hasSufficientPendingDragDeltaToAccept => _pendingDragOffset.dy.abs() > kTouchSlop; bool get _hasSufficientPendingDragDeltaToAccept => _pendingDragOffset.dy.abs() > kTouchSlop;
...@@ -400,6 +420,13 @@ class VerticalDragGestureRecognizer extends DragGestureRecognizer { ...@@ -400,6 +420,13 @@ class VerticalDragGestureRecognizer extends DragGestureRecognizer {
/// ///
/// * [HorizontalMultiDragGestureRecognizer] /// * [HorizontalMultiDragGestureRecognizer]
class HorizontalDragGestureRecognizer extends DragGestureRecognizer { class HorizontalDragGestureRecognizer extends DragGestureRecognizer {
@override
bool _isFlingGesture(VelocityEstimate estimate) {
final double minVelocity = minFlingVelocity ?? kMinFlingVelocity;
final double minDistance = minFlingDistance ?? kTouchSlop;
return estimate.pixelsPerSecond.dx.abs() > minVelocity && estimate.offset.dx.abs() > minDistance;
}
@override @override
bool get _hasSufficientPendingDragDeltaToAccept => _pendingDragOffset.dx.abs() > kTouchSlop; bool get _hasSufficientPendingDragDeltaToAccept => _pendingDragOffset.dx.abs() > kTouchSlop;
...@@ -420,6 +447,14 @@ class HorizontalDragGestureRecognizer extends DragGestureRecognizer { ...@@ -420,6 +447,14 @@ class HorizontalDragGestureRecognizer extends DragGestureRecognizer {
/// * [ImmediateMultiDragGestureRecognizer] /// * [ImmediateMultiDragGestureRecognizer]
/// * [DelayedMultiDragGestureRecognizer] /// * [DelayedMultiDragGestureRecognizer]
class PanGestureRecognizer extends DragGestureRecognizer { class PanGestureRecognizer extends DragGestureRecognizer {
@override
bool _isFlingGesture(VelocityEstimate estimate) {
final double minVelocity = minFlingVelocity ?? kMinFlingVelocity;
final double minDistance = minFlingDistance ?? kTouchSlop;
return estimate.pixelsPerSecond.distanceSquared > minVelocity * minVelocity &&
estimate.offset.distanceSquared > minDistance * minDistance;
}
@override @override
bool get _hasSufficientPendingDragDeltaToAccept { bool get _hasSufficientPendingDragDeltaToAccept {
return _pendingDragOffset.distance > kPanSlop; return _pendingDragOffset.distance > kPanSlop;
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'dart:math' as math; import 'dart:math' as math;
import 'package:flutter/gestures.dart' show kMinFlingVelocity;
import 'package:flutter/physics.dart'; import 'package:flutter/physics.dart';
import 'overscroll_indicator.dart'; import 'overscroll_indicator.dart';
...@@ -81,6 +82,12 @@ class BouncingScrollPhysics extends ScrollPhysics { ...@@ -81,6 +82,12 @@ class BouncingScrollPhysics extends ScrollPhysics {
} }
return null; return null;
} }
// The ballistic simulation here decelerates more slowly than the one for
// ClampingScrollPhysics so we require a more deliberate input gesture
// to trigger a fling.
@override
double get minFlingVelocity => kMinFlingVelocity * 2.0;
} }
/// Scroll physics for environments that prevent the scroll offset from reaching /// Scroll physics for environments that prevent the scroll offset from reaching
......
...@@ -106,6 +106,33 @@ abstract class ScrollPhysics { ...@@ -106,6 +106,33 @@ abstract class ScrollPhysics {
Tolerance get tolerance => parent?.tolerance ?? _kDefaultTolerance; Tolerance get tolerance => parent?.tolerance ?? _kDefaultTolerance;
/// The minimum distance an input pointer drag must have moved to
/// to be considered a scroll fling gesture.
///
/// This value is typically compared with the distance traveled along the
/// scrolling axis.
///
/// See also:
///
/// * [VelocityTracker.getVelocityEstimate], which computes the velocity
/// of a press-drag-release gesture.
double get minFlingDistance => parent?.minFlingDistance ?? kTouchSlop;
/// The minimum velocity for an input pointer drag to be considered a
/// scroll fling.
///
/// This value is typically compared with the magnitude of fling gesture's
/// velocity along the scrolling axis.
///
/// See also:
///
/// * [VelocityTracker.getVelocityEstimate], which computes the velocity
/// of a press-drag-release gesture.
double get minFlingVelocity => parent?.minFlingVelocity ?? kMinFlingVelocity;
/// Scroll fling velocity magnitudes will be clamped to this value.
double get maxFlingVelocity => parent?.maxFlingVelocity ?? kMaxFlingVelocity;
@override @override
String toString() { String toString() {
if (parent == null) if (parent == null)
......
...@@ -118,13 +118,14 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin ...@@ -118,13 +118,14 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin
ScrollPosition _position; ScrollPosition _position;
ScrollBehavior _configuration; ScrollBehavior _configuration;
ScrollPhysics _physics;
// only call this from places that will definitely trigger a rebuild // Only call this from places that will definitely trigger a rebuild.
void _updatePosition() { void _updatePosition() {
_configuration = ScrollConfiguration.of(context); _configuration = ScrollConfiguration.of(context);
ScrollPhysics physics = _configuration.getScrollPhysics(context); _physics = _configuration.getScrollPhysics(context);
if (config.physics != null) if (config.physics != null)
physics = config.physics.applyTo(physics); _physics = config.physics.applyTo(_physics);
final ScrollController controller = config.controller; final ScrollController controller = config.controller;
final ScrollPosition oldPosition = position; final ScrollPosition oldPosition = position;
if (oldPosition != null) { if (oldPosition != null) {
...@@ -135,8 +136,8 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin ...@@ -135,8 +136,8 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin
scheduleMicrotask(oldPosition.dispose); scheduleMicrotask(oldPosition.dispose);
} }
_position = controller?.createScrollPosition(physics, this, oldPosition) _position = controller?.createScrollPosition(_physics, this, oldPosition)
?? ScrollController.createDefaultScrollPosition(physics, this, oldPosition); ?? ScrollController.createDefaultScrollPosition(_physics, this, oldPosition);
assert(position != null); assert(position != null);
controller?.attach(position); controller?.attach(position);
} }
...@@ -201,7 +202,10 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin ...@@ -201,7 +202,10 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin
..onDown = _handleDragDown ..onDown = _handleDragDown
..onStart = _handleDragStart ..onStart = _handleDragStart
..onUpdate = _handleDragUpdate ..onUpdate = _handleDragUpdate
..onEnd = _handleDragEnd; ..onEnd = _handleDragEnd
..minFlingDistance = _physics?.minFlingDistance
..minFlingVelocity = _physics?.minFlingVelocity
..maxFlingVelocity = _physics?.maxFlingVelocity;
} }
}; };
break; break;
...@@ -212,7 +216,10 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin ...@@ -212,7 +216,10 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin
..onDown = _handleDragDown ..onDown = _handleDragDown
..onStart = _handleDragStart ..onStart = _handleDragStart
..onUpdate = _handleDragUpdate ..onUpdate = _handleDragUpdate
..onEnd = _handleDragEnd; ..onEnd = _handleDragEnd
..minFlingDistance = _physics?.minFlingDistance
..minFlingVelocity = _physics?.minFlingVelocity
..maxFlingVelocity = _physics?.maxFlingVelocity;
} }
}; };
break; break;
......
...@@ -102,7 +102,7 @@ void main() { ...@@ -102,7 +102,7 @@ void main() {
expect(showBottomSheetThenCalled, isFalse); expect(showBottomSheetThenCalled, isFalse);
expect(find.text('BottomSheet'), findsOneWidget); expect(find.text('BottomSheet'), findsOneWidget);
await tester.fling(find.text('BottomSheet'), const Offset(0.0, 20.0), 1000.0); await tester.fling(find.text('BottomSheet'), const Offset(0.0, 30.0), 1000.0);
await tester.pump(); // drain the microtask queue (Future completion callback) await tester.pump(); // drain the microtask queue (Future completion callback)
expect(showBottomSheetThenCalled, isTrue); expect(showBottomSheetThenCalled, isTrue);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment