gesture_detector.dart 33 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
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
  GestureDragDownCallback,
23 24 25
  GestureDragStartCallback,
  GestureDragUpdateCallback,
  GestureDragEndCallback,
26
  GestureDragCancelCallback,
27 28
  GestureScaleStartCallback,
  GestureScaleUpdateCallback,
29
  GestureScaleEndCallback,
30 31 32 33
  GestureForcePressStartCallback,
  GestureForcePressPeakCallback,
  GestureForcePressEndCallback,
  GestureForcePressUpdateCallback,
34 35 36
  ScaleStartDetails,
  ScaleUpdateDetails,
  ScaleEndDetails,
37 38
  TapDownDetails,
  TapUpDetails,
39
  ForcePressDetails,
40
  Velocity;
41

42 43 44 45 46
// Examples can assume:
// bool _lights;
// void setState(VoidCallback fn) { }
// String _last;

47
/// Factory for creating gesture recognizers.
48
///
49
/// `T` is the type of gesture recognizer this class manages.
50 51
///
/// Used by [RawGestureDetector.gestures].
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
@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].
74
typedef GestureRecognizerFactoryConstructor<T extends GestureRecognizer> = T Function();
75 76

/// Signature for closures that implement [GestureRecognizerFactory.initializer].
77
typedef GestureRecognizerFactoryInitializer<T extends GestureRecognizer> = void Function(T instance);
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99

/// 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.
  const GestureRecognizerFactoryWithHandlers(this._constructor, this._initializer) :
    assert(_constructor != null),
    assert(_initializer != null);

  final GestureRecognizerFactoryConstructor<T> _constructor;

  final GestureRecognizerFactoryInitializer<T> _initializer;

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

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

101 102
/// A widget that detects gestures.
///
103
/// Attempts to recognize gestures that correspond to its non-null callbacks.
104
///
105 106 107
/// 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.
///
108 109 110
/// By default a GestureDetector with an invisible child ignores touches;
/// this behavior can be controlled with [behavior].
///
Hixie's avatar
Hixie committed
111 112 113 114
/// GestureDetector also listens for accessibility events and maps
/// them to the callbacks. To ignore accessibility events, set
/// [excludeFromSemantics] to true.
///
115 116 117 118 119
/// See <http://flutter.io/gestures/> for additional information.
///
/// 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.
120
///
121
/// {@tool sample}
122 123 124 125 126
///
/// This example makes a rectangle react to being tapped by setting the
/// `_lights` field:
///
/// ```dart
127
/// GestureDetector(
128 129 130
///   onTap: () {
///     setState(() { _lights = true; });
///   },
131
///   child: Container(
132
///     color: Colors.yellow,
133
///     child: Text('TURN LIGHTS ON'),
134 135 136
///   ),
/// )
/// ```
137
/// {@end-tool}
138 139 140 141 142
///
/// ## Debugging
///
/// To see how large the hit test box of a [GestureDetector] is for debugging
/// purposes, set [debugPaintPointersEnabled] to true.
143
class GestureDetector extends StatelessWidget {
144 145 146 147 148 149 150 151 152 153 154
  /// 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.
155
  GestureDetector({
156 157
    Key key,
    this.child,
158
    this.onTapDown,
159 160
    this.onTapUp,
    this.onTap,
161
    this.onTapCancel,
162
    this.onDoubleTap,
163
    this.onLongPress,
164
    this.onLongPressUp,
165
    this.onVerticalDragDown,
166 167 168
    this.onVerticalDragStart,
    this.onVerticalDragUpdate,
    this.onVerticalDragEnd,
169 170
    this.onVerticalDragCancel,
    this.onHorizontalDragDown,
171 172 173
    this.onHorizontalDragStart,
    this.onHorizontalDragUpdate,
    this.onHorizontalDragEnd,
174
    this.onHorizontalDragCancel,
175 176 177 178
    this.onForcePressStart,
    this.onForcePressPeak,
    this.onForcePressUpdate,
    this.onForcePressEnd,
179
    this.onPanDown,
180 181
    this.onPanStart,
    this.onPanUpdate,
182
    this.onPanEnd,
183
    this.onPanCancel,
184 185
    this.onScaleStart,
    this.onScaleUpdate,
186
    this.onScaleEnd,
Hixie's avatar
Hixie committed
187
    this.behavior,
188
    this.excludeFromSemantics = false,
189
    this.dragStartBehavior = DragStartBehavior.down,
190
  }) : assert(excludeFromSemantics != null),
191
       assert(dragStartBehavior != null),
192 193 194 195 196 197 198
       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) {
199
             throw FlutterError(
200 201 202 203 204 205
               'Incorrect GestureDetector arguments.\n'
               'Having both a pan gesture recognizer and a scale gesture recognizer is redundant; scale is a superset of pan. Just use the scale gesture recognizer.'
             );
           }
           final String recognizer = havePan ? 'pan' : 'scale';
           if (haveVerticalDrag && haveHorizontalDrag) {
206
             throw FlutterError(
207 208 209 210 211 212 213
               'Incorrect GestureDetector arguments.\n'
               'Simultaneously having a vertical drag gesture recognizer, a horizontal drag gesture recognizer, and a $recognizer gesture recognizer '
               'will result in the $recognizer gesture recognizer being ignored, since the other two will catch all drags.'
             );
           }
         }
         return true;
