gesture_detector.dart 62.7 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'package:flutter/foundation.dart';
6 7
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
8

9 10
import 'basic.dart';
import 'framework.dart';
11

12
export 'package:flutter/gestures.dart' show
13 14 15 16
  DragDownDetails,
  DragStartDetails,
  DragUpdateDetails,
  DragEndDetails,
17 18
  GestureTapDownCallback,
  GestureTapUpCallback,
19
  GestureTapCallback,
20
  GestureTapCancelCallback,
21
  GestureLongPressCallback,
22 23 24 25
  GestureLongPressStartCallback,
  GestureLongPressMoveUpdateCallback,
  GestureLongPressUpCallback,
  GestureLongPressEndCallback,
26
  GestureDragDownCallback,
27 28 29
  GestureDragStartCallback,
  GestureDragUpdateCallback,
  GestureDragEndCallback,
30
  GestureDragCancelCallback,
31 32
  GestureScaleStartCallback,
  GestureScaleUpdateCallback,
33
  GestureScaleEndCallback,
34 35 36 37
  GestureForcePressStartCallback,
  GestureForcePressPeakCallback,
  GestureForcePressEndCallback,
  GestureForcePressUpdateCallback,
38 39 40
  LongPressStartDetails,
  LongPressMoveUpdateDetails,
  LongPressEndDetails,
41 42 43
  ScaleStartDetails,
  ScaleUpdateDetails,
  ScaleEndDetails,
44 45
  TapDownDetails,
  TapUpDetails,
46
  ForcePressDetails,
47
  Velocity;
48
export 'package:flutter/rendering.dart' show RenderSemanticsGestureHandler;
49

50
// Examples can assume:
51
// late bool _lights;
52
// void setState(VoidCallback fn) { }
53 54
// late String _last;
// late Color _color;
55

56
/// Factory for creating gesture recognizers.
57
///
58
/// `T` is the type of gesture recognizer this class manages.
59 60
///
/// Used by [RawGestureDetector.gestures].
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
@optionalTypeArgs
abstract class GestureRecognizerFactory<T extends GestureRecognizer> {
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
  const GestureRecognizerFactory();

  /// Must return an instance of T.
  T constructor();

  /// Must configure the given instance (which will have been created by
  /// `constructor`).
  ///
  /// This normally means setting the callbacks.
  void initializer(T instance);

  bool _debugAssertTypeMatches(Type type) {
    assert(type == T, 'GestureRecognizerFactory of type $T was used where type $type was specified.');
    return true;
  }
}

/// Signature for closures that implement [GestureRecognizerFactory.constructor].
83
typedef GestureRecognizerFactoryConstructor<T extends GestureRecognizer> = T Function();
84 85

/// Signature for closures that implement [GestureRecognizerFactory.initializer].
86
typedef GestureRecognizerFactoryInitializer<T extends GestureRecognizer> = void Function(T instance);
87 88 89 90 91 92 93 94

/// Factory for creating gesture recognizers that delegates to callbacks.
///
/// Used by [RawGestureDetector.gestures].
class GestureRecognizerFactoryWithHandlers<T extends GestureRecognizer> extends GestureRecognizerFactory<T> {
  /// Creates a gesture recognizer factory with the given callbacks.
  ///
  /// The arguments must not be null.
95 96 97
  const GestureRecognizerFactoryWithHandlers(this._constructor, this._initializer)
    : assert(_constructor != null),
      assert(_initializer != null);
98 99 100 101 102 103 104 105 106 107 108

  final GestureRecognizerFactoryConstructor<T> _constructor;

  final GestureRecognizerFactoryInitializer<T> _initializer;

  @override
  T constructor() => _constructor();

  @override
  void initializer(T instance) => _initializer(instance);
}
109

