gesture_detector.dart 44.4 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 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 51 52 53 54
// Examples can assume:
// bool _lights;
// void setState(VoidCallback fn) { }
// String _last;

55
/// Factory for creating gesture recognizers.
56
///
57
/// `T` is the type of gesture recognizer this class manages.
58 59
///
/// Used by [RawGestureDetector.gestures].
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
@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].
82
typedef GestureRecognizerFactoryConstructor<T extends GestureRecognizer> = T Function();
83 84

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

/// 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.
94 95 96
  const GestureRecognizerFactoryWithHandlers(this._constructor, this._initializer)
    : assert(_constructor != null),
      assert(_initializer != null);
97 98 99 100 101 102 103 104 105 106 107

  final GestureRecognizerFactoryConstructor<T> _constructor;

  final GestureRecognizerFactoryInitializer<T> _initializer;

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

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

109 110
/// A widget that detects gestures.
///
111
/// Attempts to recognize gestures that correspond to its non-null callbacks.
112
///
113 114 115
/// 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.
///
116 117 118
/// By default a GestureDetector with an invisible child ignores touches;
/// this behavior can be controlled with [behavior].
///
Hixie's avatar
Hixie committed
119 120 121 122
/// GestureDetector also listens for accessibility events and maps
/// them to the callbacks. To ignore accessibility events, set
/// [excludeFromSemantics] to true.
///
123
/// See <http://flutter.dev/gestures/> for additional information.
124 125 126 127
///
/// 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.
128
///
129
/// {@tool sample}
130 131 132 133 134
///
/// This example makes a rectangle react to being tapped by setting the
/// `_lights` field:
///
/// ```dart
135
/// GestureDetector(
136 137 138
///   onTap: () {
///     setState(() { _lights = true; });
///   },
139
///   child: Container(
140
///     color: Colors.yellow,
141
///     child: Text('TURN LIGHTS ON'),
142 143 144
///   ),
/// )
/// ```
145
/// {@end-tool}
146 147 148 149 150
///
/// ## Debugging
///
/// To see how large the hit test box of a [GestureDetector] is for debugging
/// purposes, set [debugPaintPointersEnabled] to true.
151
class GestureDetector extends StatelessWidget {
152 153 154 155 156 157 158 159 160 161 162
  /// 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.
163
  GestureDetector({
164 165
    Key key,
    this.child,
166
    this.onTapDown,
167 168
    this.onTapUp,
    this.onTap,
169
    this.onTapCancel,
170 171 172
    this.onSecondaryTapDown,
    this.onSecondaryTapUp,
    this.onSecondaryTapCancel,
173
    this.onDoubleTap,
174
    this.onLongPress,
175 176
    this.onLongPressStart,
    this.onLongPressMoveUpdate,
177
    this.onLongPressUp,
178
    this.onLongPressEnd,
179
    this.onVerticalDragDown,
180 181 182
    this.onVerticalDragStart,
    this.onVerticalDragUpdate,
    this.onVerticalDragEnd,
183 184
    this.onVerticalDragCancel,
    this.onHorizontalDragDown,
185 186 187
    this.onHorizontalDragStart,
    this.onHorizontalDragUpdate,
    this.onHorizontalDragEnd,
188
    this.onHorizontalDragCancel,
189 190 191 192
    this.onForcePressStart,
    this.onForcePressPeak,
    this.onForcePressUpdate,
    this.onForcePressEnd,
193
    this.onPanDown,
194 195
    this.onPanStart,
    this.onPanUpdate,
196
    this.onPanEnd,
197
    this.onPanCancel,
198 199
    this.onScaleStart,
    this.onScaleUpdate,
200
    this.onScaleEnd,
Hixie's avatar
Hixie committed
201
    this.behavior,
202
    this.excludeFromSemantics = false,
203
    this.dragStartBehavior = DragStartBehavior.start,
204
  }) : assert(excludeFromSemantics != null),
205
       assert(dragStartBehavior != null),
206 207 208 209 210 211 212
       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) {
213
             throw FlutterError(
214 215 216 217 218 219
               '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) {
220
             throw FlutterError(
221 222 223 224 225 226 227
               '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;
228
       }()),
229
       super(key: key);
230

231
  /// The widget below this widget in the tree.
232 233
  ///
  /// {@macro flutter.widgets.child}
234
  final Widget child;
235

236 237
  /// A pointer that might cause a tap with a primary button has contacted the
  /// screen at a particular location.
238 239 240 241
  ///
  /// 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.
242 243 244 245
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
246
  final GestureTapDownCallback onTapDown;
247

248 249
  /// A pointer that will trigger a tap with a primary button has stopped
  /// contacting the screen at a particular location.
250 251 252
  ///
  /// This triggers immediately before [onTap] in the case of the tap gesture
  /// winning. If the tap gesture did not win, [onTapCancel] is called instead.
253 254 255 256
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
Hixie's avatar
Hixie committed
257
  final GestureTapUpCallback onTapUp;
258

259
  /// A tap with a primary button has occurred.
260 261 262 263 264 265
  ///
  /// This triggers when the tap gesture wins. If the tap gesture did not win,
  /// [onTapCancel] is called instead.
  ///
  /// See also:
  ///
266
  ///  * [kPrimaryButton], the button this callback responds to.
267 268
  ///  * [onTapUp], which is called at the same time but includes details
  ///    regarding the pointer position.
269
  final GestureTapCallback onTap;
270

271 272
  /// The pointer that previously triggered [onTapDown] will not end up causing
  /// a tap.
273 274 275
  ///
  /// This is called after [onTapDown], and instead of [onTapUp] and [onTap], if
  /// the tap gesture did not win.
276 277 278 279
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
280
  final GestureTapCancelCallback onTapCancel;
281

282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
  /// 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.
  final GestureTapDownCallback onSecondaryTapDown;

  /// 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:
  ///
  ///  * [kSecondaryButton], the button this callback responds to.
  final GestureTapUpCallback onSecondaryTapUp;

  /// 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.
  final GestureTapCancelCallback onSecondaryTapCancel;

  /// 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.
322
  final GestureTapCallback onDoubleTap;
323

324
  /// Called when a long press gesture with a primary button has been recognized.
325 326 327 328 329 330
  ///
  /// Triggered when a pointer has remained in contact with the screen at the
  /// same location for a long period of time.
  ///
  /// See also:
  ///
331 332
  ///  * [kPrimaryButton], the button this callback responds to.
  ///  * [onLongPressStart], which has the same timing but has gesture details.
333
  final GestureLongPressCallback onLongPress;
334

335
  /// Called when a long press gesture with a primary button has been recognized.
336 337 338 339 340 341
  ///
  /// Triggered when a pointer has remained in contact with the screen at the
  /// same location for a long period of time.
  ///
  /// See also:
  ///
342 343
  ///  * [kPrimaryButton], the button this callback responds to.
  ///  * [onLongPress], which has the same timing but without the gesture details.
344 345
  final GestureLongPressStartCallback onLongPressStart;

346 347 348 349 350
  /// A pointer has been drag-moved after a long press with a primary button.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
351 352
  final GestureLongPressMoveUpdateCallback onLongPressMoveUpdate;

353 354
  /// A pointer that has triggered a long-press with a primary button has
  /// stopped contacting the screen.
355 356 357
  ///
  /// See also:
  ///
358 359
  ///  * [kPrimaryButton], the button this callback responds to.
  ///  * [onLongPressEnd], which has the same timing but has gesture details.
360 361
  final GestureLongPressUpCallback onLongPressUp;

362 363
  /// A pointer that has triggered a long-press with a primary button has
  /// stopped contacting the screen.
364 365 366
  ///
  /// See also:
  ///
367 368 369
  ///  * [kPrimaryButton], the button this callback responds to.
  ///  * [onLongPressUp], which has the same timing but without the gesture
  ///    details.
370 371
  final GestureLongPressEndCallback onLongPressEnd;

372 373 374 375 376 377
  /// 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.
378 379
  final GestureDragDownCallback onVerticalDragDown;

380 381 382 383 384 385
  /// 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.
386
  final GestureDragStartCallback onVerticalDragStart;
387

388 389 390 391 392 393
  /// 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.
394
  final GestureDragUpdateCallback onVerticalDragUpdate;
395

396 397 398 399 400 401 402
  /// 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.
403 404
  final GestureDragEndCallback onVerticalDragEnd;

405 406
  /// The pointer that previously triggered [onVerticalDragDown] did not
  /// complete.
407 408 409 410
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
411 412
  final GestureDragCancelCallback onVerticalDragCancel;

413 414 415 416 417 418
  /// 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.
419 420
  final GestureDragDownCallback onHorizontalDragDown;

421 422 423 424 425 426
  /// 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.
427
  final GestureDragStartCallback onHorizontalDragStart;
428

429 430 431 432 433 434
  /// 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.
435
  final GestureDragUpdateCallback onHorizontalDragUpdate;
436

437 438 439 440 441 442 443
  /// 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.
444 445
  final GestureDragEndCallback onHorizontalDragEnd;

446 447
  /// The pointer that previously triggered [onHorizontalDragDown] did not
  /// complete.
448 449 450 451
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
452 453
  final GestureDragCancelCallback onHorizontalDragCancel;

454 455 456 457 458 459
  /// A pointer has contacted the screen with a primary button and might begin
  /// to move.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
460
  final GestureDragDownCallback onPanDown;
461

462 463 464 465 466 467
  /// A pointer has contacted the screen with a primary button and has begun to
  /// move.
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
468
  final GestureDragStartCallback onPanStart;
469

470 471 472 473 474 475
  /// 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.
476
  final GestureDragUpdateCallback onPanUpdate;
477

478 479 480 481 482 483 484
  /// 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.
485
  final GestureDragEndCallback onPanEnd;
486 487

  /// The pointer that previously triggered [onPanDown] did not complete.
488 489 490 491
  ///
  /// See also:
  ///
  ///  * [kPrimaryButton], the button this callback responds to.
492
  final GestureDragCancelCallback onPanCancel;
493

494 495
  /// The pointers in contact with the screen have established a focal point and
  /// initial scale of 1.0.
496
  final GestureScaleStartCallback onScaleStart;
497 498 499

  /// The pointers in contact with the screen have indicated a new focal point
  /// and/or scale.
500
  final GestureScaleUpdateCallback onScaleUpdate;
501 502

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

505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535
  /// 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;

536
  /// How this gesture detector should behave during hit testing.
537 538 539
  ///
  /// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and
  /// [HitTestBehavior.translucent] if child is null.
540 541
  final HitTestBehavior behavior;

Hixie's avatar
Hixie committed
542 543 544 545 546 547 548
  /// 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;

549 550 551 552 553 554 555 556 557 558
  /// 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.
  ///
559
  /// By default, the drag start behavior is [DragStartBehavior.start].
560 561 562 563 564 565 566 567 568 569
  ///
  /// 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;

570
  @override
571
  Widget build(BuildContext context) {
572
    final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
573

574 575 576 577 578 579 580 581 582
    if (
      onTapDown != null ||
      onTapUp != null ||
      onTap != null ||
      onTapCancel != null ||
      onSecondaryTapDown != null ||
      onSecondaryTapUp != null ||
      onSecondaryTapCancel != null
    ) {
583 584
      gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
        () => TapGestureRecognizer(debugOwner: this),
585 586 587 588 589
        (TapGestureRecognizer instance) {
          instance
            ..onTapDown = onTapDown
            ..onTapUp = onTapUp
            ..onTap = onTap
590 591 592 593
            ..onTapCancel = onTapCancel
            ..onSecondaryTapDown = onSecondaryTapDown
            ..onSecondaryTapUp = onSecondaryTapUp
            ..onSecondaryTapCancel = onSecondaryTapCancel;
594 595
        },
      );
596
    }
597

598
    if (onDoubleTap != null) {
599 600
      gestures[DoubleTapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(
        () => DoubleTapGestureRecognizer(debugOwner: this),
601 602 603 604 605
        (DoubleTapGestureRecognizer instance) {
          instance
            ..onDoubleTap = onDoubleTap;
        },
      );
606
    }
607

608 609 610 611 612
    if (onLongPress != null ||
        onLongPressUp != null ||
        onLongPressStart != null ||
        onLongPressMoveUpdate != null ||
        onLongPressEnd != null) {
613 614
      gestures[LongPressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
        () => LongPressGestureRecognizer(debugOwner: this),
615 616
        (LongPressGestureRecognizer instance) {
          instance
617
            ..onLongPress = onLongPress
618 619 620
            ..onLongPressStart = onLongPressStart
            ..onLongPressMoveUpdate = onLongPressMoveUpdate
            ..onLongPressEnd =onLongPressEnd
621
            ..onLongPressUp = onLongPressUp;
622 623
        },
      );
624
    }
625

626 627 628 629 630
    if (onVerticalDragDown != null ||
        onVerticalDragStart != null ||
        onVerticalDragUpdate != null ||
        onVerticalDragEnd != null ||
        onVerticalDragCancel != null) {
631 632
      gestures[VerticalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>(
        () => VerticalDragGestureRecognizer(debugOwner: this),
633 634 635 636 637 638
        (VerticalDragGestureRecognizer instance) {
          instance
            ..onDown = onVerticalDragDown
            ..onStart = onVerticalDragStart
            ..onUpdate = onVerticalDragUpdate
            ..onEnd = onVerticalDragEnd
639 640
            ..onCancel = onVerticalDragCancel
            ..dragStartBehavior = dragStartBehavior;
641 642
        },
      );
643
    }
644

645 646 647 648 649
    if (onHorizontalDragDown != null ||
        onHorizontalDragStart != null ||
        onHorizontalDragUpdate != null ||
        onHorizontalDragEnd != null ||
        onHorizontalDragCancel != null) {
650 651
      gestures[HorizontalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>(
        () => HorizontalDragGestureRecognizer(debugOwner: this),
652 653 654 655 656 657
        (HorizontalDragGestureRecognizer instance) {
          instance
            ..onDown = onHorizontalDragDown
            ..onStart = onHorizontalDragStart
            ..onUpdate = onHorizontalDragUpdate
            ..onEnd = onHorizontalDragEnd
658 659
            ..onCancel = onHorizontalDragCancel
            ..dragStartBehavior = dragStartBehavior;
660 661
        },
      );
662
    }
663

664 665 666 667 668
    if (onPanDown != null ||
        onPanStart != null ||
        onPanUpdate != null ||
        onPanEnd != null ||
        onPanCancel != null) {
669 670
      gestures[PanGestureRecognizer] = GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
        () => PanGestureRecognizer(debugOwner: this),
671 672 673 674 675 676
        (PanGestureRecognizer instance) {
          instance
            ..onDown = onPanDown
            ..onStart = onPanStart
            ..onUpdate = onPanUpdate
            ..onEnd = onPanEnd
677 678
            ..onCancel = onPanCancel
            ..dragStartBehavior = dragStartBehavior;
679 680
        },
      );
681
    }
682

683
    if (onScaleStart != null || onScaleUpdate != null || onScaleEnd != null) {
684 685
      gestures[ScaleGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ScaleGestureRecognizer>(
        () => ScaleGestureRecognizer(debugOwner: this),
686 687 688 689 690 691 692
        (ScaleGestureRecognizer instance) {
          instance
            ..onStart = onScaleStart
            ..onUpdate = onScaleUpdate
            ..onEnd = onScaleEnd;
        },
      );
693
    }
694

695 696 697 698 699
    if (onForcePressStart != null ||
        onForcePressPeak != null ||
        onForcePressUpdate != null ||
        onForcePressEnd != null) {
      gestures[ForcePressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ForcePressGestureRecognizer>(
700 701 702 703 704 705 706
        () => ForcePressGestureRecognizer(debugOwner: this),
        (ForcePressGestureRecognizer instance) {
          instance
            ..onStart = onForcePressStart
            ..onPeak = onForcePressPeak
            ..onUpdate = onForcePressUpdate
            ..onEnd = onForcePressEnd;
707 708 709 710
        },
      );
    }

711
    return RawGestureDetector(
712 713 714
      gestures: gestures,
      behavior: behavior,
      excludeFromSemantics: excludeFromSemantics,
715
      child: child,
716
    );
717
  }
718 719 720 721 722
  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(EnumProperty<DragStartBehavior>('startBehavior', dragStartBehavior));
  }
723
}
724

725 726 727 728
/// A widget that detects gestures described by the given gesture
/// factories.
///
/// For common gestures, use a [GestureRecognizer].
729
/// [RawGestureDetector] is useful primarily when developing your
730
/// own gesture recognizers.
731 732 733 734
///
/// Configuring the gesture recognizers requires a carefully constructed map, as
/// described in [gestures] and as shown in the example below.
///
735
/// {@tool sample}
736 737 738 739 740 741
///
/// 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
742
/// RawGestureDetector(
743
///   gestures: <Type, GestureRecognizerFactory>{
744 745
///     TapGestureRecognizer: GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
///       () => TapGestureRecognizer(),
746 747
///       (TapGestureRecognizer instance) {
///         instance
748 749 750 751
///           ..onTapDown = (TapDownDetails details) { setState(() { _last = 'down'; }); }
///           ..onTapUp = (TapUpDetails details) { setState(() { _last = 'up'; }); }
///           ..onTap = () { setState(() { _last = 'tap'; }); }
///           ..onTapCancel = () { setState(() { _last = 'cancel'; }); };
752 753 754
///       },
///     ),
///   },
755
///   child: Container(width: 300.0, height: 300.0, color: Colors.yellow, child: Text(_last)),
756 757
/// )
/// ```
758
/// {@end-tool}
759 760 761 762
///
/// See also:
///
///  * [GestureDetector], a less flexible but much simpler widget that does the same thing.
763
///  * [Listener], a widget that reports raw pointer events.
764
///  * [GestureRecognizer], the class that you extend to create a custom gesture recognizer.
765
class RawGestureDetector extends StatefulWidget {
766 767
  /// Creates a widget that detects gestures.
  ///
768 769 770
  /// 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].
771
  const RawGestureDetector({
772 773
    Key key,
    this.child,
774
    this.gestures = const <Type, GestureRecognizerFactory>{},
775
    this.behavior,
776
    this.excludeFromSemantics = false,
777
    this.semantics,
778 779 780
  }) : assert(gestures != null),
       assert(excludeFromSemantics != null),
       super(key: key);
781

782
  /// The widget below this widget in the tree.
783 784
  ///
  /// {@macro flutter.widgets.child}
785 786
  final Widget child;

787
  /// The gestures that this widget will attempt to recognize.
788 789 790 791 792 793
  ///
  /// 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].
794 795 796
  final Map<Type, GestureRecognizerFactory> gestures;

  /// How this gesture detector should behave during hit testing.
797 798 799
  ///
  /// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and
  /// [HitTestBehavior.translucent] if child is null.
800 801 802 803 804 805 806 807 808
  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;

809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874
  /// 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
  ///    `onLongPressStart`, `onLongPress`, `onLongPressEnd` and `onLongPressUp`.
  ///  * 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`.
  ///
  /// {@tool sample}
  /// 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({
  ///     this.child,
  ///     this.onForcePress,
  ///   });
  ///
  ///   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}
  final SemanticsGestureDelegate semantics;

875
  @override
876
  RawGestureDetectorState createState() => RawGestureDetectorState();
877 878
}

879
/// State for a [RawGestureDetector].
880 881
class RawGestureDetectorState extends State<RawGestureDetector> {
  Map<Type, GestureRecognizer> _recognizers = const <Type, GestureRecognizer>{};
882
  SemanticsGestureDelegate _semantics;
883

884
  @override
885 886
  void initState() {
    super.initState();
887
    _semantics = widget.semantics ?? _DefaultSemanticsGestureDelegate(this);
888
    _syncAll(widget.gestures);
889 890
  }

891
  @override
892
  void didUpdateWidget(RawGestureDetector oldWidget) {
893
    super.didUpdateWidget(oldWidget);
894 895 896
    if (!(oldWidget.semantics == null && widget.semantics == null)) {
      _semantics = widget.semantics ?? _DefaultSemanticsGestureDelegate(this);
    }
897
    _syncAll(widget.gestures);
898 899
  }

900
  /// This method can be called after the build phase, during the
901
  /// layout of the nearest descendant [RenderObjectWidget] of the
902 903 904 905 906 907 908
  /// 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.
909 910 911 912
  ///
  /// The argument should follow the same conventions as
  /// [RawGestureDetector.gestures]. It acts like a temporary replacement for
  /// that value until the next build.
913 914
  void replaceGestureRecognizers(Map<Type, GestureRecognizerFactory> gestures) {
    assert(() {
915
      if (!context.findRenderObject().owner.debugDoingLayout) {
916
        throw FlutterError(
917 918
          'Unexpected call to replaceGestureRecognizers() method of RawGestureDetectorState.\n'
          'The replaceGestureRecognizers() method can only be called during the layout phase. '
919 920
          '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 '
921 922 923
          'RawGestureDetector or GestureDetector object.'
        );
      }
924
      return true;
925
    }());
926
    _syncAll(gestures);
927
    if (!widget.excludeFromSemantics) {
928
      final RenderSemanticsGestureHandler semanticsGestureHandler = context.findRenderObject();
929
      _updateSemanticsForRenderObject(semanticsGestureHandler);
930 931 932
    }
  }

933 934 935 936 937
  /// 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.
938 939 940 941 942
  ///
  /// 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
943
  /// actions to filter changes, it must be called again.
944 945
  void replaceSemanticsActions(Set<SemanticsAction> actions) {
    assert(() {
946 947
      final Element element = context;
      if (element.owner.debugBuilding) {
948
        throw FlutterError(
949
          'Unexpected call to replaceSemanticsActions() method of RawGestureDetectorState.\n'
950
          'The replaceSemanticsActions() method can only be called outside of the build phase.'
951 952 953
        );
      }
      return true;
954
    }());
955 956
    if (!widget.excludeFromSemantics) {
      final RenderSemanticsGestureHandler semanticsGestureHandler = context.findRenderObject();
957
      semanticsGestureHandler.validActions = actions; // will call _markNeedsSemanticsUpdate(), if required.
958 959 960
    }
  }

961
  @override
962 963 964 965 966
  void dispose() {
    for (GestureRecognizer recognizer in _recognizers.values)
      recognizer.dispose();
    _recognizers = null;
    super.dispose();
967 968
  }

969 970
  void _syncAll(Map<Type, GestureRecognizerFactory> gestures) {
    assert(_recognizers != null);
971
    final Map<Type, GestureRecognizer> oldRecognizers = _recognizers;
972 973
    _recognizers = <Type, GestureRecognizer>{};
    for (Type type in gestures.keys) {
974 975
      assert(gestures[type] != null);
      assert(gestures[type]._debugAssertTypeMatches(type));
976
      assert(!_recognizers.containsKey(type));
977 978 979
      _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]);
980 981 982 983 984
    }
    for (Type type in oldRecognizers.keys) {
      if (!_recognizers.containsKey(type))
        oldRecognizers[type].dispose();
    }
985 986
  }

Ian Hickson's avatar
Ian Hickson committed
987
  void _handlePointerDown(PointerDownEvent event) {
988 989 990
    assert(_recognizers != null);
    for (GestureRecognizer recognizer in _recognizers.values)
      recognizer.addPointer(event);
991 992
  }

993
  HitTestBehavior get _defaultBehavior {
994
    return widget.child == null ? HitTestBehavior.translucent : HitTestBehavior.deferToChild;
995 996
  }

997 998 999 1000
  void _updateSemanticsForRenderObject(RenderSemanticsGestureHandler renderObject) {
    assert(!widget.excludeFromSemantics);
    assert(_semantics != null);
    _semantics.assignSemantics(renderObject);
1001 1002
  }

1003
  @override
1004
  Widget build(BuildContext context) {
1005
    Widget result = Listener(
1006 1007
      onPointerDown: _handlePointerDown,
      behavior: widget.behavior ?? _defaultBehavior,
1008
      child: widget.child,
1009 1010
    );
    if (!widget.excludeFromSemantics)
1011 1012 1013 1014
      result = _GestureSemantics(
        child: result,
        assignSemantics: _updateSemanticsForRenderObject,
      );
1015 1016
    return result;
  }
Hixie's avatar
Hixie committed
1017

1018
  @override
1019 1020
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
1021
    if (_recognizers == null) {
1022
      properties.add(DiagnosticsNode.message('DISPOSED'));
1023 1024
    } else {
      final List<String> gestures = _recognizers.values.map<String>((GestureRecognizer recognizer) => recognizer.debugDescription).toList();
1025 1026
      properties.add(IterableProperty<String>('gestures', gestures, ifEmpty: '<none>'));
      properties.add(IterableProperty<GestureRecognizer>('recognizers', _recognizers.values, level: DiagnosticLevel.fine));
1027 1028 1029 1030
      properties.add(DiagnosticsProperty<bool>('excludeFromSemantics', widget.excludeFromSemantics, defaultValue: false));
      if (!widget.excludeFromSemantics) {
        properties.add(DiagnosticsProperty<SemanticsGestureDelegate>('semantics', widget.semantics, defaultValue: null));
      }
1031
    }
1032
    properties.add(EnumProperty<HitTestBehavior>('behavior', widget.behavior, defaultValue: null));
1033 1034 1035
  }
}