214
       }()),
215
       super(key: key);
216

217
  /// The widget below this widget in the tree.
218 219
  ///
  /// {@macro flutter.widgets.child}
220
  final Widget child;
221

222 223
  /// A pointer that might cause a tap has contacted the screen at a particular
  /// location.
224 225 226 227
  ///
  /// 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.
228
  final GestureTapDownCallback onTapDown;
229 230 231

  /// A pointer that will trigger a tap has stopped contacting the screen at a
  /// particular location.
232 233 234
  ///
  /// This triggers immediately before [onTap] in the case of the tap gesture
  /// winning. If the tap gesture did not win, [onTapCancel] is called instead.
Hixie's avatar
Hixie committed
235
  final GestureTapUpCallback onTapUp;
236 237

  /// A tap has occurred.
238 239 240 241 242 243 244 245
  ///
  /// This triggers when the tap gesture wins. If the tap gesture did not win,
  /// [onTapCancel] is called instead.
  ///
  /// See also:
  ///
  ///  * [onTapUp], which is called at the same time but includes details
  ///    regarding the pointer position.
246
  final GestureTapCallback onTap;
247

248 249
  /// The pointer that previously triggered [onTapDown] will not end up causing
  /// a tap.
250 251 252
  ///
  /// This is called after [onTapDown], and instead of [onTapUp] and [onTap], if
  /// the tap gesture did not win.
253
  final GestureTapCancelCallback onTapCancel;
254 255 256

  /// The user has tapped the screen at the same location twice in quick
  /// succession.
257
  final GestureTapCallback onDoubleTap;
258

259 260
  /// A pointer has remained in contact with the screen at the same location for
  /// a long period of time.
261
  final GestureLongPressCallback onLongPress;
262

263 264 265
  /// A pointer that has triggered a long-press has stopped contacting the screen.
  final GestureLongPressUpCallback onLongPressUp;

266
  /// A pointer has contacted the screen and might begin to move vertically.
267 268 269
  final GestureDragDownCallback onVerticalDragDown;

  /// A pointer has contacted the screen and has begun to move vertically.
270
  final GestureDragStartCallback onVerticalDragStart;
271 272 273

  /// A pointer that is in contact with the screen and moving vertically has
  /// moved in the vertical direction.
274
  final GestureDragUpdateCallback onVerticalDragUpdate;
275 276 277 278

  /// A pointer that was previously in contact with the screen and moving
  /// vertically is no longer in contact with the screen and was moving at a
  /// specific velocity when it stopped contacting the screen.
279 280
  final GestureDragEndCallback onVerticalDragEnd;

281 282
  /// The pointer that previously triggered [onVerticalDragDown] did not
  /// complete.
283 284
  final GestureDragCancelCallback onVerticalDragCancel;

285
  /// A pointer has contacted the screen and might begin to move horizontally.
286 287 288
  final GestureDragDownCallback onHorizontalDragDown;

  /// A pointer has contacted the screen and has begun to move horizontally.
289
  final GestureDragStartCallback onHorizontalDragStart;
290 291 292

  /// A pointer that is in contact with the screen and moving horizontally has
  /// moved in the horizontal direction.
293
  final GestureDragUpdateCallback onHorizontalDragUpdate;
294 295 296 297

  /// A pointer that was previously in contact with the screen and moving
  /// horizontally is no longer in contact with the screen and was moving at a
  /// specific velocity when it stopped contacting the screen.
