tap.dart 8.62 KB
Newer Older
1 2 3 4
// 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.

5 6
import 'package:flutter/foundation.dart';

7
import 'arena.dart';
8
import 'constants.dart';
9
import 'events.dart';
10
import 'recognizer.dart';
11

12
/// Details for [GestureTapDownCallback], such as position.
13 14 15 16 17
///
/// See also:
///
///  * [GestureDetector.onTapDown], which receives this information.
///  * [TapGestureRecognizer], which passes this information to one of its callbacks.
18 19 20 21
class TapDownDetails {
  /// Creates details for a [GestureTapDownCallback].
  ///
  /// The [globalPosition] argument must not be null.
22
  TapDownDetails({ this.globalPosition = Offset.zero })
23
    : assert(globalPosition != null);
24 25

  /// The global position at which the pointer contacted the screen.
26
  final Offset globalPosition;
27 28 29 30 31 32 33
}

/// Signature for when a pointer that might cause a tap has contacted the
/// screen.
///
/// The position at which the pointer contacted the screen is available in the
/// `details`.
34 35 36 37 38
///
/// See also:
///
///  * [GestureDetector.onTapDown], which matches this signature.
///  * [TapGestureRecognizer], which uses this signature in one of its callbacks.
39
typedef GestureTapDownCallback = void Function(TapDownDetails details);
40 41

/// Details for [GestureTapUpCallback], such as position.
42 43 44 45 46
///
/// See also:
///
///  * [GestureDetector.onTapUp], which receives this information.
///  * [TapGestureRecognizer], which passes this information to one of its callbacks.
47 48 49 50
class TapUpDetails {
  /// Creates details for a [GestureTapUpCallback].
  ///
  /// The [globalPosition] argument must not be null.
51
  TapUpDetails({ this.globalPosition = Offset.zero })
52
    : assert(globalPosition != null);
53 54

  /// The global position at which the pointer contacted the screen.
55
  final Offset globalPosition;
56
}
57 58

/// Signature for when a pointer that will trigger a tap has stopped contacting
59 60 61 62
/// the screen.
///
/// The position at which the pointer stopped contacting the screen is available
/// in the `details`.
63 64 65 66 67
///
/// See also:
///
///  * [GestureDetector.onTapUp], which matches this signature.
///  * [TapGestureRecognizer], which uses this signature in one of its callbacks.
68
typedef GestureTapUpCallback = void Function(TapUpDetails details);
69 70

/// Signature for when a tap has occurred.
71 72 73 74 75
///
/// See also:
///
///  * [GestureDetector.onTap], which matches this signature.
///  * [TapGestureRecognizer], which uses this signature in one of its callbacks.
76
typedef GestureTapCallback = void Function();
77 78 79

/// Signature for when the pointer that previously triggered a
/// [GestureTapDownCallback] will not end up causing a tap.
80 81 82 83 84
///
/// See also:
///
///  * [GestureDetector.onTapCancel], which matches this signature.
///  * [TapGestureRecognizer], which uses this signature in one of its callbacks.
85
typedef GestureTapCancelCallback = void Function();
86

87 88
/// Recognizes taps.
///
89 90 91 92
/// Gesture recognizers take part in gesture arenas to enable potential gestures
/// to be disambiguated from each other. This process is managed by a
/// [GestureArenaManager] (q.v.).
///
93 94 95
/// [TapGestureRecognizer] considers all the pointers involved in the pointer
/// event sequence as contributing to one gesture. For this reason, extra
/// pointer interactions during a tap sequence are not recognized as additional
96
/// taps. For example, down-1, down-2, up-1, up-2 produces only one tap on up-1.
97
///
98 99 100 101 102 103 104 105 106
/// The lifecycle of events for a tap gesture is as follows:
///
/// * [onTapDown], which triggers after a short timeout ([deadline]) even if the
///   gesture has not won its arena yet.
/// * [onTapUp] and [onTap], which trigger when the pointer is released if the
///   gesture wins the arena.
/// * [onTapCancel], which triggers instead of [onTapUp] and [onTap] in the case
///   of the gesture not winning the arena.
///
107 108
/// See also:
///
109
///  * [GestureDetector.onTap], which uses this recognizer.
110
///  * [MultiTapGestureRecognizer]
111
class TapGestureRecognizer extends PrimaryPointerGestureRecognizer {
112
  /// Creates a tap gesture recognizer.
113
  TapGestureRecognizer({ Object debugOwner }) : super(deadline: kPressTimeout, debugOwner: debugOwner);
114

115 116
  /// A pointer that might cause a tap has contacted the screen at a particular
  /// location.
117 118 119 120 121 122 123 124 125 126
  ///
  /// This triggers before the gesture has won the arena, after a short timeout
  /// ([deadline]).
  ///
  /// If the gesture doesn't win the arena, [onTapCancel] is called next.
  /// Otherwise, [onTapUp] is called next.
  ///
  /// See also:
  ///
  ///  * [GestureDetector.onTapDown], which exposes this callback.
127
  GestureTapDownCallback onTapDown;
128 129 130