1036 1037
typedef _AssignSemantics = void Function(RenderSemanticsGestureHandler);

1038 1039 1040 1041
class _GestureSemantics extends SingleChildRenderObjectWidget {
  const _GestureSemantics({
    Key key,
    Widget child,
1042 1043 1044
    @required this.assignSemantics,
  }) : assert(assignSemantics != null),
       super(key: key, child: child);
1045

1046
  final _AssignSemantics assignSemantics;
1047 1048 1049

  @override
  RenderSemanticsGestureHandler createRenderObject(BuildContext context) {
1050 1051 1052
    final RenderSemanticsGestureHandler renderObject = RenderSemanticsGestureHandler();
    assignSemantics(renderObject);
    return renderObject;
1053 1054
  }

1055 1056 1057
  @override
  void updateRenderObject(BuildContext context, RenderSemanticsGestureHandler renderObject) {
    assignSemantics(renderObject);
Hixie's avatar
Hixie committed
1058
  }
1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074
}

/// 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
  /// [RawGestureDetector.replaceGestureRecognizers].
  void assignSemantics(RenderSemanticsGestureHandler renderObject);
1075 1076

  @override
1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104
  String toString() => '$runtimeType()';
}

// 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
// this way in order to work independenly in a [RawGestureRecognizer] to
// 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);
    final Map<Type, GestureRecognizer> recognizers = detectorState._recognizers;
    renderObject
      ..onTap = _getTapHandler(recognizers)
      ..onLongPress = _getLongPressHandler(recognizers)
      ..onHorizontalDragUpdate = _getHorizontalDragUpdateHandler(recognizers)
      ..onVerticalDragUpdate = _getVerticalDragUpdateHandler(recognizers);