298 299
  final GestureDragEndCallback onHorizontalDragEnd;

300 301
  /// The pointer that previously triggered [onHorizontalDragDown] did not
  /// complete.
302 303
  final GestureDragCancelCallback onHorizontalDragCancel;

304
  /// A pointer has contacted the screen and might begin to move.
305
  final GestureDragDownCallback onPanDown;
306 307

  /// A pointer has contacted the screen and has begun to move.
308
  final GestureDragStartCallback onPanStart;
309 310

  /// A pointer that is in contact with the screen and moving has moved again.
311
  final GestureDragUpdateCallback onPanUpdate;
312 313 314 315

  /// 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.
316
  final GestureDragEndCallback onPanEnd;
317 318

  /// The pointer that previously triggered [onPanDown] did not complete.
319
  final GestureDragCancelCallback onPanCancel;
320

321 322
  /// The pointers in contact with the screen have established a focal point and
  /// initial scale of 1.0.
323
  final GestureScaleStartCallback onScaleStart;
324 325 326

  /// The pointers in contact with the screen have indicated a new focal point
  /// and/or scale.
327
  final GestureScaleUpdateCallback onScaleUpdate;
328 329

  /// The pointers are no longer in contact with the screen.
330 331
  final GestureScaleEndCallback onScaleEnd;

332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362
  /// 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.
  final GestureForcePressStartCallback onForcePressStart;

  /// 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.
  final GestureForcePressPeakCallback onForcePressPeak;

  /// 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.
  final GestureForcePressUpdateCallback onForcePressUpdate;

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

363
  /// How this gesture detector should behave during hit testing.
364 365 366
  ///
  /// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and
  /// [HitTestBehavior.translucent] if child is null.
367 368
  final HitTestBehavior behavior;

Hixie's avatar
Hixie committed
369 370 371 372 373 374 375
  /// 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;

376
  // TODO(jslavitz): Set the DragStartBehavior default to be start across all widgets.
377 378 379 380 381 382 383 384 385 386
  /// Determines the way that drag start behavior is handled.
  ///
  /// If set to [DragStartBehavior.start], gesture drag behavior will
  /// begin upon the detection of a drag gesture. If set to
  /// [DragStartBehavior.down] it will begin when a down event is first detected.
  ///
  /// 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.
  ///
387
  /// By default, the drag start behavior is [DragStartBehavior.down].
388 389 390 391 392 393 394 395 396 397
  ///
  /// Only the [onStart] callbacks for the [VerticalDragGestureRecognizer],
  /// [HorizontalDragGestureRecognizer] and [PanGestureRecognizer] are affected
  /// by this setting.
  ///
  /// See also:
  ///
  ///  * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors.
  final DragStartBehavior dragStartBehavior;

398
  @override
399
  Widget build(BuildContext context) {
400
    final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
401 402

    if (onTapDown != null || onTapUp != null || onTap != null || onTapCancel != null) {
403 404
      gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
        () => TapGestureRecognizer(debugOwner: this),
405 406 407 408 409 410 411 412
        (TapGestureRecognizer instance) {
          instance
            ..onTapDown = onTapDown
            ..onTapUp = onTapUp
            ..onTap = onTap
            ..onTapCancel = onTapCancel;
        },
      );
413
    }
414