110 111
/// A widget that detects gestures.
///
112
/// Attempts to recognize gestures that correspond to its non-null callbacks.
113
///
114 115 116
/// If this widget has a child, it defers to that child for its sizing behavior.
/// If it does not have a child, it grows to fit the parent instead.
///
117 118 119
/// By default a GestureDetector with an invisible child ignores touches;
/// this behavior can be controlled with [behavior].
///
Hixie's avatar
Hixie committed
120 121 122 123
/// GestureDetector also listens for accessibility events and maps
/// them to the callbacks. To ignore accessibility events, set
/// [excludeFromSemantics] to true.
///
124
/// See <http://flutter.dev/gestures/> for additional information.
125 126 127 128
///
/// Material design applications typically react to touches with ink splash
/// effects. The [InkWell] class implements this effect and can be used in place
/// of a [GestureDetector] for handling taps.
129
///
130
/// {@tool dartpad}
131 132 133
/// This example contains a black light bulb wrapped in a [GestureDetector]. It
/// turns the light bulb yellow when the "TURN LIGHT ON" button is tapped by
/// setting the `_lights` field, and off again when "TURN LIGHT OFF" is tapped.
134
///
135
/// ** See code in examples/api/lib/widgets/gesture_detector/gesture_detector.0.dart **
136
/// {@end-tool}
137
///
138
/// {@tool dartpad}
139 140
/// This example uses a [Container] that wraps a [GestureDetector] widget which
/// detects a tap.
141
///
142 143 144 145
/// Since the [GestureDetector] does not have a child, it takes on the size of its
/// parent, making the entire area of the surrounding [Container] clickable. When
/// tapped, the [Container] turns yellow by setting the `_color` field. When
/// tapped again, it goes back to white.
146
///
147
/// ** See code in examples/api/lib/widgets/gesture_detector/gesture_detector.1.dart **
148 149
/// {@end-tool}
///
150 151 152 153
/// ## Debugging
///
/// To see how large the hit test box of a [GestureDetector] is for debugging
/// purposes, set [debugPaintPointersEnabled] to true.
154 155 156 157 158 159
///
/// See also:
///
///  * [Listener], a widget for listening to lower-level raw pointer events.
///  * [MouseRegion], a widget that tracks the movement of mice, even when no
///    button is pressed.
160
///  * [RawGestureDetector], a widget that is used to detect custom gestures.
161
class GestureDetector extends StatelessWidget {
162 163 164 165 166 167 168 169 170 171 172
  /// Creates a widget that detects gestures.
  ///
  /// Pan and scale callbacks cannot be used simultaneously because scale is a
  /// superset of pan. Simply use the scale callbacks instead.
  ///
  /// Horizontal and vertical drag callbacks cannot be used simultaneously
  /// because a combination of a horizontal and vertical drag is a pan. Simply
  /// use the pan callbacks instead.
  ///
  /// By default, gesture detectors contribute semantic information to the tree
  /// that is used by assistive technology.
173
  GestureDetector({
174
    Key? key,
175
    this.child,
176
    this.onTapDown,
177 178
    this.onTapUp,
    this.onTap,
179
    this.onTapCancel,
180
    this.onSecondaryTap,
181 182 183
    this.onSecondaryTapDown,
    this.onSecondaryTapUp,
    this.onSecondaryTapCancel,
184 185 186
    this.onTertiaryTapDown,
    this.onTertiaryTapUp,
    this.onTertiaryTapCancel,
187
    this.onDoubleTapDown,
188
    this.onDoubleTap,
189
    this.onDoubleTapCancel,
190 191
    this.onLongPressDown,
    this.onLongPressCancel,
192
    this.onLongPress,
193 194
    this.onLongPressStart,
    this.onLongPressMoveUpdate,
195
    this.onLongPressUp,
196
    this.onLongPressEnd,
197 198
    this.onSecondaryLongPressDown,
    this.onSecondaryLongPressCancel,
199 200 201 202 203
    this.onSecondaryLongPress,
    this.onSecondaryLongPressStart,
    this.onSecondaryLongPressMoveUpdate,
    this.onSecondaryLongPressUp,
    this.onSecondaryLongPressEnd,
204 205 206 207 208 209 210
    this.onTertiaryLongPressDown,
    this.onTertiaryLongPressCancel,
    this.onTertiaryLongPress,
    this.onTertiaryLongPressStart,
    this.onTertiaryLongPressMoveUpdate,
    this.onTertiaryLongPressUp,
    this.onTertiaryLongPressEnd,
211
    this.onVerticalDragDown,
212 213 214
    this.onVerticalDragStart,
    this.onVerticalDragUpdate,
    this.onVerticalDragEnd,
215 216
    this.onVerticalDragCancel,
    this.onHorizontalDragDown,
217 218 219
    this.onHorizontalDragStart,
    this.onHorizontalDragUpdate,
    this.onHorizontalDragEnd,
220
    this.onHorizontalDragCancel,
221 222 223 224
    this.onForcePressStart,
    this.onForcePressPeak,
    this.onForcePressUpdate,
    this.onForcePressEnd,
225
    this.onPanDown,
226 227
    this.onPanStart,
    this.onPanUpdate,
228
    this.onPanEnd,
229
    this.onPanCancel,
230 231
    this.onScaleStart,
    this.onScaleUpdate,
232
    this.onScaleEnd,
Hixie's avatar
Hixie committed
233
    this.behavior,
234
    this.excludeFromSemantics = false,
235
    this.dragStartBehavior = DragStartBehavior.start,
236
  }) : assert(excludeFromSemantics != null),
237
       assert(dragStartBehavior != null),
238 239 240 241 242 243 244
       assert(() {
         final bool haveVerticalDrag = onVerticalDragStart != null || onVerticalDragUpdate != null || onVerticalDragEnd != null;
         final bool haveHorizontalDrag = onHorizontalDragStart != null || onHorizontalDragUpdate != null || onHorizontalDragEnd != null;
         final bool havePan = onPanStart != null || onPanUpdate != null || onPanEnd != null;
         final bool haveScale = onScaleStart != null || onScaleUpdate != null || onScaleEnd != null;
         if (havePan || haveScale) {
           if (havePan && haveScale) {
245 246 247
             throw FlutterError.fromParts(<DiagnosticsNode>[
               ErrorSummary('Incorrect GestureDetector arguments.'),
               ErrorDescription(
248
                 'Having both a pan gesture recognizer and a scale gesture recognizer is redundant; scale is a superset of pan.',
249
               ),
250
               ErrorHint('Just use the scale gesture recognizer.'),
251
             ]);
252 253 254
           }
           final String recognizer = havePan ? 'pan' : 'scale';
           if (haveVerticalDrag && haveHorizontalDrag) {
255 256 257
             throw FlutterError(
               'Incorrect GestureDetector arguments.\n'
               'Simultaneously having a vertical drag gesture recognizer, a horizontal drag gesture recognizer, and a $recognizer gesture recognizer '
258
               'will result in the $recognizer gesture recognizer being ignored, since the other two will catch all drags.',
259
             );
260 261 262
           }
         }
         return true;
263
       }()),
264
       super(key: key);
265

266
  /// The widget below this widget in the tree.
267
  ///
268
  /// {@macro flutter.widgets.ProxyWidget.child}
269
  final Widget? child;
270

271 272
  /// A pointer that might cause a tap with a primary button has contacted the
  /// screen at a particular location.
273 274 275 276
  ///
  /// This is called after a short timeout, even if the winning gesture has not
  /// yet been selected. If the tap gesture wins, [onTapUp] will be called,
  /// otherwise [onTapCancel] will be called.
277 278 279 280
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
281
  final GestureTapDownCallback? onTapDown;
282

283 284
  /// A pointer that will trigger a tap with a primary button has stopped
  /// contacting the screen at a particular location.
285 286 287
  ///
  /// This triggers immediately before [onTap] in the case of the tap gesture
  /// winning. If the tap gesture did not win, [onTapCancel] is called instead.
288 289 290 291
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
292
  final GestureTapUpCallback? onTapUp;
293

294
  /// A tap with a primary button has occurred.
295 296 297 298 299 300
  ///
  /// This triggers when the tap gesture wins. If the tap gesture did not win,
  /// [onTapCancel] is called instead.
  ///
  /// See also:
  ///
301
  ///  * [kPrimaryButton], the button this callback responds to.
302 303
  ///  * [onTapUp], which is called at the same time but includes details
  ///    regarding the pointer position.
304
  final GestureTapCallback? onTap;
305

306 307
  /// The pointer that previously triggered [onTapDown] will not end up causing
  /// a tap.
308 309 310
  ///
  /// This is called after [onTapDown], and instead of [onTapUp] and [onTap], if
  /// the tap gesture did not win.
311 312 313 314
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
315
  final GestureTapCancelCallback? onTapCancel;
316

317 318 319 320 321 322 323 324 325 326
  /// A tap with a secondary button has occurred.
  ///
  /// This triggers when the tap gesture wins. If the tap gesture did not win,
  /// [onSecondaryTapCancel] is called instead.
  ///
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
  ///  * [onSecondaryTapUp], which is called at the same time but includes details
  ///    regarding the pointer position.
327
  final GestureTapCallback? onSecondaryTap;
328

329 330 331 332 333 334 335 336 337 338
  /// A pointer that might cause a tap with a secondary button has contacted the
  /// screen at a particular location.
  ///
  /// This is called after a short timeout, even if the winning gesture has not
  /// yet been selected. If the tap gesture wins, [onSecondaryTapUp] will be
  /// called, otherwise [onSecondaryTapCancel] will be called.
  ///
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
339
  final GestureTapDownCallback? onSecondaryTapDown;
340 341 342 343 344 345 346 347 348

  /// A pointer that will trigger a tap with a secondary button has stopped
  /// contacting the screen at a particular location.
  ///
  /// This triggers in the case of the tap gesture winning. If the tap gesture
  /// did not win, [onSecondaryTapCancel] is called instead.
  ///
  /// See also:
  ///
349 350
  ///  * [onSecondaryTap], a handler triggered right after this one that doesn't
  ///    pass any details about the tap.
351
  ///  * [kSecondaryButton], the button this callback responds to.
352
  final GestureTapUpCallback? onSecondaryTapUp;
353 354 355 356 357 358 359 360 361 362

  /// The pointer that previously triggered [onSecondaryTapDown] will not end up
  /// causing a tap.
  ///
  /// This is called after [onSecondaryTapDown], and instead of
  /// [onSecondaryTapUp], if the tap gesture did not win.
  ///
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
363
  final GestureTapCancelCallback? onSecondaryTapCancel;
364

365 366 367 368 369 370 371 372 373 374
  /// A pointer that might cause a tap with a tertiary button has contacted the
  /// screen at a particular location.
  ///
  /// This is called after a short timeout, even if the winning gesture has not
  /// yet been selected. If the tap gesture wins, [onTertiaryTapUp] will be
  /// called, otherwise [onTertiaryTapCancel] will be called.
  ///
  /// See also:
  ///
  ///  * [kTertiaryButton], the button this callback responds to.
375
  final GestureTapDownCallback? onTertiaryTapDown;
376 377 378 379 380 381 382 383 384 385

  /// A pointer that will trigger a tap with a tertiary button has stopped
  /// contacting the screen at a particular location.
  ///
  /// This triggers in the case of the tap gesture winning. If the tap gesture
  /// did not win, [onTertiaryTapCancel] is called instead.
  ///
  /// See also:
  ///
  ///  * [kTertiaryButton], the button this callback responds to.
386
  final GestureTapUpCallback? onTertiaryTapUp;
387 388 389 390 391 392 393 394 395 396

  /// The pointer that previously triggered [onTertiaryTapDown] will not end up
  /// causing a tap.
  ///
  /// This is called after [onTertiaryTapDown], and instead of
  /// [onTertiaryTapUp], if the tap gesture did not win.
  ///
  /// See also:
  ///
  ///  * [kTertiaryButton], the button this callback responds to.
397
  final GestureTapCancelCallback? onTertiaryTapCancel;
398

399 400 401 402 403 404 405 406 407 408 409 410
  /// A pointer that might cause a double tap has contacted the screen at a
  /// particular location.
  ///
  /// Triggered immediately after the down event of the second tap.
  ///
  /// If the user completes the double tap and the gesture wins, [onDoubleTap]
  /// will be called after this callback. Otherwise, [onDoubleTapCancel] will
  /// be called after this callback.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
411
  final GestureTapDownCallback? onDoubleTapDown;
412

413 414 415 416 417 418
  /// The user has tapped the screen with a primary button at the same location
  /// twice in quick succession.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
419
  final GestureTapCallback? onDoubleTap;
420

421 422 423 424 425 426
  /// The pointer that previously triggered [onDoubleTapDown] will not end up
  /// causing a double tap.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
427
  final GestureTapCancelCallback? onDoubleTapCancel;
428

429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462
  /// The pointer has contacted the screen with a primary button, which might
  /// be the start of a long-press.
  ///
  /// This triggers after the pointer down event.
  ///
  /// If the user completes the long-press, and this gesture wins,
  /// [onLongPressStart] will be called after this callback. Otherwise,
  /// [onLongPressCancel] will be called after this callback.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
  ///  * [onSecondaryLongPressDown], a similar callback but for a secondary button.
  ///  * [onTertiaryLongPressDown], a similar callback but for a tertiary button.
  ///  * [LongPressGestureRecognizer.onLongPressDown], which exposes this
  ///    callback at the gesture layer.
  final GestureLongPressDownCallback? onLongPressDown;

