tap.dart 5.78 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 13 14 15 16
/// Details for [GestureTapDownCallback], such as position.
class TapDownDetails {
  /// Creates details for a [GestureTapDownCallback].
  ///
  /// The [globalPosition] argument must not be null.
17 18
  TapDownDetails({ this.globalPosition: Offset.zero })
    : assert(globalPosition != null);
19 20

  /// The global position at which the pointer contacted the screen.
21
  final Offset globalPosition;
22 23 24 25 26 27 28 29 30 31 32 33 34 35
}

/// 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`.
typedef void GestureTapDownCallback(TapDownDetails details);

/// Details for [GestureTapUpCallback], such as position.
class TapUpDetails {
  /// Creates details for a [GestureTapUpCallback].
  ///
  /// The [globalPosition] argument must not be null.
36 37
  TapUpDetails({ this.globalPosition: Offset.zero })
    : assert(globalPosition != null);
38 39

  /// The global position at which the pointer contacted the screen.
40
  final Offset globalPosition;
41
}
42 43

/// Signature for when a pointer that will trigger a tap has stopped contacting
44 45 46 47 48
/// the screen.
///
/// The position at which the pointer stopped contacting the screen is available
/// in the `details`.
typedef void GestureTapUpCallback(TapUpDetails details);
49 50

/// Signature for when a tap has occurred.
51
typedef void GestureTapCallback();
52 53 54

/// Signature for when the pointer that previously triggered a
/// [GestureTapDownCallback] will not end up causing a tap.
55
typedef void GestureTapCancelCallback();
56

57 58 59 60 61
/// Recognizes taps.
///
/// [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
62
/// taps. For example, down-1, down-2, up-1, up-2 produces only one tap on up-1.
63 64 65 66
///
/// See also:
///
///  * [MultiTapGestureRecognizer]
67
class TapGestureRecognizer extends PrimaryPointerGestureRecognizer {
68
  /// Creates a tap gesture recognizer.
69
  TapGestureRecognizer({ Object debugOwner }) : super(deadline: kPressTimeout, debugOwner: debugOwner);
70

71 72
  /// A pointer that might cause a tap has contacted the screen at a particular
  /// location.
73
  GestureTapDownCallback onTapDown;
74 75 76

  /// A pointer that will trigger a tap has stopped contacting the screen at a
  /// particular location.
Hixie's avatar
Hixie committed
77
  GestureTapUpCallback onTapUp;
78 79

  /// A tap has occurred.
80
  GestureTapCallback onTap;
81 82 83

  /// The pointer that previously triggered [onTapDown] will not end up causing
  /// a tap.
84
  GestureTapCancelCallback onTapCancel;
85

Hixie's avatar
Hixie committed
86
  bool _sentTapDown = false;
87
  bool _wonArenaForPrimaryPointer = false;
88
  Offset _finalPosition;
89

90
  @override
Ian Hickson's avatar
Ian Hickson committed
91 92
  void handlePrimaryPointer(PointerEvent event) {
    if (event is PointerUpEvent) {
93
      _finalPosition = event.position;
Hixie's avatar
Hixie committed
94
      _checkUp();
95 96
    } else if (event is PointerCancelEvent) {
      _reset();
97 98 99
    }
  }

100
  @override
Ian Hickson's avatar
Ian Hickson committed
101
  void resolve(GestureDisposition disposition) {
102
    if (_wonArenaForPrimaryPointer && disposition == GestureDisposition.rejected) {
103 104
      // 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
105
      if (onTapCancel != null)
106
        invokeCallback<void>('spontaneous onTapCancel', onTapCancel);
Ian Hickson's avatar
Ian Hickson committed
107
      _reset();
108
    }
Ian Hickson's avatar
Ian Hickson committed
109 110 111
    super.resolve(disposition);
  }

112
  @override
Hixie's avatar
Hixie committed
113 114 115 116
  void didExceedDeadline() {
    _checkDown();
  }

117
  @override
118 119 120
  void acceptGesture(int pointer) {
    super.acceptGesture(pointer);
    if (pointer == primaryPointer) {
Hixie's avatar
Hixie committed
121
      _checkDown();
122
      _wonArenaForPrimaryPointer = true;
Hixie's avatar
Hixie committed
123
      _checkUp();
124 125 126
    }
  }

127
  @override
128 129 130
  void rejectGesture(int pointer) {
    super.rejectGesture(pointer);
    if (pointer == primaryPointer) {
131 132
      // Another gesture won the arena.
      assert(state != GestureRecognizerState.possible);
133
      if (onTapCancel != null)
134
        invokeCallback<void>('forced onTapCancel', onTapCancel);
135
      _reset();
136 137 138
    }
  }

Hixie's avatar
Hixie committed
139 140 141
  void _checkDown() {
    if (!_sentTapDown) {
      if (onTapDown != null)
142
        invokeCallback<void>('onTapDown', () { onTapDown(new TapDownDetails(globalPosition: initialPosition)); });
Hixie's avatar
Hixie committed
143 144 145 146 147
      _sentTapDown = true;
    }
  }

  void _checkUp() {
148
    if (_wonArenaForPrimaryPointer && _finalPosition != null) {
149
      resolve(GestureDisposition.accepted);
150 151 152 153 154 155 156 157
      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;
      }
158
      if (onTapUp != null)
159
        invokeCallback<void>('onTapUp', () { onTapUp(new TapUpDetails(globalPosition: _finalPosition)); });
160
      if (onTap != null)
161
        invokeCallback<void>('onTap', onTap);
162
      _reset();
163 164
    }
  }
165 166

  void _reset() {
Hixie's avatar
Hixie committed
167
    _sentTapDown = false;
168
    _wonArenaForPrimaryPointer = false;
169 170
    _finalPosition = null;
  }
171

172
  @override
173
  String get debugDescription => 'tap';
174 175 176 177 178 179 180 181

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder description) {
    super.debugFillProperties(description);
    description.add(new FlagProperty('wonArenaForPrimaryPointer', value: _wonArenaForPrimaryPointer, ifTrue: 'won arena'));
    description.add(new DiagnosticsProperty<Offset>('finalPosition', _finalPosition, defaultValue: null));
    description.add(new FlagProperty('sentTapDown', value: _sentTapDown, ifTrue: 'sent tap down'));
  }
182
}