415
    if (onDoubleTap != null) {
416 417
      gestures[DoubleTapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(
        () => DoubleTapGestureRecognizer(debugOwner: this),
418 419 420 421 422
        (DoubleTapGestureRecognizer instance) {
          instance
            ..onDoubleTap = onDoubleTap;
        },
      );
423
    }
424

425
    if (onLongPress != null || onLongPressUp !=null) {
426 427
      gestures[LongPressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
        () => LongPressGestureRecognizer(debugOwner: this),
428 429
        (LongPressGestureRecognizer instance) {
          instance
430 431
            ..onLongPress = onLongPress
            ..onLongPressUp = onLongPressUp;
432 433
        },
      );
434
    }
435

436 437 438 439 440
    if (onVerticalDragDown != null ||
        onVerticalDragStart != null ||
        onVerticalDragUpdate != null ||
        onVerticalDragEnd != null ||
        onVerticalDragCancel != null) {
441 442
      gestures[VerticalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>(
        () => VerticalDragGestureRecognizer(debugOwner: this),
443 444 445 446 447 448
        (VerticalDragGestureRecognizer instance) {
          instance
            ..onDown = onVerticalDragDown
            ..onStart = onVerticalDragStart
            ..onUpdate = onVerticalDragUpdate
            ..onEnd = onVerticalDragEnd
449 450
            ..onCancel = onVerticalDragCancel
            ..dragStartBehavior = dragStartBehavior;
451 452
        },
      );
453
    }
454

455 456 457 458 459
    if (onHorizontalDragDown != null ||
        onHorizontalDragStart != null ||
        onHorizontalDragUpdate != null ||
        onHorizontalDragEnd != null ||
        onHorizontalDragCancel != null) {
460 461
      gestures[HorizontalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>(
        () => HorizontalDragGestureRecognizer(debugOwner: this),
462 463 464 465 466 467
        (HorizontalDragGestureRecognizer instance) {
          instance
            ..onDown = onHorizontalDragDown
            ..onStart = onHorizontalDragStart
            ..onUpdate = onHorizontalDragUpdate
            ..onEnd = onHorizontalDragEnd
468 469
            ..onCancel = onHorizontalDragCancel
            ..dragStartBehavior = dragStartBehavior;
470 471
        },
      );
472
    }
473

474 475 476 477 478
    if (onPanDown != null ||
        onPanStart != null ||
        onPanUpdate != null ||
        onPanEnd != null ||
        onPanCancel != null) {
479 480
      gestures[PanGestureRecognizer] = GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
        () => PanGestureRecognizer(debugOwner: this),
481 482 483 484 485 486
        (PanGestureRecognizer instance) {
          instance
            ..onDown = onPanDown
            ..onStart = onPanStart
            ..onUpdate = onPanUpdate
            ..onEnd = onPanEnd
487 488
            ..onCancel = onPanCancel
            ..dragStartBehavior = dragStartBehavior;
489 490
        },
      );
491
    }
492

493
    if (onScaleStart != null || onScaleUpdate != null || onScaleEnd != null) {
494 495
      gestures[ScaleGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ScaleGestureRecognizer>(
        () => ScaleGestureRecognizer(debugOwner: this),
496 497 498 499 500 501 502
        (ScaleGestureRecognizer instance) {
          instance
            ..onStart = onScaleStart
            ..onUpdate = onScaleUpdate
            ..onEnd = onScaleEnd;
        },
      );
503
    }
504

505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520
    if (onForcePressStart != null ||
        onForcePressPeak != null ||
        onForcePressUpdate != null ||
        onForcePressEnd != null) {
      gestures[ForcePressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ForcePressGestureRecognizer>(
          () => ForcePressGestureRecognizer(debugOwner: this),
          (ForcePressGestureRecognizer instance) {
            instance
              ..onStart = onForcePressStart
              ..onPeak = onForcePressPeak
              ..onUpdate = onForcePressUpdate
              ..onEnd = onForcePressEnd;
        },
      );
    }

521
    return RawGestureDetector(
522 523 524
      gestures: gestures,
      behavior: behavior,
      excludeFromSemantics: excludeFromSemantics,
525
      child: child,
526
    );
527
  }
528 529 530 531 532
  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(EnumProperty<DragStartBehavior>('startBehavior', dragStartBehavior));
  }
533
}
534

535 536 537 538
/// A widget that detects gestures described by the given gesture
/// factories.
///
/// For common gestures, use a [GestureRecognizer].
539
/// [RawGestureDetector] is useful primarily when developing your
540
/// own gesture recognizers.
541 542 543 544
///
/// Configuring the gesture recognizers requires a carefully constructed map, as
/// described in [gestures] and as shown in the example below.
///
545
/// {@tool sample}
546 547 548 549 550 551
///
/// 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
552
/// RawGestureDetector(
553
///   gestures: <Type, GestureRecognizerFactory>{
554 555
///     TapGestureRecognizer: GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
///       () => TapGestureRecognizer(),
556 557
///       (TapGestureRecognizer instance) {
///         instance
558 559 560 561
///           ..onTapDown = (TapDownDetails details) { setState(() { _last = 'down'; }); }
///           ..onTapUp = (TapUpDetails details) { setState(() { _last = 'up'; }); }
///           ..onTap = () { setState(() { _last = 'tap'; }); }
///           ..onTapCancel = () { setState(() { _last = 'cancel'; }); };
562 563 564
///       },
///     ),
///   },
565
///   child: Container(width: 300.0, height: 300.0, color: Colors.yellow, child: Text(_last)),
566 567
/// )
/// ```
568
/// {@end-tool}
569 570 571 572
///
/// See also:
///
///  * [GestureDetector], a less flexible but much simpler widget that does the same thing.
573
///  * [Listener], a widget that reports raw pointer events.
574
///  * [GestureRecognizer], the class that you extend to create a custom gesture recognizer.
575
class RawGestureDetector extends StatefulWidget {
576 577 578
  /// Creates a widget that detects gestures.
  ///
  /// By default, gesture detectors contribute semantic information to the tree
579 580
  /// that is used by assistive technology. This can be controlled using
  /// [excludeFromSemantics].
581
  const RawGestureDetector({
582 583
    Key key,
    this.child,
584
    this.gestures = const <Type, GestureRecognizerFactory>{},
585
    this.behavior,
586
    this.excludeFromSemantics = false,
587 588 589
  }) : assert(gestures != null),
       assert(excludeFromSemantics != null),
       super(key: key);
590

591
  /// The widget below this widget in the tree.
592 593
  ///
  /// {@macro flutter.widgets.child}
594 595
  final Widget child;

596
  /// The gestures that this widget will attempt to recognize.
597 598 599 600 601 602
  ///
  /// 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].
603 604 605
  final Map<Type, GestureRecognizerFactory> gestures;

  /// How this gesture detector should behave during hit testing.
606 607 608
  ///
  /// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and
  /// [HitTestBehavior.translucent] if child is null.
609 610 611 612 613 614 615 616 617
  final HitTestBehavior behavior;

  /// 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;

618
  @override
619
  RawGestureDetectorState createState() => RawGestureDetectorState();
620 621
}

622
/// State for a [RawGestureDetector].
623 624 625
class RawGestureDetectorState extends State<RawGestureDetector> {
  Map<Type, GestureRecognizer> _recognizers = const <Type, GestureRecognizer>{};

626
  @override
627 628
  void initState() {
    super.initState();
629
    _syncAll(widget.gestures);
630 631
  }

632
  @override
633
  void didUpdateWidget(RawGestureDetector oldWidget) {
634
    super.didUpdateWidget(oldWidget);
635
    _syncAll(widget.gestures);
636 637
  }

638
  /// This method can be called after the build phase, during the
639
  /// layout of the nearest descendant [RenderObjectWidget] of the
640 641 642 643 644 645 646
  /// 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.
647 648 649 650
  ///
  /// The argument should follow the same conventions as
  /// [RawGestureDetector.gestures]. It acts like a temporary replacement for
  /// that value until the next build.
651 652
  void replaceGestureRecognizers(Map<Type, GestureRecognizerFactory> gestures) {
    assert(() {
653
      if (!context.findRenderObject().owner.debugDoingLayout) {
654
        throw FlutterError(
655 656
          'Unexpected call to replaceGestureRecognizers() method of RawGestureDetectorState.\n'
          'The replaceGestureRecognizers() method can only be called during the layout phase. '
657 658
          '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 '
659 660 661
          'RawGestureDetector or GestureDetector object.'
        );
      }
662
      return true;
663
    }());
664
    _syncAll(gestures);
665
    if (!widget.excludeFromSemantics) {
666
      final RenderSemanticsGestureHandler semanticsGestureHandler = context.findRenderObject();
667
      context.visitChildElements((Element element) {
668
        final _GestureSemantics widget = element.widget;
669
        widget._updateHandlers(semanticsGestureHandler);
670
      });
671 672 673
    }
  }

674 675 676 677 678
  /// This method can be called outside of the build phase to filter the list of
  /// available semantic actions.
  ///
  /// The actual filtering is happening in the next frame and a frame will be
  /// scheduled if non is pending.
679 680 681 682 683
  ///
  /// 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
684
  /// actions to filter changes, it must be called again.
685 686
  void replaceSemanticsActions(Set<SemanticsAction> actions) {
    assert(() {
687 688
      final Element element = context;
      if (element.owner.debugBuilding) {
689
        throw FlutterError(
690
          'Unexpected call to replaceSemanticsActions() method of RawGestureDetectorState.\n'
691
          'The replaceSemanticsActions() method can only be called outside of the build phase.'
692 693 694
        );
      }
      return true;
695
    }());
696 697
    if (!widget.excludeFromSemantics) {
      final RenderSemanticsGestureHandler semanticsGestureHandler = context.findRenderObject();
698
      semanticsGestureHandler.validActions = actions; // will call _markNeedsSemanticsUpdate(), if required.
699 700 701
    }
  }

702
  @override
703 704 705 706 707
  void dispose() {
    for (GestureRecognizer recognizer in _recognizers.values)
      recognizer.dispose();
    _recognizers = null;
    super.dispose();
708 709
  }

710 711
  void _syncAll(Map<Type, GestureRecognizerFactory> gestures) {
    assert(_recognizers != null);
712
    final Map<Type, GestureRecognizer> oldRecognizers = _recognizers;
713 714
    _recognizers = <Type, GestureRecognizer>{};
    for (Type type in gestures.keys) {
715 716
      assert(gestures[type] != null);
      assert(gestures[type]._debugAssertTypeMatches(type));
717
      assert(!_recognizers.containsKey(type));
718 719 720
      _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]);
721 722 723 724 725
    }
    for (Type type in oldRecognizers.keys) {
      if (!_recognizers.containsKey(type))
        oldRecognizers[type].dispose();
    }
726 727
  }