  /// A pointer that previously triggered [onLongPressDown] will not end up
  /// causing a long-press.
  ///
  /// This triggers once the gesture loses if [onLongPressDown] has previously
  /// been triggered.
  ///
  /// If the user completed the long-press, and the gesture won, then
  /// [onLongPressStart] and [onLongPress] are called instead.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
  ///  * [LongPressGestureRecognizer.onLongPressCancel], which exposes this
  ///    callback at the gesture layer.
  final GestureLongPressCancelCallback? onLongPressCancel;

463
  /// Called when a long press gesture with a primary button has been recognized.
464 465 466 467
  ///
  /// Triggered when a pointer has remained in contact with the screen at the
  /// same location for a long period of time.
  ///
468 469 470 471 472
  /// This is equivalent to (and is called immediately after) [onLongPressStart].
  /// The only difference between the two is that this callback does not
  /// contain details of the position at which the pointer initially contacted
  /// the screen.
  ///
473 474
  /// See also:
  ///
475
  ///  * [kPrimaryButton], the button this callback responds to.
476 477
  ///  * [LongPressGestureRecognizer.onLongPress], which exposes this
  ///    callback at the gesture layer.
478
  final GestureLongPressCallback? onLongPress;
479

480
  /// Called when a long press gesture with a primary button has been recognized.
481 482 483 484
  ///
  /// Triggered when a pointer has remained in contact with the screen at the
  /// same location for a long period of time.
  ///
485 486 487 488 489
  /// This is equivalent to (and is called immediately before) [onLongPress].
  /// The only difference between the two is that this callback contains
  /// details of the position at which the pointer initially contacted the
  /// screen, whereas [onLongPress] does not.
  ///
490 491
  /// See also:
  ///
492
  ///  * [kPrimaryButton], the button this callback responds to.
493 494
  ///  * [LongPressGestureRecognizer.onLongPressStart], which exposes this
  ///    callback at the gesture layer.
495
  final GestureLongPressStartCallback? onLongPressStart;
496

497
  /// A pointer has been drag-moved after a long-press with a primary button.
498 499 500 501
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
502 503
  ///  * [LongPressGestureRecognizer.onLongPressMoveUpdate], which exposes this
  ///    callback at the gesture layer.
504
  final GestureLongPressMoveUpdateCallback? onLongPressMoveUpdate;
505

506 507
  /// A pointer that has triggered a long-press with a primary button has
  /// stopped contacting the screen.
508
  ///
509 510 511 512 513
  /// This is equivalent to (and is called immediately after) [onLongPressEnd].
  /// The only difference between the two is that this callback does not
  /// contain details of the state of the pointer when it stopped contacting
  /// the screen.
  ///
514 515
  /// See also:
  ///
516
  ///  * [kPrimaryButton], the button this callback responds to.
517 518
  ///  * [LongPressGestureRecognizer.onLongPressUp], which exposes this
  ///    callback at the gesture layer.
519
  final GestureLongPressUpCallback? onLongPressUp;
520

521 522
  /// A pointer that has triggered a long-press with a primary button has
  /// stopped contacting the screen.
523
  ///
524 525 526 527 528
  /// This is equivalent to (and is called immediately before) [onLongPressUp].
  /// The only difference between the two is that this callback contains
  /// details of the state of the pointer when it stopped contacting the
  /// screen, whereas [onLongPressUp] does not.
  ///
529 530
  /// See also:
  ///
531
  ///  * [kPrimaryButton], the button this callback responds to.
532 533
  ///  * [LongPressGestureRecognizer.onLongPressEnd], which exposes this
  ///    callback at the gesture layer.
534
  final GestureLongPressEndCallback? onLongPressEnd;
535

536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569
  /// The pointer has contacted the screen with a secondary button, which might
  /// be the start of a long-press.
  ///
  /// This triggers after the pointer down event.
  ///
  /// If the user completes the long-press, and this gesture wins,
  /// [onSecondaryLongPressStart] will be called after this callback. Otherwise,
  /// [onSecondaryLongPressCancel] will be called after this callback.
  ///
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
  ///  * [onLongPressDown], a similar callback but for a secondary button.
  ///  * [onTertiaryLongPressDown], a similar callback but for a tertiary button.
  ///  * [LongPressGestureRecognizer.onSecondaryLongPressDown], which exposes
  ///    this callback at the gesture layer.
  final GestureLongPressDownCallback? onSecondaryLongPressDown;

  /// A pointer that previously triggered [onSecondaryLongPressDown] will not
  /// end up causing a long-press.
  ///
  /// This triggers once the gesture loses if [onSecondaryLongPressDown] has
  /// previously been triggered.
  ///
  /// If the user completed the long-press, and the gesture won, then
  /// [onSecondaryLongPressStart] and [onSecondaryLongPress] are called instead.
  ///
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
  ///  * [LongPressGestureRecognizer.onSecondaryLongPressCancel], which exposes
  ///    this callback at the gesture layer.
  final GestureLongPressCancelCallback? onSecondaryLongPressCancel;

570 571 572 573 574 575
  /// Called when a long press gesture with a secondary button has been
  /// recognized.
  ///
  /// Triggered when a pointer has remained in contact with the screen at the
  /// same location for a long period of time.
  ///
576 577 578 579 580
  /// This is equivalent to (and is called immediately after)
  /// [onSecondaryLongPressStart]. The only difference between the two is that
  /// this callback does not contain details of the position at which the
  /// pointer initially contacted the screen.
  ///
581 582 583
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
584 585
  ///  * [LongPressGestureRecognizer.onSecondaryLongPress], which exposes
  ///    this callback at the gesture layer.
586
  final GestureLongPressCallback? onSecondaryLongPress;
587 588 589 590 591 592 593

  /// Called when a long press gesture with a secondary button has been
  /// recognized.
  ///
  /// Triggered when a pointer has remained in contact with the screen at the
  /// same location for a long period of time.
  ///
594 595 596 597 598
  /// This is equivalent to (and is called immediately before)
  /// [onSecondaryLongPress]. The only difference between the two is that this
  /// callback contains details of the position at which the pointer initially
  /// contacted the screen, whereas [onSecondaryLongPress] does not.
  ///
599 600 601
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
602 603
  ///  * [LongPressGestureRecognizer.onSecondaryLongPressStart], which exposes
  ///    this callback at the gesture layer.
604
  final GestureLongPressStartCallback? onSecondaryLongPressStart;
605 606 607 608 609 610

  /// A pointer has been drag-moved after a long press with a secondary button.
  ///
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
611 612
  ///  * [LongPressGestureRecognizer.onSecondaryLongPressMoveUpdate], which exposes
  ///    this callback at the gesture layer.
613
  final GestureLongPressMoveUpdateCallback? onSecondaryLongPressMoveUpdate;
614 615 616 617

  /// A pointer that has triggered a long-press with a secondary button has
  /// stopped contacting the screen.
  ///
618 619 620 621 622
  /// This is equivalent to (and is called immediately after)
  /// [onSecondaryLongPressEnd]. The only difference between the two is that
  /// this callback does not contain details of the state of the pointer when
  /// it stopped contacting the screen.
  ///
623 624 625
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
626 627
  ///  * [LongPressGestureRecognizer.onSecondaryLongPressUp], which exposes
  ///    this callback at the gesture layer.
628
  final GestureLongPressUpCallback? onSecondaryLongPressUp;
629 630 631 632