1105
  }
1106

1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121
  GestureTapCallback _getTapHandler(Map<Type, GestureRecognizer> recognizers) {
    final TapGestureRecognizer tap = recognizers[TapGestureRecognizer];
    if (tap == null)
      return null;
    assert(tap is TapGestureRecognizer);

    return () {
      assert(tap != null);
      if (tap.onTapDown != null)
        tap.onTapDown(TapDownDetails());
      if (tap.onTapUp != null)
        tap.onTapUp(TapUpDetails());
      if (tap.onTap != null)
        tap.onTap();
    };
1122
  }
1123

1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139
  GestureLongPressCallback _getLongPressHandler(Map<Type, GestureRecognizer> recognizers) {
    final LongPressGestureRecognizer longPress = recognizers[LongPressGestureRecognizer];
    if (longPress == null)
      return null;

    return () {
      assert(longPress is LongPressGestureRecognizer);
      if (longPress.onLongPressStart != null)
        longPress.onLongPressStart(const LongPressStartDetails());
      if (longPress.onLongPress != null)
        longPress.onLongPress();
      if (longPress.onLongPressEnd != null)
        longPress.onLongPressEnd(const LongPressEndDetails());
      if (longPress.onLongPressUp != null)
        longPress.onLongPressUp();
    };
1140
  }