Ian Hickson's avatar
Ian Hickson committed
728
  void _handlePointerDown(PointerDownEvent event) {
729 730 731
    assert(_recognizers != null);
    for (GestureRecognizer recognizer in _recognizers.values)
      recognizer.addPointer(event);
732 733
  }

734
  HitTestBehavior get _defaultBehavior {
735
    return widget.child == null ? HitTestBehavior.translucent : HitTestBehavior.deferToChild;
736 737
  }

738 739
  void _handleSemanticsTap() {
    final TapGestureRecognizer recognizer = _recognizers[TapGestureRecognizer];
740 741
    assert(recognizer != null);
    if (recognizer.onTapDown != null)
742
      recognizer.onTapDown(TapDownDetails());
743
    if (recognizer.onTapUp != null)
744
      recognizer.onTapUp(TapUpDetails());
745 746
    if (recognizer.onTap != null)
      recognizer.onTap();
Hixie's avatar
Hixie committed
747 748
  }

749 750
  void _handleSemanticsLongPress() {
    final LongPressGestureRecognizer recognizer = _recognizers[LongPressGestureRecognizer];
751 752 753
    assert(recognizer != null);
    if (recognizer.onLongPress != null)
      recognizer.onLongPress();
Hixie's avatar
Hixie committed
754 755
  }