  /// A pointer that has triggered a long-press with a secondary button has
  /// stopped contacting the screen.
  ///
633 634 635 636 637
  /// This is equivalent to (and is called immediately before)
  /// [onSecondaryLongPressUp]. The only difference between the two is that
  /// this callback contains details of the state of the pointer when it
  /// stopped contacting the screen, whereas [onSecondaryLongPressUp] does not.
  ///
638 639 640
  /// See also:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
641 642
  ///  * [LongPressGestureRecognizer.onSecondaryLongPressEnd], which exposes
  ///    this callback at the gesture layer.
643
  final GestureLongPressEndCallback? onSecondaryLongPressEnd;
644

645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753
  /// The pointer has contacted the screen with a tertiary button, which might
  /// be the start of a long-press.
  ///
  /// This triggers after the pointer down event.
  ///
  /// If the user completes the long-press, and this gesture wins,
  /// [onTertiaryLongPressStart] will be called after this callback. Otherwise,
  /// [onTertiaryLongPressCancel] will be called after this callback.
  ///
  /// See also:
  ///
  ///  * [kTertiaryButton], the button this callback responds to.
  ///  * [onLongPressDown], a similar callback but for a primary button.
  ///  * [onSecondaryLongPressDown], a similar callback but for a secondary button.
  ///  * [LongPressGestureRecognizer.onTertiaryLongPressDown], which exposes
  ///    this callback at the gesture layer.
  final GestureLongPressDownCallback? onTertiaryLongPressDown;

  /// A pointer that previously triggered [onTertiaryLongPressDown] will not
  /// end up causing a long-press.
  ///
  /// This triggers once the gesture loses if [onTertiaryLongPressDown] has
  /// previously been triggered.
  ///
  /// If the user completed the long-press, and the gesture won, then
  /// [onTertiaryLongPressStart] and [onTertiaryLongPress] are called instead.
  ///
  /// See also:
  ///
  ///  * [kTertiaryButton], the button this callback responds to.
  ///  * [LongPressGestureRecognizer.onTertiaryLongPressCancel], which exposes
  ///    this callback at the gesture layer.
  final GestureLongPressCancelCallback? onTertiaryLongPressCancel;

  /// Called when a long press gesture with a tertiary button has been
  /// recognized.
  ///
  /// Triggered when a pointer has remained in contact with the screen at the
  /// same location for a long period of time.
  ///
  /// This is equivalent to (and is called immediately after)
  /// [onTertiaryLongPressStart]. The only difference between the two is that
  /// this callback does not contain details of the position at which the
  /// pointer initially contacted the screen.
  ///
  /// See also:
  ///
  ///  * [kTertiaryButton], the button this callback responds to.
  ///  * [LongPressGestureRecognizer.onTertiaryLongPress], which exposes
  ///    this callback at the gesture layer.
  final GestureLongPressCallback? onTertiaryLongPress;

  /// Called when a long press gesture with a tertiary button has been
  /// recognized.
  ///
  /// Triggered when a pointer has remained in contact with the screen at the
  /// same location for a long period of time.
  ///
  /// This is equivalent to (and is called immediately before)
  /// [onTertiaryLongPress]. The only difference between the two is that this
  /// callback contains details of the position at which the pointer initially
  /// contacted the screen, whereas [onTertiaryLongPress] does not.
  ///
  /// See also:
  ///
  ///  * [kTertiaryButton], the button this callback responds to.
  ///  * [LongPressGestureRecognizer.onTertiaryLongPressStart], which exposes
  ///    this callback at the gesture layer.
  final GestureLongPressStartCallback? onTertiaryLongPressStart;

  /// A pointer has been drag-moved after a long press with a tertiary button.
  ///
  /// See also:
  ///
  ///  * [kTertiaryButton], the button this callback responds to.
  ///  * [LongPressGestureRecognizer.onTertiaryLongPressMoveUpdate], which exposes
  ///    this callback at the gesture layer.
  final GestureLongPressMoveUpdateCallback? onTertiaryLongPressMoveUpdate;

  /// A pointer that has triggered a long-press with a tertiary button has
  /// stopped contacting the screen.
  ///
  /// This is equivalent to (and is called immediately after)
  /// [onTertiaryLongPressEnd]. The only difference between the two is that
  /// this callback does not contain details of the state of the pointer when
  /// it stopped contacting the screen.
  ///
  /// See also:
  ///
  ///  * [kTertiaryButton], the button this callback responds to.
  ///  * [LongPressGestureRecognizer.onTertiaryLongPressUp], which exposes
  ///    this callback at the gesture layer.
  final GestureLongPressUpCallback? onTertiaryLongPressUp;

  /// A pointer that has triggered a long-press with a tertiary button has
  /// stopped contacting the screen.
  ///
  /// This is equivalent to (and is called immediately before)
  /// [onTertiaryLongPressUp]. The only difference between the two is that
  /// this callback contains details of the state of the pointer when it
  /// stopped contacting the screen, whereas [onTertiaryLongPressUp] does not.
  ///
  /// See also:
  ///
  ///  * [kTertiaryButton], the button this callback responds to.
  ///  * [LongPressGestureRecognizer.onTertiaryLongPressEnd], which exposes
  ///    this callback at the gesture layer.
  final GestureLongPressEndCallback? onTertiaryLongPressEnd;

754 755 756 757 758 759
  /// A pointer has contacted the screen with a primary button and might begin
  /// to move vertically.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
760
  final GestureDragDownCallback? onVerticalDragDown;
761

762 763 764 765 766 767
  /// A pointer has contacted the screen with a primary button and has begun to
  /// move vertically.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
768
  final GestureDragStartCallback? onVerticalDragStart;
769

770 771 772 773 774 775
  /// A pointer that is in contact with the screen with a primary button and
  /// moving vertically has moved in the vertical direction.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
776
  final GestureDragUpdateCallback? onVerticalDragUpdate;
777

778 779 780 781 782 783 784
  /// A pointer that was previously in contact with the screen with a primary
  /// button and moving vertically is no longer in contact with the screen and
  /// was moving at a specific velocity when it stopped contacting the screen.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
785
  final GestureDragEndCallback? onVerticalDragEnd;
786

787 788
  /// The pointer that previously triggered [onVerticalDragDown] did not
  /// complete.
789 790 791 792
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
793
  final GestureDragCancelCallback? onVerticalDragCancel;
794

795 796 797 798 799 800
  /// A pointer has contacted the screen with a primary button and might begin
  /// to move horizontally.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
801
  final GestureDragDownCallback? onHorizontalDragDown;
802

803 804 805 806 807 808
  /// A pointer has contacted the screen with a primary button and has begun to
  /// move horizontally.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
809
  final GestureDragStartCallback? onHorizontalDragStart;
810

811 812 813 814 815 816
  /// A pointer that is in contact with the screen with a primary button and
  /// moving horizontally has moved in the horizontal direction.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
817
  final GestureDragUpdateCallback? onHorizontalDragUpdate;
818

819 820 821 822 823 824 825
  /// A pointer that was previously in contact with the screen with a primary
  /// button and moving horizontally is no longer in contact with the screen and
  /// was moving at a specific velocity when it stopped contacting the screen.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
826
  final GestureDragEndCallback? onHorizontalDragEnd;
827

828 829
  /// The pointer that previously triggered [onHorizontalDragDown] did not
  /// complete.
830 831 832 833
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
834
  final GestureDragCancelCallback? onHorizontalDragCancel;
835

836 837 838 839 840 841
  /// A pointer has contacted the screen with a primary button and might begin
  /// to move.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
842
  final GestureDragDownCallback? onPanDown;
843

844 845 846 847 848 849
  /// A pointer has contacted the screen with a primary button and has begun to
  /// move.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
850
  final GestureDragStartCallback? onPanStart;
851

852 853 854 855 856 857
  /// A pointer that is in contact with the screen with a primary button and
  /// moving has moved again.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
858
  final GestureDragUpdateCallback? onPanUpdate;
859

860 861 862 863 864 865 866
  /// A pointer that was previously in contact with the screen with a primary
  /// button and moving is no longer in contact with the screen and was moving
  /// at a specific velocity when it stopped contacting the screen.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
867
  final GestureDragEndCallback? onPanEnd;
868 869

  /// The pointer that previously triggered [onPanDown] did not complete.
870 871 872 873
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
874
  final GestureDragCancelCallback? onPanCancel;
875

876 877
  /// The pointers in contact with the screen have established a focal point and
  /// initial scale of 1.0.
878
  final GestureScaleStartCallback? onScaleStart;
879 880 881

  /// The pointers in contact with the screen have indicated a new focal point
  /// and/or scale.
882
  final GestureScaleUpdateCallback? onScaleUpdate;
883 884

  /// The pointers are no longer in contact with the screen.
885
  final GestureScaleEndCallback? onScaleEnd;
886

887 888 889 890 891 892
  /// The pointer is in contact with the screen and has pressed with sufficient
  /// force to initiate a force press. The amount of force is at least
  /// [ForcePressGestureRecognizer.startPressure].
  ///
  /// Note that this callback will only be fired on devices with pressure
  /// detecting screens.
893
  final GestureForcePressStartCallback? onForcePressStart;
894 895 896 897 898 899 900

