diff --git a/packages/flutter/lib/gestures.dart b/packages/flutter/lib/gestures.dart index 498ddaa6f1..162b61f409 100644 --- a/packages/flutter/lib/gestures.dart +++ b/packages/flutter/lib/gestures.dart @@ -12,10 +12,12 @@ export 'src/gestures/binding.dart'; export 'src/gestures/constants.dart'; export 'src/gestures/converter.dart'; export 'src/gestures/drag.dart'; +export 'src/gestures/drag_details.dart'; export 'src/gestures/events.dart'; export 'src/gestures/hit_test.dart'; export 'src/gestures/long_press.dart'; export 'src/gestures/lsq_solver.dart'; +export 'src/gestures/monodrag.dart'; export 'src/gestures/multidrag.dart'; export 'src/gestures/multitap.dart'; export 'src/gestures/pointer_router.dart'; diff --git a/packages/flutter/lib/src/gestures/drag.dart b/packages/flutter/lib/src/gestures/drag.dart index e5383abe6f..4e0cf4f7cd 100644 --- a/packages/flutter/lib/src/gestures/drag.dart +++ b/packages/flutter/lib/src/gestures/drag.dart @@ -2,470 +2,28 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter/foundation.dart'; +import 'drag_details.dart'; -import 'arena.dart'; -import 'constants.dart'; -import 'events.dart'; -import 'recognizer.dart'; -import 'velocity_tracker.dart'; +/// Interface for objects that receive updates about drags. +/// +/// This interface is used in various ways. For example, +/// [MultiDragGestureRecognizer] uses it to update its clients when it +/// recognizes a gesture. Similarly, the scrolling infrastructure in the widgets +/// library uses it to notify the [DragScrollActivity] when the user drags the +/// scrollable. +abstract class Drag { + /// The pointer has moved. + void update(DragUpdateDetails details) { } -enum _DragState { - ready, - possible, - accepted, -} - -/// Details object for callbacks that use [GestureDragDownCallback]. -/// -/// See also: -/// -/// * [DragGestureRecognizer.onDown], which uses [GestureDragDownCallback]. -/// * [DragStartDetails], the details for [GestureDragStartCallback]. -/// * [DragUpdateDetails], the details for [GestureDragUpdateCallback]. -/// * [DragEndDetails], the details for [GestureDragEndCallback]. -class DragDownDetails { - /// Creates details for a [GestureDragDownCallback]. - /// - /// The [globalPosition] argument must not be null. - DragDownDetails({ this.globalPosition: Offset.zero }) { - assert(globalPosition != null); - } - - /// The global position at which the pointer contacted the screen. - /// - /// Defaults to the origin if not specified in the constructor. - final Offset globalPosition; - - @override - String toString() => '$runtimeType($globalPosition)'; -} - -/// Signature for when a pointer has contacted the screen and might begin to -/// move. -/// -/// The `details` object provides the position of the touch. -/// -/// See [DragGestureRecognizer.onDown]. -typedef void GestureDragDownCallback(DragDownDetails details); - -/// Details object for callbacks that use [GestureDragStartCallback]. -/// -/// See also: -/// -/// * [DragGestureRecognizer.onStart], which uses [GestureDragStartCallback]. -/// * [DragDownDetails], the details for [GestureDragDownCallback]. -/// * [DragUpdateDetails], the details for [GestureDragUpdateCallback]. -/// * [DragEndDetails], the details for [GestureDragEndCallback]. -class DragStartDetails { - /// Creates details for a [GestureDragStartCallback]. - /// - /// The [globalPosition] argument must not be null. - DragStartDetails({ this.globalPosition: Offset.zero }) { - assert(globalPosition != null); - } - - /// The global position at which the pointer contacted the screen. - /// - /// Defaults to the origin if not specified in the constructor. - final Offset globalPosition; - - @override - String toString() => '$runtimeType($globalPosition)'; -} - -/// Signature for when a pointer has contacted the screen and has begun to move. -/// -/// The `details` object provides the position of the touch when it first -/// touched the surface. -/// -/// See [DragGestureRecognizer.onStart]. -typedef void GestureDragStartCallback(DragStartDetails details); - -/// Details object for callbacks that use [GestureDragUpdateCallback]. -/// -/// See also: -/// -/// * [DragGestureRecognizer.onUpdate], which uses [GestureDragUpdateCallback]. -/// * [DragDownDetails], the details for [GestureDragDownCallback]. -/// * [DragStartDetails], the details for [GestureDragStartCallback]. -/// * [DragEndDetails], the details for [GestureDragEndCallback]. -class DragUpdateDetails { - /// Creates details for a [DragUpdateDetails]. - /// - /// The [delta] argument must not be null. - /// - /// If [primaryDelta] is non-null, then its value must match one of the - /// coordinates of [delta] and the other coordinate must be zero. - /// - /// The [globalPosition] argument must be provided and must not be null. - DragUpdateDetails({ - this.delta: Offset.zero, - this.primaryDelta, - @required this.globalPosition - }) { - assert(primaryDelta == null - || (primaryDelta == delta.dx && delta.dy == 0.0) - || (primaryDelta == delta.dy && delta.dx == 0.0)); - } - - /// The amount the pointer has moved since the previous update. - /// - /// If the [GestureDragUpdateCallback] is for a one-dimensional drag (e.g., - /// a horizontal or vertical drag), then this offset contains only the delta - /// in that direction (i.e., the coordinate in the other direction is zero). - /// - /// Defaults to zero if not specified in the constructor. - final Offset delta; - - /// The amount the pointer has moved along the primary axis since the previous - /// update. - /// - /// If the [GestureDragUpdateCallback] is for a one-dimensional drag (e.g., - /// a horizontal or vertical drag), then this value contains the component of - /// [delta] along the primary axis (e.g., horizontal or vertical, - /// respectively). Otherwise, if the [GestureDragUpdateCallback] is for a - /// two-dimensional drag (e.g., a pan), then this value is null. - /// - /// Defaults to null if not specified in the constructor. - final double primaryDelta; - - /// The pointer's global position when it triggered this update. - final Offset globalPosition; - - @override - String toString() => '$runtimeType($delta)'; -} - -/// Signature for when a pointer that is in contact with the screen and moving -/// has moved again. -/// -/// The `details` object provides the position of the touch and the distance it -/// has travelled since the last update. -/// -/// See [DragGestureRecognizer.onUpdate]. -typedef void GestureDragUpdateCallback(DragUpdateDetails details); - -/// Details object for callbacks that use [GestureDragEndCallback]. -/// -/// See also: -/// -/// * [DragGestureRecognizer.onEnd], which uses [GestureDragEndCallback]. -/// * [DragDownDetails], the details for [GestureDragDownCallback]. -/// * [DragStartDetails], the details for [GestureDragStartCallback]. -/// * [DragUpdateDetails], the details for [GestureDragUpdateCallback]. -class DragEndDetails { - /// Creates details for a [GestureDragEndCallback]. - /// - /// The [velocity] argument must not be null. - DragEndDetails({ - this.velocity: Velocity.zero, - this.primaryVelocity, - }) { - assert(velocity != null); - assert(primaryVelocity == null - || primaryVelocity == velocity.pixelsPerSecond.dx - || primaryVelocity == velocity.pixelsPerSecond.dy); - } - - /// The velocity the pointer was moving when it stopped contacting the screen. - /// - /// Defaults to zero if not specified in the constructor. - final Velocity velocity; - - /// The velocity the pointer was moving along the primary axis when it stopped - /// contacting the screen, in logical pixels per second. - /// - /// If the [GestureDragEndCallback] is for a one-dimensional drag (e.g., a - /// horizontal or vertical drag), then this value contains the component of - /// [velocity] along the primary axis (e.g., horizontal or vertical, - /// respectively). Otherwise, if the [GestureDragEndCallback] is for a - /// two-dimensional drag (e.g., a pan), then this value is null. - /// - /// Defaults to null if not specified in the constructor. - final double primaryVelocity; - - @override - String toString() => '$runtimeType($velocity)'; -} - -/// Signature for when a pointer that was previously in contact with the screen -/// and moving is no longer in contact with the screen. -/// -/// The velocity at which the pointer was moving when it stopped contacting -/// the screen is available in the `details`. -/// -/// See [DragGestureRecognizer.onEnd]. -typedef void GestureDragEndCallback(DragEndDetails details); - -/// Signature for when the pointer that previously triggered a -/// [GestureDragDownCallback] did not complete. -/// -/// See [DragGestureRecognizer.onCancel]. -typedef void GestureDragCancelCallback(); - -/// Recognizes movement. -/// -/// In contrast to [MultiDragGestureRecognizer], [DragGestureRecognizer] -/// recognizes a single gesture sequence for all the pointers it watches, which -/// means that the recognizer has at most one drag sequence active at any given -/// time regardless of how many pointers are in contact with the screen. -/// -/// [DragGestureRecognizer] is not intended to be used directly. Instead, -/// consider using one of its subclasses to recognize specific types for drag -/// gestures. -/// -/// See also: -/// -/// * [HorizontalDragGestureRecognizer] -/// * [VerticalDragGestureRecognizer] -/// * [PanGestureRecognizer] -abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { - /// A pointer has contacted the screen and might begin to move. - /// - /// The position of the pointer is provided in the callback's `details` - /// argument, which is a [DragDownDetails] object. - GestureDragDownCallback onDown; - - /// A pointer has contacted the screen and has begun to move. - /// - /// The position of the pointer is provided in the callback's `details` - /// argument, which is a [DragStartDetails] object. - GestureDragStartCallback onStart; - - /// A pointer that is in contact with the screen and moving has moved again. - /// - /// The distance travelled by the pointer since the last update is provided in - /// the callback's `details` argument, which is a [DragUpdateDetails] object. - GestureDragUpdateCallback onUpdate; - - /// A pointer that was previously in contact with the screen and moving is no - /// longer in contact with the screen and was moving at a specific velocity - /// when it stopped contacting the screen. - /// - /// The velocity is provided in the callback's `details` argument, which is a - /// [DragEndDetails] object. - GestureDragEndCallback onEnd; - - /// The pointer that previously triggered [onDown] did not complete. - 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; - Offset _initialPosition; - Offset _pendingDragOffset; - - bool _isFlingGesture(VelocityEstimate estimate); - Offset _getDeltaForDetails(Offset delta); - double _getPrimaryValueFromOffset(Offset value); - bool get _hasSufficientPendingDragDeltaToAccept; - - final Map _velocityTrackers = {}; - - @override - void addPointer(PointerEvent event) { - startTrackingPointer(event.pointer); - _velocityTrackers[event.pointer] = new VelocityTracker(); - if (_state == _DragState.ready) { - _state = _DragState.possible; - _initialPosition = event.position; - _pendingDragOffset = Offset.zero; - if (onDown != null) - invokeCallback('onDown', () => onDown(new DragDownDetails(globalPosition: _initialPosition))); // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504 - } - } - - @override - void handleEvent(PointerEvent event) { - assert(_state != _DragState.ready); - if (event is PointerMoveEvent) { - final VelocityTracker tracker = _velocityTrackers[event.pointer]; - assert(tracker != null); - tracker.addPosition(event.timeStamp, event.position); - final Offset delta = event.delta; - if (_state == _DragState.accepted) { - if (onUpdate != null) { - invokeCallback('onUpdate', () => onUpdate(new DragUpdateDetails( // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504 - delta: _getDeltaForDetails(delta), - primaryDelta: _getPrimaryValueFromOffset(delta), - globalPosition: event.position, - ))); - } - } else { - _pendingDragOffset += delta; - if (_hasSufficientPendingDragDeltaToAccept) - resolve(GestureDisposition.accepted); - } - } - stopTrackingIfPointerNoLongerDown(event); - } - - @override - void acceptGesture(int pointer) { - if (_state != _DragState.accepted) { - _state = _DragState.accepted; - final Offset delta = _pendingDragOffset; - _pendingDragOffset = Offset.zero; - if (onStart != null) { - invokeCallback('onStart', () => onStart(new DragStartDetails( // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504 - globalPosition: _initialPosition, - ))); - } - if (delta != Offset.zero && onUpdate != null) { - invokeCallback('onUpdate', () => onUpdate(new DragUpdateDetails( // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504 - delta: _getDeltaForDetails(delta), - primaryDelta: _getPrimaryValueFromOffset(delta), - globalPosition: _initialPosition, - ))); - } - } - } - - @override - void rejectGesture(int pointer) { - stopTrackingPointer(pointer); - } - - @override - void didStopTrackingLastPointer(int pointer) { - if (_state == _DragState.possible) { - resolve(GestureDisposition.rejected); - _state = _DragState.ready; - if (onCancel != null) - invokeCallback('onCancel', onCancel); // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504 - return; - } - final bool wasAccepted = (_state == _DragState.accepted); - _state = _DragState.ready; - if (wasAccepted && onEnd != null) { - final VelocityTracker tracker = _velocityTrackers[pointer]; - assert(tracker != null); - - final VelocityEstimate estimate = tracker.getVelocityEstimate(); - if (estimate != null && _isFlingGesture(estimate)) { - final Velocity velocity = new Velocity(pixelsPerSecond: estimate.pixelsPerSecond) - .clampMagnitude(minFlingVelocity ?? kMinFlingVelocity, maxFlingVelocity ?? kMaxFlingVelocity); - invokeCallback('onEnd', () => onEnd(new DragEndDetails( // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504 - velocity: velocity, - primaryVelocity: _getPrimaryValueFromOffset(velocity.pixelsPerSecond), - ))); - } else { - invokeCallback('onEnd', () => onEnd(new DragEndDetails( // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504 - velocity: Velocity.zero, - primaryVelocity: 0.0, - ))); - } - } - _velocityTrackers.clear(); - } - - @override - void dispose() { - _velocityTrackers.clear(); - super.dispose(); - } -} - -/// Recognizes movement in the vertical direction. -/// -/// Used for vertical scrolling. -/// -/// See also: -/// -/// * [VerticalMultiDragGestureRecognizer] -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 - bool get _hasSufficientPendingDragDeltaToAccept => _pendingDragOffset.dy.abs() > kTouchSlop; - - @override - Offset _getDeltaForDetails(Offset delta) => new Offset(0.0, delta.dy); - - @override - double _getPrimaryValueFromOffset(Offset value) => value.dy; - - @override - String toStringShort() => 'vertical drag'; -} - -/// Recognizes movement in the horizontal direction. -/// -/// Used for horizontal scrolling. -/// -/// See also: -/// -/// * [HorizontalMultiDragGestureRecognizer] -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 - bool get _hasSufficientPendingDragDeltaToAccept => _pendingDragOffset.dx.abs() > kTouchSlop; - - @override - Offset _getDeltaForDetails(Offset delta) => new Offset(delta.dx, 0.0); - - @override - double _getPrimaryValueFromOffset(Offset value) => value.dx; - - @override - String toStringShort() => 'horizontal drag'; -} - -/// Recognizes movement both horizontally and vertically. -/// -/// See also: -/// -/// * [ImmediateMultiDragGestureRecognizer] -/// * [DelayedMultiDragGestureRecognizer] -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 - bool get _hasSufficientPendingDragDeltaToAccept { - return _pendingDragOffset.distance > kPanSlop; - } - - @override - Offset _getDeltaForDetails(Offset delta) => delta; - - @override - double _getPrimaryValueFromOffset(Offset value) => null; - - @override - String toStringShort() => 'pan'; + /// The pointer is no longer in contact with the screen. + /// + /// The velocity at which the pointer was moving when it stopped contacting + /// the screen is available in the `details`. + void end(DragEndDetails details) { } + + /// The input from the pointer is no longer directed towards this receiver. + /// + /// For example, the user might have been interrupted by a system-modal dialog + /// in the middle of the drag. + void cancel() { } } diff --git a/packages/flutter/lib/src/gestures/drag_details.dart b/packages/flutter/lib/src/gestures/drag_details.dart new file mode 100644 index 0000000000..476de8f8de --- /dev/null +++ b/packages/flutter/lib/src/gestures/drag_details.dart @@ -0,0 +1,182 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:ui' show Offset; + +import 'package:flutter/foundation.dart'; + +import 'velocity_tracker.dart'; + +/// Details object for callbacks that use [GestureDragDownCallback]. +/// +/// See also: +/// +/// * [DragGestureRecognizer.onDown], which uses [GestureDragDownCallback]. +/// * [DragStartDetails], the details for [GestureDragStartCallback]. +/// * [DragUpdateDetails], the details for [GestureDragUpdateCallback]. +/// * [DragEndDetails], the details for [GestureDragEndCallback]. +class DragDownDetails { + /// Creates details for a [GestureDragDownCallback]. + /// + /// The [globalPosition] argument must not be null. + DragDownDetails({ this.globalPosition: Offset.zero }) { + assert(globalPosition != null); + } + + /// The global position at which the pointer contacted the screen. + /// + /// Defaults to the origin if not specified in the constructor. + final Offset globalPosition; + + @override + String toString() => '$runtimeType($globalPosition)'; +} + +/// Signature for when a pointer has contacted the screen and might begin to +/// move. +/// +/// The `details` object provides the position of the touch. +/// +/// See [DragGestureRecognizer.onDown]. +typedef void GestureDragDownCallback(DragDownDetails details); + +/// Details object for callbacks that use [GestureDragStartCallback]. +/// +/// See also: +/// +/// * [DragGestureRecognizer.onStart], which uses [GestureDragStartCallback]. +/// * [DragDownDetails], the details for [GestureDragDownCallback]. +/// * [DragUpdateDetails], the details for [GestureDragUpdateCallback]. +/// * [DragEndDetails], the details for [GestureDragEndCallback]. +class DragStartDetails { + /// Creates details for a [GestureDragStartCallback]. + /// + /// The [globalPosition] argument must not be null. + DragStartDetails({ this.globalPosition: Offset.zero }) { + assert(globalPosition != null); + } + + /// The global position at which the pointer contacted the screen. + /// + /// Defaults to the origin if not specified in the constructor. + final Offset globalPosition; + + @override + String toString() => '$runtimeType($globalPosition)'; +} + +/// Signature for when a pointer has contacted the screen and has begun to move. +/// +/// The `details` object provides the position of the touch when it first +/// touched the surface. +/// +/// See [DragGestureRecognizer.onStart]. +typedef void GestureDragStartCallback(DragStartDetails details); + +/// Details object for callbacks that use [GestureDragUpdateCallback]. +/// +/// See also: +/// +/// * [DragGestureRecognizer.onUpdate], which uses [GestureDragUpdateCallback]. +/// * [DragDownDetails], the details for [GestureDragDownCallback]. +/// * [DragStartDetails], the details for [GestureDragStartCallback]. +/// * [DragEndDetails], the details for [GestureDragEndCallback]. +class DragUpdateDetails { + /// Creates details for a [DragUpdateDetails]. + /// + /// The [delta] argument must not be null. + /// + /// If [primaryDelta] is non-null, then its value must match one of the + /// coordinates of [delta] and the other coordinate must be zero. + /// + /// The [globalPosition] argument must be provided and must not be null. + DragUpdateDetails({ + this.delta: Offset.zero, + this.primaryDelta, + @required this.globalPosition + }) { + assert(primaryDelta == null + || (primaryDelta == delta.dx && delta.dy == 0.0) + || (primaryDelta == delta.dy && delta.dx == 0.0)); + } + + /// The amount the pointer has moved since the previous update. + /// + /// If the [GestureDragUpdateCallback] is for a one-dimensional drag (e.g., + /// a horizontal or vertical drag), then this offset contains only the delta + /// in that direction (i.e., the coordinate in the other direction is zero). + /// + /// Defaults to zero if not specified in the constructor. + final Offset delta; + + /// The amount the pointer has moved along the primary axis since the previous + /// update. + /// + /// If the [GestureDragUpdateCallback] is for a one-dimensional drag (e.g., + /// a horizontal or vertical drag), then this value contains the component of + /// [delta] along the primary axis (e.g., horizontal or vertical, + /// respectively). Otherwise, if the [GestureDragUpdateCallback] is for a + /// two-dimensional drag (e.g., a pan), then this value is null. + /// + /// Defaults to null if not specified in the constructor. + final double primaryDelta; + + /// The pointer's global position when it triggered this update. + final Offset globalPosition; + + @override + String toString() => '$runtimeType($delta)'; +} + +/// Signature for when a pointer that is in contact with the screen and moving +/// has moved again. +/// +/// The `details` object provides the position of the touch and the distance it +/// has travelled since the last update. +/// +/// See [DragGestureRecognizer.onUpdate]. +typedef void GestureDragUpdateCallback(DragUpdateDetails details); + +/// Details object for callbacks that use [GestureDragEndCallback]. +/// +/// See also: +/// +/// * [DragGestureRecognizer.onEnd], which uses [GestureDragEndCallback]. +/// * [DragDownDetails], the details for [GestureDragDownCallback]. +/// * [DragStartDetails], the details for [GestureDragStartCallback]. +/// * [DragUpdateDetails], the details for [GestureDragUpdateCallback]. +class DragEndDetails { + /// Creates details for a [GestureDragEndCallback]. + /// + /// The [velocity] argument must not be null. + DragEndDetails({ + this.velocity: Velocity.zero, + this.primaryVelocity, + }) { + assert(velocity != null); + assert(primaryVelocity == null + || primaryVelocity == velocity.pixelsPerSecond.dx + || primaryVelocity == velocity.pixelsPerSecond.dy); + } + + /// The velocity the pointer was moving when it stopped contacting the screen. + /// + /// Defaults to zero if not specified in the constructor. + final Velocity velocity; + + /// The velocity the pointer was moving along the primary axis when it stopped + /// contacting the screen, in logical pixels per second. + /// + /// If the [GestureDragEndCallback] is for a one-dimensional drag (e.g., a + /// horizontal or vertical drag), then this value contains the component of + /// [velocity] along the primary axis (e.g., horizontal or vertical, + /// respectively). Otherwise, if the [GestureDragEndCallback] is for a + /// two-dimensional drag (e.g., a pan), then this value is null. + /// + /// Defaults to null if not specified in the constructor. + final double primaryVelocity; + + @override + String toString() => '$runtimeType($velocity)'; +} diff --git a/packages/flutter/lib/src/gestures/monodrag.dart b/packages/flutter/lib/src/gestures/monodrag.dart new file mode 100644 index 0000000000..a5a86c9713 --- /dev/null +++ b/packages/flutter/lib/src/gestures/monodrag.dart @@ -0,0 +1,297 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'arena.dart'; +import 'constants.dart'; +import 'drag_details.dart'; +import 'events.dart'; +import 'recognizer.dart'; +import 'velocity_tracker.dart'; + +enum _DragState { + ready, + possible, + accepted, +} + +/// Signature for when a pointer that was previously in contact with the screen +/// and moving is no longer in contact with the screen. +/// +/// The velocity at which the pointer was moving when it stopped contacting +/// the screen is available in the `details`. +/// +/// See [DragGestureRecognizer.onEnd]. +typedef void GestureDragEndCallback(DragEndDetails details); + +/// Signature for when the pointer that previously triggered a +/// [GestureDragDownCallback] did not complete. +/// +/// See [DragGestureRecognizer.onCancel]. +typedef void GestureDragCancelCallback(); + +/// Recognizes movement. +/// +/// In contrast to [MultiDragGestureRecognizer], [DragGestureRecognizer] +/// recognizes a single gesture sequence for all the pointers it watches, which +/// means that the recognizer has at most one drag sequence active at any given +/// time regardless of how many pointers are in contact with the screen. +/// +/// [DragGestureRecognizer] is not intended to be used directly. Instead, +/// consider using one of its subclasses to recognize specific types for drag +/// gestures. +/// +/// See also: +/// +/// * [HorizontalDragGestureRecognizer] +/// * [VerticalDragGestureRecognizer] +/// * [PanGestureRecognizer] +abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { + /// A pointer has contacted the screen and might begin to move. + /// + /// The position of the pointer is provided in the callback's `details` + /// argument, which is a [DragDownDetails] object. + GestureDragDownCallback onDown; + + /// A pointer has contacted the screen and has begun to move. + /// + /// The position of the pointer is provided in the callback's `details` + /// argument, which is a [DragStartDetails] object. + GestureDragStartCallback onStart; + + /// A pointer that is in contact with the screen and moving has moved again. + /// + /// The distance travelled by the pointer since the last update is provided in + /// the callback's `details` argument, which is a [DragUpdateDetails] object. + GestureDragUpdateCallback onUpdate; + + /// A pointer that was previously in contact with the screen and moving is no + /// longer in contact with the screen and was moving at a specific velocity + /// when it stopped contacting the screen. + /// + /// The velocity is provided in the callback's `details` argument, which is a + /// [DragEndDetails] object. + GestureDragEndCallback onEnd; + + /// The pointer that previously triggered [onDown] did not complete. + 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; + Offset _initialPosition; + Offset _pendingDragOffset; + + bool _isFlingGesture(VelocityEstimate estimate); + Offset _getDeltaForDetails(Offset delta); + double _getPrimaryValueFromOffset(Offset value); + bool get _hasSufficientPendingDragDeltaToAccept; + + final Map _velocityTrackers = {}; + + @override + void addPointer(PointerEvent event) { + startTrackingPointer(event.pointer); + _velocityTrackers[event.pointer] = new VelocityTracker(); + if (_state == _DragState.ready) { + _state = _DragState.possible; + _initialPosition = event.position; + _pendingDragOffset = Offset.zero; + if (onDown != null) + invokeCallback('onDown', () => onDown(new DragDownDetails(globalPosition: _initialPosition))); // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504 + } + } + + @override + void handleEvent(PointerEvent event) { + assert(_state != _DragState.ready); + if (event is PointerMoveEvent) { + final VelocityTracker tracker = _velocityTrackers[event.pointer]; + assert(tracker != null); + tracker.addPosition(event.timeStamp, event.position); + final Offset delta = event.delta; + if (_state == _DragState.accepted) { + if (onUpdate != null) { + invokeCallback('onUpdate', () => onUpdate(new DragUpdateDetails( // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504 + delta: _getDeltaForDetails(delta), + primaryDelta: _getPrimaryValueFromOffset(delta), + globalPosition: event.position, + ))); + } + } else { + _pendingDragOffset += delta; + if (_hasSufficientPendingDragDeltaToAccept) + resolve(GestureDisposition.accepted); + } + } + stopTrackingIfPointerNoLongerDown(event); + } + + @override + void acceptGesture(int pointer) { + if (_state != _DragState.accepted) { + _state = _DragState.accepted; + final Offset delta = _pendingDragOffset; + _pendingDragOffset = Offset.zero; + if (onStart != null) { + invokeCallback('onStart', () => onStart(new DragStartDetails( // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504 + globalPosition: _initialPosition, + ))); + } + if (delta != Offset.zero && onUpdate != null) { + invokeCallback('onUpdate', () => onUpdate(new DragUpdateDetails( // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504 + delta: _getDeltaForDetails(delta), + primaryDelta: _getPrimaryValueFromOffset(delta), + globalPosition: _initialPosition, + ))); + } + } + } + + @override + void rejectGesture(int pointer) { + stopTrackingPointer(pointer); + } + + @override + void didStopTrackingLastPointer(int pointer) { + if (_state == _DragState.possible) { + resolve(GestureDisposition.rejected); + _state = _DragState.ready; + if (onCancel != null) + invokeCallback('onCancel', onCancel); // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504 + return; + } + final bool wasAccepted = (_state == _DragState.accepted); + _state = _DragState.ready; + if (wasAccepted && onEnd != null) { + final VelocityTracker tracker = _velocityTrackers[pointer]; + assert(tracker != null); + + final VelocityEstimate estimate = tracker.getVelocityEstimate(); + if (estimate != null && _isFlingGesture(estimate)) { + final Velocity velocity = new Velocity(pixelsPerSecond: estimate.pixelsPerSecond) + .clampMagnitude(minFlingVelocity ?? kMinFlingVelocity, maxFlingVelocity ?? kMaxFlingVelocity); + invokeCallback('onEnd', () => onEnd(new DragEndDetails( // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504 + velocity: velocity, + primaryVelocity: _getPrimaryValueFromOffset(velocity.pixelsPerSecond), + ))); + } else { + invokeCallback('onEnd', () => onEnd(new DragEndDetails( // ignore: STRONG_MODE_INVALID_CAST_FUNCTION_EXPR, https://github.com/dart-lang/sdk/issues/27504 + velocity: Velocity.zero, + primaryVelocity: 0.0, + ))); + } + } + _velocityTrackers.clear(); + } + + @override + void dispose() { + _velocityTrackers.clear(); + super.dispose(); + } +} + +/// Recognizes movement in the vertical direction. +/// +/// Used for vertical scrolling. +/// +/// See also: +/// +/// * [VerticalMultiDragGestureRecognizer] +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 + bool get _hasSufficientPendingDragDeltaToAccept => _pendingDragOffset.dy.abs() > kTouchSlop; + + @override + Offset _getDeltaForDetails(Offset delta) => new Offset(0.0, delta.dy); + + @override + double _getPrimaryValueFromOffset(Offset value) => value.dy; + + @override + String toStringShort() => 'vertical drag'; +} + +/// Recognizes movement in the horizontal direction. +/// +/// Used for horizontal scrolling. +/// +/// See also: +/// +/// * [HorizontalMultiDragGestureRecognizer] +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 + bool get _hasSufficientPendingDragDeltaToAccept => _pendingDragOffset.dx.abs() > kTouchSlop; + + @override + Offset _getDeltaForDetails(Offset delta) => new Offset(delta.dx, 0.0); + + @override + double _getPrimaryValueFromOffset(Offset value) => value.dx; + + @override + String toStringShort() => 'horizontal drag'; +} + +/// Recognizes movement both horizontally and vertically. +/// +/// See also: +/// +/// * [ImmediateMultiDragGestureRecognizer] +/// * [DelayedMultiDragGestureRecognizer] +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 + bool get _hasSufficientPendingDragDeltaToAccept { + return _pendingDragOffset.distance > kPanSlop; + } + + @override + Offset _getDeltaForDetails(Offset delta) => delta; + + @override + double _getPrimaryValueFromOffset(Offset value) => null; + + @override + String toStringShort() => 'pan'; +} diff --git a/packages/flutter/lib/src/gestures/multidrag.dart b/packages/flutter/lib/src/gestures/multidrag.dart index 48cf91eda5..831ab8c83a 100644 --- a/packages/flutter/lib/src/gestures/multidrag.dart +++ b/packages/flutter/lib/src/gestures/multidrag.dart @@ -11,6 +11,7 @@ import 'arena.dart'; import 'binding.dart'; import 'constants.dart'; import 'drag.dart'; +import 'drag_details.dart'; import 'events.dart'; import 'recognizer.dart'; import 'velocity_tracker.dart'; @@ -18,24 +19,6 @@ import 'velocity_tracker.dart'; /// Signature for when [MultiDragGestureRecognizer] recognizes the start of a drag gesture. typedef Drag GestureMultiDragStartCallback(Offset position); -/// Interface for receiving updates about drags from a [MultiDragGestureRecognizer]. -abstract class Drag { - /// The pointer has moved. - void update(DragUpdateDetails details) { } - - /// The pointer is no longer in contact with the screen. - /// - /// The velocity at which the pointer was moving when it stopped contacting - /// the screen is available in the `details`. - void end(DragEndDetails details) { } - - /// The input from the pointer is no longer directed towards this receiver. - /// - /// For example, the user might have been interrupted by a system-modal dialog - /// in the middle of the drag. - void cancel() { } -} - /// Per-pointer state for a [MultiDragGestureRecognizer]. /// /// A [MultiDragGestureRecognizer] tracks each pointer separately. The state for