756
  void _handleSemanticsHorizontalDragUpdate(DragUpdateDetails updateDetails) {
757
    {
758
      final HorizontalDragGestureRecognizer recognizer = _recognizers[HorizontalDragGestureRecognizer];
759
      if (recognizer != null) {
760
        if (recognizer.onDown != null)
761
          recognizer.onDown(DragDownDetails());
762
        if (recognizer.onStart != null)
763
          recognizer.onStart(DragStartDetails());
764
        if (recognizer.onUpdate != null)
765
          recognizer.onUpdate(updateDetails);
766
        if (recognizer.onEnd != null)
767
          recognizer.onEnd(DragEndDetails(primaryVelocity: 0.0));
768 769 770 771
        return;
      }
    }
    {
772
      final PanGestureRecognizer recognizer = _recognizers[PanGestureRecognizer];
773
      if (recognizer != null) {
774
        if (recognizer.onDown != null)
775
          recognizer.onDown(DragDownDetails());
776
        if (recognizer.onStart != null)
777
          recognizer.onStart(DragStartDetails());
778
        if (recognizer.onUpdate != null)
779
          recognizer.onUpdate(updateDetails);
780
        if (recognizer.onEnd != null)
781
          recognizer.onEnd(DragEndDetails());
782 783
        return;
      }
Hixie's avatar
Hixie committed
784 785 786
    }
  }