1141

1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181
  GestureDragUpdateCallback _getHorizontalDragUpdateHandler(Map<Type, GestureRecognizer> recognizers) {
    final HorizontalDragGestureRecognizer horizontal = recognizers[HorizontalDragGestureRecognizer];
    final PanGestureRecognizer pan = recognizers[PanGestureRecognizer];

    final GestureDragUpdateCallback horizontalHandler = horizontal == null ?
      null :
      (DragUpdateDetails details) {
        assert(horizontal is HorizontalDragGestureRecognizer);
        if (horizontal.onDown != null)
          horizontal.onDown(DragDownDetails());
        if (horizontal.onStart != null)
          horizontal.onStart(DragStartDetails());
        if (horizontal.onUpdate != null)
          horizontal.onUpdate(details);
        if (horizontal.onEnd != null)
          horizontal.onEnd(DragEndDetails(primaryVelocity: 0.0));
      };

    final GestureDragUpdateCallback panHandler = pan == null ?
      null :
      (DragUpdateDetails details) {
        assert(pan is PanGestureRecognizer);
        if (pan.onDown != null)
          pan.onDown(DragDownDetails());
        if (pan.onStart != null)
          pan.onStart(DragStartDetails());
        if (pan.onUpdate != null)
          pan.onUpdate(details);
        if (pan.onEnd != null)
          pan.onEnd(DragEndDetails());
      };

    if (horizontalHandler == null && panHandler == null)
      return null;
    return (DragUpdateDetails details) {
      if (horizontalHandler != null)
        horizontalHandler(details);
      if (panHandler != null)
        panHandler(details);
    };
1182
  }