  /// The pointer is in contact with the screen and has pressed with the maximum
  /// force. The amount of force is at least
  /// [ForcePressGestureRecognizer.peakPressure].
  ///
  /// Note that this callback will only be fired on devices with pressure
  /// detecting screens.
901
  final GestureForcePressPeakCallback? onForcePressPeak;
902 903 904 905 906 907 908 909

  /// A pointer is in contact with the screen, has previously passed the
  /// [ForcePressGestureRecognizer.startPressure] and is either moving on the
  /// plane of the screen, pressing the screen with varying forces or both
  /// simultaneously.
  ///
  /// Note that this callback will only be fired on devices with pressure
  /// detecting screens.
910
  final GestureForcePressUpdateCallback? onForcePressUpdate;
911 912 913 914 915

  /// The pointer is no longer in contact with the screen.
  ///
  /// Note that this callback will only be fired on devices with pressure
  /// detecting screens.
916
  final GestureForcePressEndCallback? onForcePressEnd;
917

918
  /// How this gesture detector should behave during hit testing.
919 920 921
  ///
  /// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and
  /// [HitTestBehavior.translucent] if child is null.
922
  final HitTestBehavior? behavior;
923

Hixie's avatar
Hixie committed
924 925 926 927 928 929 930
  /// Whether to exclude these gestures from the semantics tree. For
  /// example, the long-press gesture for showing a tooltip is
  /// excluded because the tooltip itself is included in the semantics
  /// tree directly and so having a gesture to show it would result in
  /// duplication of information.
  final bool excludeFromSemantics;

931 932 933
  /// Determines the way that drag start behavior is handled.
  ///
  /// If set to [DragStartBehavior.start], gesture drag behavior will
934 935 936
  /// begin at the position where the drag gesture won the arena. If set to
  /// [DragStartBehavior.down] it will begin at the position where a down event
  /// is first detected.
937 938 939 940 941
  ///
  /// In general, setting this to [DragStartBehavior.start] will make drag
  /// animation smoother and setting it to [DragStartBehavior.down] will make
  /// drag behavior feel slightly more reactive.
  ///
942
  /// By default, the drag start behavior is [DragStartBehavior.start].
943
  ///
944 945 946
  /// Only the [DragGestureRecognizer.onStart] callbacks for the
  /// [VerticalDragGestureRecognizer], [HorizontalDragGestureRecognizer] and
  /// [PanGestureRecognizer] are affected by this setting.
947 948 949 950 951 952
  ///
  /// See also:
  ///
  ///  * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors.
  final DragStartBehavior dragStartBehavior;

953
  @override
954
  Widget build(BuildContext context) {
955
    final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
956

957 958 959 960 961 962 963
    if (onTapDown != null ||
        onTapUp != null ||
        onTap != null ||
        onTapCancel != null ||
        onSecondaryTap != null ||
        onSecondaryTapDown != null ||
        onSecondaryTapUp != null ||
964 965 966 967
        onSecondaryTapCancel != null||
        onTertiaryTapDown != null ||
        onTertiaryTapUp != null ||
        onTertiaryTapCancel != null
968
    ) {
969 970
      gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
        () => TapGestureRecognizer(debugOwner: this),
971 972 973 974 975
        (TapGestureRecognizer instance) {
          instance
            ..onTapDown = onTapDown
            ..onTapUp = onTapUp
            ..onTap = onTap
976
            ..onTapCancel = onTapCancel
977
            ..onSecondaryTap = onSecondaryTap
978 979
            ..onSecondaryTapDown = onSecondaryTapDown
            ..onSecondaryTapUp = onSecondaryTapUp
980 981 982 983
            ..onSecondaryTapCancel = onSecondaryTapCancel
            ..onTertiaryTapDown = onTertiaryTapDown
            ..onTertiaryTapUp = onTertiaryTapUp
            ..onTertiaryTapCancel = onTertiaryTapCancel;
984 985
        },
      );
986
    }
987

988
    if (onDoubleTap != null) {
989 990
      gestures[DoubleTapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(
        () => DoubleTapGestureRecognizer(debugOwner: this),
991
        (DoubleTapGestureRecognizer instance) {
992 993 994 995
          instance
            ..onDoubleTapDown = onDoubleTapDown
            ..onDoubleTap = onDoubleTap
            ..onDoubleTapCancel = onDoubleTapCancel;
996 997
        },
      );
998
    }
999

1000 1001 1002
    if (onLongPressDown != null ||
        onLongPressCancel != null ||
        onLongPress != null ||
1003 1004
        onLongPressStart != null ||
        onLongPressMoveUpdate != null ||
1005
        onLongPressUp != null ||
1006
        onLongPressEnd != null ||
1007 1008
        onSecondaryLongPressDown != null ||
        onSecondaryLongPressCancel != null ||
1009
        onSecondaryLongPress != null ||
1010 1011
        onSecondaryLongPressStart != null ||
        onSecondaryLongPressMoveUpdate != null ||
1012 1013 1014 1015 1016 1017 1018 1019 1020
        onSecondaryLongPressUp != null ||
        onSecondaryLongPressEnd != null ||
        onTertiaryLongPressDown != null ||
        onTertiaryLongPressCancel != null ||
        onTertiaryLongPress != null ||
        onTertiaryLongPressStart != null ||
        onTertiaryLongPressMoveUpdate != null ||
        onTertiaryLongPressUp != null ||
        onTertiaryLongPressEnd != null) {
1021 1022 1023 1024
      gestures[LongPressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
        () => LongPressGestureRecognizer(debugOwner: this),
        (LongPressGestureRecognizer instance) {
          instance
1025 1026
            ..onLongPressDown = onLongPressDown
            ..onLongPressCancel = onLongPressCancel
1027 1028 1029 1030
            ..onLongPress = onLongPress
            ..onLongPressStart = onLongPressStart
            ..onLongPressMoveUpdate = onLongPressMoveUpdate
            ..onLongPressUp = onLongPressUp
1031 1032 1033
            ..onLongPressEnd = onLongPressEnd
            ..onSecondaryLongPressDown = onSecondaryLongPressDown
            ..onSecondaryLongPressCancel = onSecondaryLongPressCancel
1034 1035 1036
            ..onSecondaryLongPress = onSecondaryLongPress
            ..onSecondaryLongPressStart = onSecondaryLongPressStart
            ..onSecondaryLongPressMoveUpdate = onSecondaryLongPressMoveUpdate
1037
            ..onSecondaryLongPressUp = onSecondaryLongPressUp
1038
            ..onSecondaryLongPressEnd = onSecondaryLongPressEnd
1039 1040 1041 1042 1043 1044 1045
            ..onTertiaryLongPressDown = onTertiaryLongPressDown
            ..onTertiaryLongPressCancel = onTertiaryLongPressCancel
            ..onTertiaryLongPress = onTertiaryLongPress
            ..onTertiaryLongPressStart = onTertiaryLongPressStart
            ..onTertiaryLongPressMoveUpdate = onTertiaryLongPressMoveUpdate
            ..onTertiaryLongPressUp = onTertiaryLongPressUp
            ..onTertiaryLongPressEnd = onTertiaryLongPressEnd;
1046 1047 1048 1049
        },
      );
    }

1050 1051 1052 1053 1054
    if (onVerticalDragDown != null ||
        onVerticalDragStart != null ||
        onVerticalDragUpdate != null ||
        onVerticalDragEnd != null ||
        onVerticalDragCancel != null) {
1055 1056
      gestures[VerticalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>(
        () => VerticalDragGestureRecognizer(debugOwner: this),
1057 1058 1059 1060 1061 1062
        (VerticalDragGestureRecognizer instance) {
          instance
            ..onDown = onVerticalDragDown
            ..onStart = onVerticalDragStart
            ..onUpdate = onVerticalDragUpdate
            ..onEnd = onVerticalDragEnd
1063 1064
            ..onCancel = onVerticalDragCancel
            ..dragStartBehavior = dragStartBehavior;
1065 1066
        },
      );
1067
    }
1068

1069 1070 1071 1072 1073
    if (onHorizontalDragDown != null ||
        onHorizontalDragStart != null ||
        onHorizontalDragUpdate != null ||
        onHorizontalDragEnd != null ||
        onHorizontalDragCancel != null) {
1074 1075
      gestures[HorizontalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>(
        () => HorizontalDragGestureRecognizer(debugOwner: this),
1076 1077 1078 1079 1080 1081
        (HorizontalDragGestureRecognizer instance) {
          instance
            ..onDown = onHorizontalDragDown
            ..onStart = onHorizontalDragStart
            ..onUpdate = onHorizontalDragUpdate
            ..onEnd = onHorizontalDragEnd
1082 1083
            ..onCancel = onHorizontalDragCancel
            ..dragStartBehavior = dragStartBehavior;
1084 1085
        },
      );
1086
    }
1087

1088 1089 1090 1091 1092
    if (onPanDown != null ||
        onPanStart != null ||
        onPanUpdate != null ||
        onPanEnd != null ||
        onPanCancel != null) {
1093 1094
      gestures[PanGestureRecognizer] = GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
        () => PanGestureRecognizer(debugOwner: this),
1095 1096 1097 1098 1099 1100
        (PanGestureRecognizer instance) {
          instance
            ..onDown = onPanDown
            ..onStart = onPanStart
            ..onUpdate = onPanUpdate
            ..onEnd = onPanEnd
1101 1102
            ..onCancel = onPanCancel
            ..dragStartBehavior = dragStartBehavior;
1103 1104
        },
      );
1105
    }
1106

1107
    if (onScaleStart != null || onScaleUpdate != null || onScaleEnd != null) {
1108 1109
      gestures[ScaleGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ScaleGestureRecognizer>(
        () => ScaleGestureRecognizer(debugOwner: this),
1110 1111 1112 1113
        (ScaleGestureRecognizer instance) {
          instance
            ..onStart = onScaleStart
            ..onUpdate = onScaleUpdate
1114 1115
            ..onEnd = onScaleEnd
            ..dragStartBehavior = dragStartBehavior;
1116 1117
        },
      );
1118
    }
1119

1120 1121 1122 1123 1124
    if (onForcePressStart != null ||
        onForcePressPeak != null ||
        onForcePressUpdate != null ||
        onForcePressEnd != null) {
      gestures[ForcePressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ForcePressGestureRecognizer>(
1125 1126 1127 1128 1129 1130 1131
        () => ForcePressGestureRecognizer(debugOwner: this),
        (ForcePressGestureRecognizer instance) {
          instance
            ..onStart = onForcePressStart
            ..onPeak = onForcePressPeak
            ..onUpdate = onForcePressUpdate
            ..onEnd = onForcePressEnd;
1132 1133 1134 1135
        },
      );
    }

1136
    return RawGestureDetector(
1137 1138 1139
      gestures: gestures,
      behavior: behavior,
      excludeFromSemantics: excludeFromSemantics,
1140
      child: child,
1141
    );
1142
  }
1143

1144 1145 1146 1147 1148
  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(EnumProperty<DragStartBehavior>('startBehavior', dragStartBehavior));
  }
1149
}
1150

1151 1152 1153 1154
/// A widget that detects gestures described by the given gesture
/// factories.
///
/// For common gestures, use a [GestureRecognizer].
1155
/// [RawGestureDetector] is useful primarily when developing your
1156
/// own gesture recognizers.
1157 1158 1159 1160
///
/// Configuring the gesture recognizers requires a carefully constructed map, as
/// described in [gestures] and as shown in the example below.
///
1161
/// {@tool snippet}
1162 1163 1164 1165 1166 1167
///
/// This example shows how to hook up a [TapGestureRecognizer]. It assumes that
/// the code is being used inside a [State] object with a `_last` field that is
/// then displayed as the child of the gesture detector.
///
/// ```dart
1168
/// RawGestureDetector(
1169
///   gestures: <Type, GestureRecognizerFactory>{
1170 1171
///     TapGestureRecognizer: GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
///       () => TapGestureRecognizer(),
1172 1173
///       (TapGestureRecognizer instance) {
///         instance
1174 1175 1176 1177
///           ..onTapDown = (TapDownDetails details) { setState(() { _last = 'down'; }); }
///           ..onTapUp = (TapUpDetails details) { setState(() { _last = 'up'; }); }
///           ..onTap = () { setState(() { _last = 'tap'; }); }
///           ..onTapCancel = () { setState(() { _last = 'cancel'; }); };
1178 1179 1180
///       },
///     ),
///   },
1181
///   child: Container(width: 300.0, height: 300.0, color: Colors.yellow, child: Text(_last)),
1182 1183
/// )
/// ```
1184
/// {@end-tool}
1185 1186 1187 1188
///
/// See also:
///
///  * [GestureDetector], a less flexible but much simpler widget that does the same thing.
1189
///  * [Listener], a widget that reports raw pointer events.
1190
///  * [GestureRecognizer], the class that you extend to create a custom gesture recognizer.
1191
class RawGestureDetector extends StatefulWidget {
1192 1193
  /// Creates a widget that detects gestures.
  ///
1194 1195 1196
  /// Gesture detectors can contribute semantic information to the tree that is
  /// used by assistive technology. The behavior can be configured by
  /// [semantics], or disabled with [excludeFromSemantics].
1197
  const RawGestureDetector({
1198
    Key? key,
1199
    this.child,
1200
    this.gestures = const <Type, GestureRecognizerFactory>{},
1201
    this.behavior,
1202
    this.excludeFromSemantics = false,
1203
    this.semantics,
1204 1205 1206
  }) : assert(gestures != null),
       assert(excludeFromSemantics != null),
       super(key: key);
1207

1208
  /// The widget below this widget in the tree.
1209
  ///
1210
  /// {@macro flutter.widgets.ProxyWidget.child}
1211
  final Widget? child;
1212

1213
  /// The gestures that this widget will attempt to recognize.
1214 1215 1216 1217 1218 1219
  ///
  /// This should be a map from [GestureRecognizer] subclasses to
  /// [GestureRecognizerFactory] subclasses specialized with the same type.
  ///
  /// This value can be late-bound at layout time using
  /// [RawGestureDetectorState.replaceGestureRecognizers].
1220 1221 1222
  final Map<Type, GestureRecognizerFactory> gestures;

  /// How this gesture detector should behave during hit testing.
1223 1224 1225
  ///
  /// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and
  /// [HitTestBehavior.translucent] if child is null.
1226
  final HitTestBehavior? behavior;
1227 1228 1229 1230 1231 1232 1233 1234

  /// Whether to exclude these gestures from the semantics tree. For
  /// example, the long-press gesture for showing a tooltip is
  /// excluded because the tooltip itself is included in the semantics
  /// tree directly and so having a gesture to show it would result in
  /// duplication of information.
  final bool excludeFromSemantics;

1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246
  /// Describes the semantics notations that should be added to the underlying
  /// render object [RenderSemanticsGestureHandler].
  ///
  /// It has no effect if [excludeFromSemantics] is true.
  ///
  /// When [semantics] is null, [RawGestureDetector] will fall back to a
  /// default delegate which checks if the detector owns certain gesture
  /// recognizers and calls their callbacks if they exist:
  ///
  ///  * During a semantic tap, it calls [TapGestureRecognizer]'s
  ///    `onTapDown`, `onTapUp`, and `onTap`.
  ///  * During a semantic long press, it calls [LongPressGestureRecognizer]'s
1247 1248
  ///    `onLongPressDown`, `onLongPressStart`, `onLongPress`, `onLongPressEnd`
  ///    and `onLongPressUp`.
1249 1250 1251 1252 1253 1254 1255
  ///  * During a semantic horizontal drag, it calls [HorizontalDragGestureRecognizer]'s
  ///    `onDown`, `onStart`, `onUpdate` and `onEnd`, then
  ///    [PanGestureRecognizer]'s `onDown`, `onStart`, `onUpdate` and `onEnd`.
  ///  * During a semantic vertical drag, it calls [VerticalDragGestureRecognizer]'s
  ///    `onDown`, `onStart`, `onUpdate` and `onEnd`, then
  ///    [PanGestureRecognizer]'s `onDown`, `onStart`, `onUpdate` and `onEnd`.
  ///
1256
  /// {@tool snippet}
1257 1258 1259 1260 1261 1262
  /// This custom gesture detector listens to force presses, while also allows
  /// the same callback to be triggered by semantic long presses.
  ///
  /// ```dart
  /// class ForcePressGestureDetectorWithSemantics extends StatelessWidget {
  ///   const ForcePressGestureDetectorWithSemantics({
1263
  ///     Key? key,
1264 1265
  ///     required this.child,
  ///     required this.onForcePress,
1266
  ///   }) : super(key: key);
1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300
  ///
  ///   final Widget child;
  ///   final VoidCallback onForcePress;
  ///
  ///   @override
  ///   Widget build(BuildContext context) {
  ///     return RawGestureDetector(
  ///       gestures: <Type, GestureRecognizerFactory>{
  ///         ForcePressGestureRecognizer: GestureRecognizerFactoryWithHandlers<ForcePressGestureRecognizer>(
  ///           () => ForcePressGestureRecognizer(debugOwner: this),
  ///           (ForcePressGestureRecognizer instance) {
  ///             instance.onStart = (_) => onForcePress();
  ///           }
  ///         ),
  ///       },
  ///       behavior: HitTestBehavior.opaque,
  ///       semantics: _LongPressSemanticsDelegate(onForcePress),
  ///       child: child,
  ///     );
  ///   }
  /// }
  ///
  /// class _LongPressSemanticsDelegate extends SemanticsGestureDelegate {
  ///   _LongPressSemanticsDelegate(this.onLongPress);
  ///
  ///   VoidCallback onLongPress;
  ///
  ///   @override
  ///   void assignSemantics(RenderSemanticsGestureHandler renderObject) {
  ///     renderObject.onLongPress = onLongPress;
  ///   }
  /// }
  /// ```
  /// {@end-tool}
1301
  final SemanticsGestureDelegate? semantics;
1302

1303
  @override
1304
  RawGestureDetectorState createState() => RawGestureDetectorState();
1305 1306
}

1307
/// State for a [RawGestureDetector].
1308
class RawGestureDetectorState extends State<RawGestureDetector> {
1309 1310
  Map<Type, GestureRecognizer>? _recognizers = const <Type, GestureRecognizer>{};
  SemanticsGestureDelegate? _semantics;
1311

1312
  @override
1313 1314
  void initState() {
    super.initState();
1315
    _semantics = widget.semantics ?? _DefaultSemanticsGestureDelegate(this);
1316
    _syncAll(widget.gestures);
1317 1318
  }

1319
  @override
1320
  void didUpdateWidget(RawGestureDetector oldWidget) {
1321
    super.didUpdateWidget(oldWidget);
1322 1323 1324
    if (!(oldWidget.semantics == null && widget.semantics == null)) {
      _semantics = widget.semantics ?? _DefaultSemanticsGestureDelegate(this);
    }
1325
    _syncAll(widget.gestures);
1326 1327
  }

1328
  /// This method can be called after the build phase, during the
1329
  /// layout of the nearest descendant [RenderObjectWidget] of the
1330 1331 1332 1333 1334 1335 1336
  /// gesture detector, to update the list of active gesture
  /// recognizers.
  ///
  /// The typical use case is [Scrollable]s, which put their viewport
  /// in their gesture detector, and then need to know the dimensions
  /// of the viewport and the viewport's child to determine whether
  /// the gesture detector should be enabled.
1337 1338 1339 1340
  ///
  /// The argument should follow the same conventions as
  /// [RawGestureDetector.gestures]. It acts like a temporary replacement for
  /// that value until the next build.
1341 1342
  void replaceGestureRecognizers(Map<Type, GestureRecognizerFactory> gestures) {
    assert(() {
1343
      if (!context.findRenderObject()!.owner!.debugDoingLayout) {
1344 1345 1346 1347 1348 1349
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('Unexpected call to replaceGestureRecognizers() method of RawGestureDetectorState.'),
          ErrorDescription('The replaceGestureRecognizers() method can only be called during the layout phase.'),
          ErrorHint(
            'To set the gesture recognizers at other times, trigger a new build using setState() '
            'and provide the new gesture recognizers as constructor arguments to the corresponding '
1350
            'RawGestureDetector or GestureDetector object.',
1351
          ),
1352
        ]);
1353
      }
1354
      return true;
1355
    }());
1356
    _syncAll(gestures);
1357
    if (!widget.excludeFromSemantics) {
1358
      final RenderSemanticsGestureHandler semanticsGestureHandler = context.findRenderObject()! as RenderSemanticsGestureHandler;
1359
      _updateSemanticsForRenderObject(semanticsGestureHandler);
1360 1361 1362
    }
  }

1363 1364
  /// This method can be called to filter the list of available semantic actions,
  /// after the render object was created.
1365 1366 1367
  ///
  /// The actual filtering is happening in the next frame and a frame will be
  /// scheduled if non is pending.
1368 1369 1370 1371 1372
  ///
  /// This is used by [Scrollable] to configure system accessibility tools so
  /// that they know in which direction a particular list can be scrolled.
  ///
  /// If this is never called, then the actions are not filtered. If the list of
1373
  /// actions to filter changes, it must be called again.
1374
  void replaceSemanticsActions(Set<SemanticsAction> actions) {
1375 1376 1377
    if (widget.excludeFromSemantics)
      return;

1378
    final RenderSemanticsGestureHandler? semanticsGestureHandler = context.findRenderObject() as RenderSemanticsGestureHandler?;
1379
    assert(() {
1380
      if (semanticsGestureHandler == null) {
1381
        throw FlutterError(
1382
          'Unexpected call to replaceSemanticsActions() method of RawGestureDetectorState.\n'
1383
          'The replaceSemanticsActions() method can only be called after the RenderSemanticsGestureHandler has been created.',
1384 1385 1386
        );
      }
      return true;
1387
    }());
1388

1389
    semanticsGestureHandler!.validActions = actions; // will call _markNeedsSemanticsUpdate(), if required.
1390 1391
  }

1392
  @override
1393
  void dispose() {
1394
    for (final GestureRecognizer recognizer in _recognizers!.values)
1395 1396 1397
      recognizer.dispose();
    _recognizers = null;
    super.dispose();
1398 1399
  }

1400 1401
  void _syncAll(Map<Type, GestureRecognizerFactory> gestures) {
    assert(_recognizers != null);
1402
    final Map<Type, GestureRecognizer> oldRecognizers = _recognizers!;
1403
    _recognizers = <Type, GestureRecognizer>{};
1404
    for (final Type type in gestures.keys) {
1405
      assert(gestures[type] != null);
1406 1407 1408 1409 1410
      assert(gestures[type]!._debugAssertTypeMatches(type));
      assert(!_recognizers!.containsKey(type));
      _recognizers![type] = oldRecognizers[type] ?? gestures[type]!.constructor();
      assert(_recognizers![type].runtimeType == type, 'GestureRecognizerFactory of type $type created a GestureRecognizer of type ${_recognizers![type].runtimeType}. The GestureRecognizerFactory must be specialized with the type of the class that it returns from its constructor method.');
      gestures[type]!.initializer(_recognizers![type]!);
1411
    }
1412
    for (final Type type in oldRecognizers.keys) {
1413 1414
      if (!_recognizers!.containsKey(type))
        oldRecognizers[type]!.dispose();
1415
    }
1416 1417
  }

Ian Hickson's avatar
Ian Hickson committed
1418
  void _handlePointerDown(PointerDownEvent event) {
1419
    assert(_recognizers != null);
1420
    for (final GestureRecognizer recognizer in _recognizers!.values)
1421
      recognizer.addPointer(event);
1422 1423
  }

1424
  HitTestBehavior get _defaultBehavior {
1425
    return widget.child == null ? HitTestBehavior.translucent : HitTestBehavior.deferToChild;
1426 1427
  }

1428 1429 1430
  void _updateSemanticsForRenderObject(RenderSemanticsGestureHandler renderObject) {
    assert(!widget.excludeFromSemantics);
    assert(_semantics != null);
1431
    _semantics!.assignSemantics(renderObject);
1432 1433
  }

1434
  @override
1435
  Widget build(BuildContext context) {
1436
    Widget result = Listener(
1437 1438
      onPointerDown: _handlePointerDown,
      behavior: widget.behavior ?? _defaultBehavior,
1439
      child: widget.child,
1440
    );
1441
    if (!widget.excludeFromSemantics) {
1442
      result = _GestureSemantics(
1443
        behavior: widget.behavior ?? _defaultBehavior,
1444
        assignSemantics: _updateSemanticsForRenderObject,
1445
        child: result,
1446
      );
1447
    }
1448 1449
    return result;
  }
Hixie's avatar
Hixie committed
1450

1451
  @override
1452 1453
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
1454
    if (_recognizers == null) {
1455
      properties.add(DiagnosticsNode.message('DISPOSED'));
1456
    } else {
1457
      final List<String> gestures = _recognizers!.values.map<String>((GestureRecognizer recognizer) => recognizer.debugDescription).toList();
1458
      properties.add(IterableProperty<String>('gestures', gestures, ifEmpty: '<none>'));
1459
      properties.add(IterableProperty<GestureRecognizer>('recognizers', _recognizers!.values, level: DiagnosticLevel.fine));
1460 1461 1462 1463
      properties.add(DiagnosticsProperty<bool>('excludeFromSemantics', widget.excludeFromSemantics, defaultValue: false));
      if (!widget.excludeFromSemantics) {
        properties.add(DiagnosticsProperty<SemanticsGestureDelegate>('semantics', widget.semantics, defaultValue: null));
      }
1464
    }
1465
    properties.add(EnumProperty<HitTestBehavior>('behavior', widget.behavior, defaultValue: null));
1466 1467 1468
  }
}