787
  void _handleSemanticsVerticalDragUpdate(DragUpdateDetails updateDetails) {
788
    {
789
      final VerticalDragGestureRecognizer recognizer = _recognizers[VerticalDragGestureRecognizer];
790
      if (recognizer != null) {
791
        if (recognizer.onDown != null)
792
          recognizer.onDown(DragDownDetails());
793
        if (recognizer.onStart != null)
794
          recognizer.onStart(DragStartDetails());
795
        if (recognizer.onUpdate != null)
796
          recognizer.onUpdate(updateDetails);
797
        if (recognizer.onEnd != null)
798
          recognizer.onEnd(DragEndDetails(primaryVelocity: 0.0));
799 800
        return;
      }
Hixie's avatar
Hixie committed
801
    }
802
    {
803
      final PanGestureRecognizer recognizer = _recognizers[PanGestureRecognizer];
804
      if (recognizer != null) {
805
        if (recognizer.onDown != null)
806
          recognizer.onDown(DragDownDetails());
807
        if (recognizer.onStart != null)
808
          recognizer.onStart(DragStartDetails());
809
        if (recognizer.onUpdate != null)
810
          recognizer.onUpdate(updateDetails);
811
        if (recognizer.onEnd != null)
812
          recognizer.onEnd(DragEndDetails());
813 814 815
        return;
      }
    }
Hixie's avatar
Hixie committed
816 817
  }

818
  @override
819
  Widget build(BuildContext context) {
820
    Widget result = Listener(
821 822 823 824 825
      onPointerDown: _handlePointerDown,
      behavior: widget.behavior ?? _defaultBehavior,
      child: widget.child
    );
    if (!widget.excludeFromSemantics)
826
      result = _GestureSemantics(owner: this, child: result);
827 828
    return result;
  }
Hixie's avatar
Hixie committed
829

830
  @override
831 832
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
833
    if (_recognizers == null) {
834
      properties.add(DiagnosticsNode.message('DISPOSED'));
835 836
    } else {
      final List<String> gestures = _recognizers.values.map<String>((GestureRecognizer recognizer) => recognizer.debugDescription).toList();
837 838
      properties.add(IterableProperty<String>('gestures', gestures, ifEmpty: '<none>'));
      properties.add(IterableProperty<GestureRecognizer>('recognizers', _recognizers.values, level: DiagnosticLevel.fine));
839
    }
840
    properties.add(EnumProperty<HitTestBehavior>('behavior', widget.behavior, defaultValue: null));
841 842 843 844 845 846 847 848 849 850 851 852 853 854
  }
}

class _GestureSemantics extends SingleChildRenderObjectWidget {
  const _GestureSemantics({
    Key key,
    Widget child,
    this.owner
  }) : super(key: key, child: child);

  final RawGestureDetectorState owner;

  @override
  RenderSemanticsGestureHandler createRenderObject(BuildContext context) {
855
    return RenderSemanticsGestureHandler(
856 857 858 859 860
      onTap: _onTapHandler,
      onLongPress: _onLongPressHandler,
      onHorizontalDragUpdate: _onHorizontalDragUpdateHandler,
      onVerticalDragUpdate: _onVerticalDragUpdateHandler,
    );
861 862 863
  }

  void _updateHandlers(RenderSemanticsGestureHandler renderObject) {
864
    renderObject
865 866 867 868
      ..onTap = _onTapHandler
      ..onLongPress = _onLongPressHandler
      ..onHorizontalDragUpdate = _onHorizontalDragUpdateHandler
      ..onVerticalDragUpdate = _onVerticalDragUpdateHandler;
Hixie's avatar
Hixie committed
869
  }
870 871 872

  @override
  void updateRenderObject(BuildContext context, RenderSemanticsGestureHandler renderObject) {
873
    _updateHandlers(renderObject);
874
  }
875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892

  GestureTapCallback get _onTapHandler {
    return owner._recognizers.containsKey(TapGestureRecognizer) ? owner._handleSemanticsTap : null;
  }

  GestureTapCallback get _onLongPressHandler {
    return owner._recognizers.containsKey(LongPressGestureRecognizer) ? owner._handleSemanticsLongPress : null;
  }

  GestureDragUpdateCallback get _onHorizontalDragUpdateHandler {
    return owner._recognizers.containsKey(HorizontalDragGestureRecognizer) ||
        owner._recognizers.containsKey(PanGestureRecognizer) ? owner._handleSemanticsHorizontalDragUpdate : null;
  }

  GestureDragUpdateCallback get _onVerticalDragUpdateHandler {
    return owner._recognizers.containsKey(VerticalDragGestureRecognizer) ||
        owner._recognizers.containsKey(PanGestureRecognizer) ? owner._handleSemanticsVerticalDragUpdate : null;
  }
Hixie's avatar
Hixie committed
893
}