  /// A pointer that will trigger a tap has stopped contacting the screen at a
  /// particular location.
131 132 133 134 135 136 137 138 139 140
  ///
  /// This triggers once the gesture has won the arena, immediately before
  /// [onTap].
  ///
  /// If the gesture doesn't win the arena, [onTapCancel] is called instead.
  ///
  /// See also:
  ///
  ///  * [GestureDetector.onTapUp], which exposes this callback.
  ///  * [TapUpDetails], which is passed as an argument to this callback.
Hixie's avatar
Hixie committed
141
  GestureTapUpCallback onTapUp;
142 143

  /// A tap has occurred.
144 145 146 147 148 149 150 151 152
  ///
  /// This triggers once the gesture has won the arena, immediately after
  /// [onTapUp].
  ///
  /// If the gesture doesn't win the arena, [onTapCancel] is called instead.
  ///
  /// See also:
  ///
  ///  * [GestureDetector.onTap], which exposes this callback.
153
  GestureTapCallback onTap;
154 155 156

  /// The pointer that previously triggered [onTapDown] will not end up causing
  /// a tap.
157 158 159 160 161 162 163 164
  ///
  /// This triggers if the gesture loses the arena.
  ///
  /// If the gesture wins the arena, [onTapUp] and [onTap] are called instead.
  ///
  /// See also:
  ///
  ///  * [GestureDetector.onTapCancel], which exposes this callback.
165
  GestureTapCancelCallback onTapCancel;
166

Hixie's avatar
Hixie committed
167
  bool _sentTapDown = false;
168
  bool _wonArenaForPrimaryPointer = false;
169
  Offset _finalPosition;
170

171
  @override
Ian Hickson's avatar
Ian Hickson committed
172 173
  void handlePrimaryPointer(PointerEvent event) {
    if (event is PointerUpEvent) {
174
      _finalPosition = event.position;
Hixie's avatar
Hixie committed
175
      _checkUp();
176 177
    } else if (event is PointerCancelEvent) {
      _reset();
178 179 180
    }
  }

181
  @override
Ian Hickson's avatar
Ian Hickson committed
182
  void resolve(GestureDisposition disposition) {
183
    if (_wonArenaForPrimaryPointer && disposition == GestureDisposition.rejected) {
184 185
      // This can happen if the superclass decides the primary pointer
      // exceeded the touch slop, or if the recognizer is disposed.
Ian Hickson's avatar
Ian Hickson committed
186
      if (onTapCancel != null)
187
        invokeCallback<void>('spontaneous onTapCancel', onTapCancel);
Ian Hickson's avatar
Ian Hickson committed
188
      _reset();
189
    }
Ian Hickson's avatar
Ian Hickson committed
190 191 192
    super.resolve(disposition);
  }

193
  @override
Hixie's avatar
Hixie committed
194 195 196 197
  void didExceedDeadline() {
    _checkDown();
  }

198
  @override
199 200 201
  void acceptGesture(int pointer) {
    super.acceptGesture(pointer);
    if (pointer == primaryPointer) {
Hixie's avatar
Hixie committed
202
      _checkDown();
203
      _wonArenaForPrimaryPointer = true;
Hixie's avatar
Hixie committed
204
      _checkUp();
205 206 207
    }
  }

208
  @override
209 210 211
  void rejectGesture(int pointer) {
    super.rejectGesture(pointer);
    if (pointer == primaryPointer) {
212 213
      // Another gesture won the arena.
      assert(state != GestureRecognizerState.possible);
214
      if (onTapCancel != null)
215
        invokeCallback<void>('forced onTapCancel', onTapCancel);
216
      _reset();
217 218 219
    }
  }

Hixie's avatar
Hixie committed
220 221 222
  void _checkDown() {
    if (!_sentTapDown) {
      if (onTapDown != null)
223
        invokeCallback<void>('onTapDown', () { onTapDown(TapDownDetails(globalPosition: initialPosition)); });
Hixie's avatar
Hixie committed
224 225 226 227 228
      _sentTapDown = true;
    }
  }

  void _checkUp() {
229
    if (_wonArenaForPrimaryPointer && _finalPosition != null) {
230
      resolve(GestureDisposition.accepted);
231 232 233 234 235 236 237 238
      if (!_wonArenaForPrimaryPointer || _finalPosition == null) {
        // It is possible that resolve has just recursively called _checkUp
        // (see https://github.com/flutter/flutter/issues/12470).
        // In that case _wonArenaForPrimaryPointer will be false (as _checkUp
        // calls _reset) and we return here to avoid double invocation of the
        // tap callbacks.
        return;
      }
239
      if (onTapUp != null)
240
        invokeCallback<void>('onTapUp', () { onTapUp(TapUpDetails(globalPosition: _finalPosition)); });
241
      if (onTap != null)
242
        invokeCallback<void>('onTap', onTap);
243
      _reset();
244 245
    }
  }
246 247

  void _reset() {
Hixie's avatar
Hixie committed
248
    _sentTapDown = false;
249
    _wonArenaForPrimaryPointer = false;
250 251
    _finalPosition = null;
  }
252

253
  @override
254
  String get debugDescription => 'tap';
255 256

  @override
257 258
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
259 260 261
    properties.add(FlagProperty('wonArenaForPrimaryPointer', value: _wonArenaForPrimaryPointer, ifTrue: 'won arena'));
    properties.add(DiagnosticsProperty<Offset>('finalPosition', _finalPosition, defaultValue: null));
    properties.add(FlagProperty('sentTapDown', value: _sentTapDown, ifTrue: 'sent tap down'));
262
  }
263
}