1469 1470
typedef _AssignSemantics = void Function(RenderSemanticsGestureHandler);

1471 1472
class _GestureSemantics extends SingleChildRenderObjectWidget {
  const _GestureSemantics({
1473 1474
    Key? key,
    Widget? child,
1475
    required this.behavior,
1476
    required this.assignSemantics,
1477 1478
  }) : assert(assignSemantics != null),
       super(key: key, child: child);
1479

1480
  final HitTestBehavior behavior;
1481
  final _AssignSemantics assignSemantics;
1482 1483 1484

  @override
  RenderSemanticsGestureHandler createRenderObject(BuildContext context) {
1485 1486
    final RenderSemanticsGestureHandler renderObject = RenderSemanticsGestureHandler()
      ..behavior = behavior;
1487 1488
    assignSemantics(renderObject);
    return renderObject;
1489 1490
  }

1491 1492
  @override
  void updateRenderObject(BuildContext context, RenderSemanticsGestureHandler renderObject) {
1493
    renderObject.behavior = behavior;
1494
    assignSemantics(renderObject);
Hixie's avatar
Hixie committed
1495
  }
1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509
}

/// A base class that describes what semantics notations a [RawGestureDetector]
/// should add to the render object [RenderSemanticsGestureHandler].
///
/// It is used to allow custom [GestureDetector]s to add semantics notations.
abstract class SemanticsGestureDelegate {
  /// Create a delegate of gesture semantics.
  const SemanticsGestureDelegate();

