diff --git a/packages/flutter/lib/src/gestures/drag.dart b/packages/flutter/lib/src/gestures/drag.dart index b00e20394b..8f0ab80963 100644 --- a/packages/flutter/lib/src/gestures/drag.dart +++ b/packages/flutter/lib/src/gestures/drag.dart @@ -16,19 +16,19 @@ enum DragState { typedef void GestureDragStartCallback(Point globalPosition); typedef void GestureDragUpdateCallback(double delta); -typedef void GestureDragEndCallback(Offset velocity); +typedef void GestureDragEndCallback(Velocity velocity); typedef void GesturePanStartCallback(Point globalPosition); typedef void GesturePanUpdateCallback(Offset delta); -typedef void GesturePanEndCallback(Offset velocity); +typedef void GesturePanEndCallback(Velocity velocity); typedef void _GesturePolymorphicUpdateCallback(T delta); -bool _isFlingGesture(Offset velocity) { +bool _isFlingGesture(Velocity velocity) { assert(velocity != null); - double velocitySquared = velocity.dx * velocity.dx + velocity.dy * velocity.dy; - return velocitySquared > kMinFlingVelocity * kMinFlingVelocity - && velocitySquared < kMaxFlingVelocity * kMaxFlingVelocity; + final double speedSquared = velocity.pixelsPerSecond.distanceSquared; + return speedSquared > kMinFlingVelocity * kMinFlingVelocity + && speedSquared < kMaxFlingVelocity * kMaxFlingVelocity; } abstract class _DragGestureRecognizer extends OneSequenceGestureRecognizer { @@ -110,11 +110,11 @@ abstract class _DragGestureRecognizer extends OneSequenceGest VelocityTracker tracker = _velocityTrackers[pointer]; assert(tracker != null); - Offset velocity = tracker.getVelocity(); + Velocity velocity = tracker.getVelocity(); if (velocity != null && _isFlingGesture(velocity)) onEnd(velocity); else - onEnd(Offset.zero); + onEnd(Velocity.zero); } _velocityTrackers.clear(); } diff --git a/packages/flutter/lib/src/gestures/multidrag.dart b/packages/flutter/lib/src/gestures/multidrag.dart index 9e6fd719db..1b9ab89b84 100644 --- a/packages/flutter/lib/src/gestures/multidrag.dart +++ b/packages/flutter/lib/src/gestures/multidrag.dart @@ -16,7 +16,7 @@ typedef Drag GestureMultiDragStartCallback(Point position); class Drag { void move(Offset offset) { } - void end(Offset velocity) { } + void end(Velocity velocity) { } void cancel() { } } diff --git a/packages/flutter/lib/src/gestures/velocity_tracker.dart b/packages/flutter/lib/src/gestures/velocity_tracker.dart index 439041e8b9..27fe9b4b32 100644 --- a/packages/flutter/lib/src/gestures/velocity_tracker.dart +++ b/packages/flutter/lib/src/gestures/velocity_tracker.dart @@ -208,6 +208,28 @@ class _LeastSquaresVelocityTrackerStrategy extends _VelocityTrackerStrategy { } +/// A velocity in two dimensions. +class Velocity { + const Velocity({ this.pixelsPerSecond }); + + /// A velocity that isn't moving at all. + static const Velocity zero = const Velocity(pixelsPerSecond: Offset.zero); + + /// The number of pixels per second of velocity in the x and y directions. + final Offset pixelsPerSecond; + + bool operator ==(dynamic other) { + if (other is! Velocity) + return false; + final Velocity typedOther = other; + return pixelsPerSecond == typedOther.pixelsPerSecond; + } + + int get hashCode => pixelsPerSecond.hashCode; + + String toString() => 'Velocity(${pixelsPerSecond.dx.toStringAsFixed(1)}, ${pixelsPerSecond.dy.toStringAsFixed(1)})'; +} + /// Computes a pointer velocity based on data from PointerMove events. /// /// The input data is provided by calling addPosition(). Adding data @@ -248,12 +270,14 @@ class VelocityTracker { /// /// getVelocity() will return null if no estimate is available or if /// the velocity is zero. - Offset getVelocity() { + Velocity getVelocity() { _Estimate estimate = _strategy.getEstimate(); if (estimate != null && estimate.degree >= 1) { - return new Offset( // convert from pixels/ms to pixels/s - estimate.xCoefficients[1] * 1000, - estimate.yCoefficients[1] * 1000 + return new Velocity( + pixelsPerSecond: new Offset( // convert from pixels/ms to pixels/s + estimate.xCoefficients[1] * 1000, + estimate.yCoefficients[1] * 1000 + ) ); } return null; diff --git a/packages/flutter/lib/src/material/bottom_sheet.dart b/packages/flutter/lib/src/material/bottom_sheet.dart index 398e33fa70..b7a29bbd3b 100644 --- a/packages/flutter/lib/src/material/bottom_sheet.dart +++ b/packages/flutter/lib/src/material/bottom_sheet.dart @@ -59,11 +59,11 @@ class _BottomSheetState extends State { config.animationController.value -= delta / (_childHeight ?? delta); } - void _handleDragEnd(Offset velocity) { + void _handleDragEnd(Velocity velocity) { if (_dismissUnderway) return; - if (velocity.dy > _kMinFlingVelocity) { - double flingVelocity = -velocity.dy / _childHeight; + if (velocity.pixelsPerSecond.dy > _kMinFlingVelocity) { + double flingVelocity = -velocity.pixelsPerSecond.dy / _childHeight; config.animationController.fling(velocity: flingVelocity); if (flingVelocity < 0.0) config.onClosing(); diff --git a/packages/flutter/lib/src/material/drawer.dart b/packages/flutter/lib/src/material/drawer.dart index 5661a8d93d..5a01d341e1 100644 --- a/packages/flutter/lib/src/material/drawer.dart +++ b/packages/flutter/lib/src/material/drawer.dart @@ -135,11 +135,11 @@ class DrawerControllerState extends State { _controller.value += delta / _width; } - void _settle(Offset velocity) { + void _settle(Velocity velocity) { if (_controller.isDismissed) return; - if (velocity.dx.abs() >= _kMinFlingVelocity) { - _controller.fling(velocity: velocity.dx / _width); + if (velocity.pixelsPerSecond.dx.abs() >= _kMinFlingVelocity) { + _controller.fling(velocity: velocity.pixelsPerSecond.dx / _width); } else if (_controller.value < 0.5) { close(); } else { diff --git a/packages/flutter/lib/src/material/slider.dart b/packages/flutter/lib/src/material/slider.dart index e5011d5708..39aa2fe1df 100644 --- a/packages/flutter/lib/src/material/slider.dart +++ b/packages/flutter/lib/src/material/slider.dart @@ -145,7 +145,7 @@ class _RenderSlider extends RenderConstrainedBox { } } - void _handleDragEnd(Offset velocity) { + void _handleDragEnd(Velocity velocity) { if (_active) { _active = false; _currentDragValue = 0.0; diff --git a/packages/flutter/lib/src/material/switch.dart b/packages/flutter/lib/src/material/switch.dart index 38f33bea55..2fe58fec8c 100644 --- a/packages/flutter/lib/src/material/switch.dart +++ b/packages/flutter/lib/src/material/switch.dart @@ -156,7 +156,7 @@ class _RenderSwitch extends RenderToggleable { } } - void _handleDragEnd(Offset velocity) { + void _handleDragEnd(Velocity velocity) { if (position.value >= 0.5) positionController.forward(); else diff --git a/packages/flutter/lib/src/material/time_picker.dart b/packages/flutter/lib/src/material/time_picker.dart index 94bd578639..e4a262d353 100644 --- a/packages/flutter/lib/src/material/time_picker.dart +++ b/packages/flutter/lib/src/material/time_picker.dart @@ -425,7 +425,7 @@ class _DialState extends State<_Dial> { _notifyOnChangedIfNeeded(); } - void _handlePanEnd(Offset velocity) { + void _handlePanEnd(Velocity velocity) { assert(_dragging); _dragging = false; _position = null; diff --git a/packages/flutter/lib/src/widgets/dismissable.dart b/packages/flutter/lib/src/widgets/dismissable.dart index a1f1edd420..fd0ec16434 100644 --- a/packages/flutter/lib/src/widgets/dismissable.dart +++ b/packages/flutter/lib/src/widgets/dismissable.dart @@ -182,9 +182,9 @@ class _DismissableState extends State { _dismissController.value = _dragExtent.abs() / _size.width; } - bool _isFlingGesture(Offset velocity) { - double vx = velocity.dx; - double vy = velocity.dy; + bool _isFlingGesture(Velocity velocity) { + double vx = velocity.pixelsPerSecond.dx; + double vy = velocity.pixelsPerSecond.dy; if (_directionIsYAxis) { if (vy.abs() - vx.abs() < _kMinFlingVelocityDelta) return false; @@ -211,7 +211,7 @@ class _DismissableState extends State { return false; } - void _handleDragEnd(Offset velocity) { + void _handleDragEnd(Velocity velocity) { if (!_isActive || _dismissController.isAnimating) return; setState(() { @@ -219,7 +219,7 @@ class _DismissableState extends State { if (_dismissController.isCompleted) { _startResizeAnimation(); } else if (_isFlingGesture(velocity)) { - double flingVelocity = _directionIsYAxis ? velocity.dy : velocity.dx; + double flingVelocity = _directionIsYAxis ? velocity.pixelsPerSecond.dy : velocity.pixelsPerSecond.dx; _dragExtent = flingVelocity.sign; _dismissController.fling(velocity: flingVelocity.abs() * _kFlingVelocityScale); } else if (_dismissController.value > _kDismissCardThreshold) { diff --git a/packages/flutter/lib/src/widgets/drag_target.dart b/packages/flutter/lib/src/widgets/drag_target.dart index fc1ceaa3df..5a1a7ee087 100644 --- a/packages/flutter/lib/src/widgets/drag_target.dart +++ b/packages/flutter/lib/src/widgets/drag_target.dart @@ -396,7 +396,7 @@ class _DragAvatar extends Drag { _position += offset; update(_position); } - void end(Offset velocity) { + void end(Velocity velocity) { finish(_DragEndKind.dropped); } void cancel() { diff --git a/packages/flutter/lib/src/widgets/gesture_detector.dart b/packages/flutter/lib/src/widgets/gesture_detector.dart index 7373fcd091..bc7c923d02 100644 --- a/packages/flutter/lib/src/widgets/gesture_detector.dart +++ b/packages/flutter/lib/src/widgets/gesture_detector.dart @@ -25,7 +25,8 @@ export 'package:flutter/gestures.dart' show GesturePanEndCallback, GestureScaleStartCallback, GestureScaleUpdateCallback, - GestureScaleEndCallback; + GestureScaleEndCallback, + Velocity; /// A widget that detects gestures. /// @@ -414,7 +415,7 @@ class _GestureSemantics extends OneChildRenderObjectWidget { if (onHorizontalDragUpdate != null) onHorizontalDragUpdate(delta); if (onHorizontalDragEnd != null) - onHorizontalDragEnd(Offset.zero); + onHorizontalDragEnd(Velocity.zero); } else { assert(_watchingPans); if (onPanStart != null) @@ -422,7 +423,7 @@ class _GestureSemantics extends OneChildRenderObjectWidget { if (onPanUpdate != null) onPanUpdate(new Offset(delta, 0.0)); if (onPanEnd != null) - onPanEnd(Offset.zero); + onPanEnd(Velocity.zero); } } @@ -433,7 +434,7 @@ class _GestureSemantics extends OneChildRenderObjectWidget { if (onVerticalDragUpdate != null) onVerticalDragUpdate(delta); if (onVerticalDragEnd != null) - onVerticalDragEnd(Offset.zero); + onVerticalDragEnd(Velocity.zero); } else { assert(_watchingPans); if (onPanStart != null) @@ -441,7 +442,7 @@ class _GestureSemantics extends OneChildRenderObjectWidget { if (onPanUpdate != null) onPanUpdate(new Offset(0.0, delta)); if (onPanEnd != null) - onPanEnd(Offset.zero); + onPanEnd(Velocity.zero); } } diff --git a/packages/flutter/lib/src/widgets/scrollable.dart b/packages/flutter/lib/src/widgets/scrollable.dart index c26391f8d4..585a8580f1 100644 --- a/packages/flutter/lib/src/widgets/scrollable.dart +++ b/packages/flutter/lib/src/widgets/scrollable.dart @@ -450,8 +450,8 @@ abstract class ScrollableState extends State { scrollBy(pixelOffsetToScrollOffset(delta)); } - Future _handleDragEnd(Offset velocity) { - double scrollVelocity = pixelDeltaToScrollOffset(velocity) / Duration.MILLISECONDS_PER_SECOND; + Future _handleDragEnd(Velocity velocity) { + double scrollVelocity = pixelDeltaToScrollOffset(velocity.pixelsPerSecond) / Duration.MILLISECONDS_PER_SECOND; // The gesture velocity properties are pixels/second, config min,max limits are pixels/ms return fling(scrollVelocity.clamp(-kMaxFlingVelocity, kMaxFlingVelocity)).then((_) { dispatchOnScrollEnd(); diff --git a/packages/flutter/lib/src/widgets/semantics_debugger.dart b/packages/flutter/lib/src/widgets/semantics_debugger.dart index 854505297a..0d4cefa1ef 100644 --- a/packages/flutter/lib/src/widgets/semantics_debugger.dart +++ b/packages/flutter/lib/src/widgets/semantics_debugger.dart @@ -58,7 +58,7 @@ class _SemanticsDebuggerState extends State { _lastPointerDownLocation = null; }); } - void _handlePanEnd(Offset velocity) { + void _handlePanEnd(Velocity velocity) { assert(_lastPointerDownLocation != null); _SemanticsDebuggerListener.instance.handlePanEnd(_lastPointerDownLocation, velocity); setState(() { @@ -329,16 +329,18 @@ class _SemanticsDebuggerListener implements mojom.SemanticsListener { void handleLongPress(Point position) { _server.longPress(_hitTest(position, (_SemanticsDebuggerEntry entry) => entry.canBeLongPressed)?.id ?? 0); } - void handlePanEnd(Point position, Offset velocity) { - if (velocity.dx.abs() == velocity.dy.abs()) + void handlePanEnd(Point position, Velocity velocity) { + double vx = velocity.pixelsPerSecond.dx; + double vy = velocity.pixelsPerSecond.dy; + if (vx.abs() == vy.abs()) return; - if (velocity.dx.abs() > velocity.dy.abs()) { - if (velocity.dx.sign < 0) + if (vx.abs() > vy.abs()) { + if (vx.sign < 0) _server.scrollLeft(_hitTest(position, (_SemanticsDebuggerEntry entry) => entry.canBeScrolledHorizontally)?.id ?? 0); else _server.scrollRight(_hitTest(position, (_SemanticsDebuggerEntry entry) => entry.canBeScrolledHorizontally)?.id ?? 0); } else { - if (velocity.dy.sign < 0) + if (vy.sign < 0) _server.scrollUp(_hitTest(position, (_SemanticsDebuggerEntry entry) => entry.canBeScrolledVertically)?.id ?? 0); else _server.scrollDown(_hitTest(position, (_SemanticsDebuggerEntry entry) => entry.canBeScrolledVertically)?.id ?? 0); diff --git a/packages/flutter/test/gestures/scroll_test.dart b/packages/flutter/test/gestures/scroll_test.dart index 649727c5ca..1b3c283708 100644 --- a/packages/flutter/test/gestures/scroll_test.dart +++ b/packages/flutter/test/gestures/scroll_test.dart @@ -30,7 +30,7 @@ void main() { }; bool didEndPan = false; - pan.onEnd = (Offset velocity) { + pan.onEnd = (Velocity velocity) { didEndPan = true; }; diff --git a/packages/flutter/test/gestures/velocity_tracker_test.dart b/packages/flutter/test/gestures/velocity_tracker_test.dart index 6bd13bc7ff..cc7c7325b0 100644 --- a/packages/flutter/test/gestures/velocity_tracker_test.dart +++ b/packages/flutter/test/gestures/velocity_tracker_test.dart @@ -12,10 +12,10 @@ bool _withinTolerance(double actual, double expected) { return diff.abs() < kTolerance; } -bool _checkVelocity(Offset actual, Offset expected) { +bool _checkVelocity(Velocity actual, Offset expected) { return (actual != null) - && _withinTolerance(actual.dx, expected.dx) - && _withinTolerance(actual.dy, expected.dy); + && _withinTolerance(actual.pixelsPerSecond.dx, expected.dx) + && _withinTolerance(actual.pixelsPerSecond.dy, expected.dy); } void main() { diff --git a/packages/flutter/test/widget/gesture_detector_test.dart b/packages/flutter/test/widget/gesture_detector_test.dart index 593fe42e30..47fe00d3cb 100644 --- a/packages/flutter/test/widget/gesture_detector_test.dart +++ b/packages/flutter/test/widget/gesture_detector_test.dart @@ -22,7 +22,7 @@ void main() { onVerticalDragUpdate: (double scrollDelta) { updatedDragDelta = scrollDelta; }, - onVerticalDragEnd: (Offset velocity) { + onVerticalDragEnd: (Velocity velocity) { didEndDrag = true; }, child: new Container( @@ -73,9 +73,9 @@ void main() { Widget widget = new GestureDetector( onVerticalDragUpdate: (double delta) { dragDistance += delta; }, - onVerticalDragEnd: (Offset velocity) { gestureCount += 1; }, + onVerticalDragEnd: (Velocity velocity) { gestureCount += 1; }, onHorizontalDragUpdate: (_) { fail("gesture should not match"); }, - onHorizontalDragEnd: (Offset velocity) { fail("gesture should not match"); }, + onHorizontalDragEnd: (Velocity velocity) { fail("gesture should not match"); }, child: new Container( decoration: const BoxDecoration( backgroundColor: const Color(0xFF00FF00)