1183

1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223
  GestureDragUpdateCallback _getVerticalDragUpdateHandler(Map<Type, GestureRecognizer> recognizers) {
    final VerticalDragGestureRecognizer vertical = recognizers[VerticalDragGestureRecognizer];
    final PanGestureRecognizer pan = recognizers[PanGestureRecognizer];

    final GestureDragUpdateCallback verticalHandler = vertical == null ?
      null :
      (DragUpdateDetails details) {
        assert(vertical is VerticalDragGestureRecognizer);
        if (vertical.onDown != null)
          vertical.onDown(DragDownDetails());
        if (vertical.onStart != null)
          vertical.onStart(DragStartDetails());
        if (vertical.onUpdate != null)
          vertical.onUpdate(details);
        if (vertical.onEnd != null)
          vertical.onEnd(DragEndDetails(primaryVelocity: 0.0));
      };

    final GestureDragUpdateCallback panHandler = pan == null ?
      null :
      (DragUpdateDetails details) {
        assert(pan is PanGestureRecognizer);
        if (pan.onDown != null)
          pan.onDown(DragDownDetails());
        if (pan.onStart != null)
          pan.onStart(DragStartDetails());
        if (pan.onUpdate != null)
          pan.onUpdate(details);
        if (pan.onEnd != null)
          pan.onEnd(DragEndDetails());
      };

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