  /// Assigns semantics notations to the [RenderSemanticsGestureHandler] render
  /// object of the gesture detector.
  ///
  /// This method is called when the widget is created, updated, or during
1510
  /// [RawGestureDetectorState.replaceGestureRecognizers].
1511
  void assignSemantics(RenderSemanticsGestureHandler renderObject);
1512 1513

  @override
1514
  String toString() => '${objectRuntimeType(this, 'SemanticsGestureDelegate')}()';
1515 1516 1517 1518 1519 1520 1521 1522
}

// The default semantics delegate of [RawGestureDetector]. Its behavior is
// described in [RawGestureDetector.semantics].
//
// For readers who come here to learn how to write custom semantics delegates:
// this is not a proper sample code. It has access to the detector state as well
// as its private properties, which are inaccessible normally. It is designed
1523
// this way in order to work independently in a [RawGestureRecognizer] to
1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535
// preserve existing behavior.
//
// Instead, a normal delegate will store callbacks as properties, and use them
// in `assignSemantics`.
class _DefaultSemanticsGestureDelegate extends SemanticsGestureDelegate {
  _DefaultSemanticsGestureDelegate(this.detectorState);

  final RawGestureDetectorState detectorState;

  @override
  void assignSemantics(RenderSemanticsGestureHandler renderObject) {
    assert(!detectorState.widget.excludeFromSemantics);
1536
    final Map<Type, GestureRecognizer> recognizers = detectorState._recognizers!;
1537 1538 1539 1540 1541
    renderObject
      ..onTap = _getTapHandler(recognizers)
      ..onLongPress = _getLongPressHandler(recognizers)
      ..onHorizontalDragUpdate = _getHorizontalDragUpdateHandler(recognizers)
      ..onVerticalDragUpdate = _getVerticalDragUpdateHandler(recognizers);
1542
  }
1543

1544 1545
  GestureTapCallback? _getTapHandler(Map<Type, GestureRecognizer> recognizers) {
    final TapGestureRecognizer? tap = recognizers[TapGestureRecognizer] as TapGestureRecognizer?;
1546 1547 1548 1549 1550
    if (tap == null)
      return null;

    return () {
      assert(tap != null);
1551 1552 1553
      tap.onTapDown?.call(TapDownDetails());
      tap.onTapUp?.call(TapUpDetails(kind: PointerDeviceKind.unknown));
      tap.onTap?.call();
1554
    };
1555
  }
1556

1557 1558
  GestureLongPressCallback? _getLongPressHandler(Map<Type, GestureRecognizer> recognizers) {
    final LongPressGestureRecognizer? longPress = recognizers[LongPressGestureRecognizer] as LongPressGestureRecognizer?;
1559 1560 1561 1562
    if (longPress == null)
      return null;

    return () {
1563
      longPress.onLongPressDown?.call(const LongPressDownDetails());
1564 1565 1566 1567
      longPress.onLongPressStart?.call(const LongPressStartDetails());
      longPress.onLongPress?.call();
      longPress.onLongPressEnd?.call(const LongPressEndDetails());
      longPress.onLongPressUp?.call();
1568
    };
1569
  }
1570

1571 1572 1573
  GestureDragUpdateCallback? _getHorizontalDragUpdateHandler(Map<Type, GestureRecognizer> recognizers) {
    final HorizontalDragGestureRecognizer? horizontal = recognizers[HorizontalDragGestureRecognizer] as HorizontalDragGestureRecognizer?;
    final PanGestureRecognizer? pan = recognizers[PanGestureRecognizer] as PanGestureRecognizer?;
1574

1575
    final GestureDragUpdateCallback? horizontalHandler = horizontal == null ?
1576 1577
      null :
      (DragUpdateDetails details) {
1578 1579 1580 1581
        horizontal.onDown?.call(DragDownDetails());
        horizontal.onStart?.call(DragStartDetails());
        horizontal.onUpdate?.call(details);
        horizontal.onEnd?.call(DragEndDetails(primaryVelocity: 0.0));
1582 1583
      };

1584
    final GestureDragUpdateCallback? panHandler = pan == null ?
1585 1586
      null :
      (DragUpdateDetails details) {
1587 1588 1589 1590
        pan.onDown?.call(DragDownDetails());
        pan.onStart?.call(DragStartDetails());
        pan.onUpdate?.call(details);
        pan.onEnd?.call(DragEndDetails());
1591 1592 1593 1594 1595 1596 1597 1598 1599 1600
      };

    if (horizontalHandler == null && panHandler == null)
      return null;
    return (DragUpdateDetails details) {
      if (horizontalHandler != null)
        horizontalHandler(details);
      if (panHandler != null)
        panHandler(details);
    };
1601
  }
1602

1603 1604 1605
  GestureDragUpdateCallback? _getVerticalDragUpdateHandler(Map<Type, GestureRecognizer> recognizers) {
    final VerticalDragGestureRecognizer? vertical = recognizers[VerticalDragGestureRecognizer] as VerticalDragGestureRecognizer?;
    final PanGestureRecognizer? pan = recognizers[PanGestureRecognizer] as PanGestureRecognizer?;
1606

1607
    final GestureDragUpdateCallback? verticalHandler = vertical == null ?
1608 1609
      null :
      (DragUpdateDetails details) {
1610 1611 1612 1613
        vertical.onDown?.call(DragDownDetails());
        vertical.onStart?.call(DragStartDetails());
        vertical.onUpdate?.call(details);
        vertical.onEnd?.call(DragEndDetails(primaryVelocity: 0.0));
1614 1615
      };

1616
    final GestureDragUpdateCallback? panHandler = pan == null ?
1617 1618
      null :
      (DragUpdateDetails details) {
1619 1620 1621 1622
        pan.onDown?.call(DragDownDetails());
        pan.onStart?.call(DragStartDetails());
        pan.onUpdate?.call(details);
        pan.onEnd?.call(DragEndDetails());
1623 1624 1625 1626 1627 1628 1629 1630 1631 1632
      };

    if (verticalHandler == null && panHandler == null)
      return null;
    return (DragUpdateDetails details) {
      if (verticalHandler != null)
        verticalHandler(details);
      if (panHandler != null)
        panHandler(details);
    };
1633
  }
Hixie's avatar
Hixie committed
1634
}