1// Copyright 2015 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5import 'package:flutter/foundation.dart'; 6import 'package:flutter/gestures.dart'; 7import 'package:flutter/rendering.dart'; 8 9import 'basic.dart'; 10import 'framework.dart'; 11 12export 'package:flutter/gestures.dart' show 13 DragDownDetails, 14 DragStartDetails, 15 DragUpdateDetails, 16 DragEndDetails, 17 GestureTapDownCallback, 18 GestureTapUpCallback, 19 GestureTapCallback, 20 GestureTapCancelCallback, 21 GestureLongPressCallback, 22 GestureLongPressStartCallback, 23 GestureLongPressMoveUpdateCallback, 24 GestureLongPressUpCallback, 25 GestureLongPressEndCallback, 26 GestureDragDownCallback, 27 GestureDragStartCallback, 28 GestureDragUpdateCallback, 29 GestureDragEndCallback, 30 GestureDragCancelCallback, 31 GestureScaleStartCallback, 32 GestureScaleUpdateCallback, 33 GestureScaleEndCallback, 34 GestureForcePressStartCallback, 35 GestureForcePressPeakCallback, 36 GestureForcePressEndCallback, 37 GestureForcePressUpdateCallback, 38 LongPressStartDetails, 39 LongPressMoveUpdateDetails, 40 LongPressEndDetails, 41 ScaleStartDetails, 42 ScaleUpdateDetails, 43 ScaleEndDetails, 44 TapDownDetails, 45 TapUpDetails, 46 ForcePressDetails, 47 Velocity; 48export 'package:flutter/rendering.dart' show RenderSemanticsGestureHandler; 49 50// Examples can assume: 51// bool _lights; 52// void setState(VoidCallback fn) { } 53// String _last; 54 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@optionalTypeArgs 61abstract class GestureRecognizerFactory<T extends GestureRecognizer> { 62 /// Abstract const constructor. This constructor enables subclasses to provide 63 /// const constructors so that they can be used in const expressions. 64 const GestureRecognizerFactory(); 65 66 /// Must return an instance of T. 67 T constructor(); 68 69 /// Must configure the given instance (which will have been created by 70 /// `constructor`). 71 /// 72 /// This normally means setting the callbacks. 73 void initializer(T instance); 74 75 bool _debugAssertTypeMatches(Type type) { 76 assert(type == T, 'GestureRecognizerFactory of type $T was used where type $type was specified.'); 77 return true; 78 } 79} 80 81/// Signature for closures that implement [GestureRecognizerFactory.constructor]. 82typedef GestureRecognizerFactoryConstructor<T extends GestureRecognizer> = T Function(); 83 84/// Signature for closures that implement [GestureRecognizerFactory.initializer]. 85typedef GestureRecognizerFactoryInitializer<T extends GestureRecognizer> = void Function(T instance); 86 87/// Factory for creating gesture recognizers that delegates to callbacks. 88/// 89/// Used by [RawGestureDetector.gestures]. 90class GestureRecognizerFactoryWithHandlers<T extends GestureRecognizer> extends GestureRecognizerFactory<T> { 91 /// Creates a gesture recognizer factory with the given callbacks. 92 /// 93 /// The arguments must not be null. 94 const GestureRecognizerFactoryWithHandlers(this._constructor, this._initializer) 95 : assert(_constructor != null), 96 assert(_initializer != null); 97 98 final GestureRecognizerFactoryConstructor<T> _constructor; 99 100 final GestureRecognizerFactoryInitializer<T> _initializer; 101 102 @override 103 T constructor() => _constructor(); 104 105 @override 106 void initializer(T instance) => _initializer(instance); 107} 108 109/// A widget that detects gestures. 110/// 111/// Attempts to recognize gestures that correspond to its non-null callbacks. 112/// 113/// If this widget has a child, it defers to that child for its sizing behavior. 114/// If it does not have a child, it grows to fit the parent instead. 115/// 116/// By default a GestureDetector with an invisible child ignores touches; 117/// this behavior can be controlled with [behavior]. 118/// 119/// GestureDetector also listens for accessibility events and maps 120/// them to the callbacks. To ignore accessibility events, set 121/// [excludeFromSemantics] to true. 122/// 123/// See <http://flutter.dev/gestures/> for additional information. 124/// 125/// Material design applications typically react to touches with ink splash 126/// effects. The [InkWell] class implements this effect and can be used in place 127/// of a [GestureDetector] for handling taps. 128/// 129/// {@tool sample} 130/// 131/// This example makes a rectangle react to being tapped by setting the 132/// `_lights` field: 133/// 134/// ```dart 135/// GestureDetector( 136/// onTap: () { 137/// setState(() { _lights = true; }); 138/// }, 139/// child: Container( 140/// color: Colors.yellow, 141/// child: Text('TURN LIGHTS ON'), 142/// ), 143/// ) 144/// ``` 145/// {@end-tool} 146/// 147/// ## Debugging 148/// 149/// To see how large the hit test box of a [GestureDetector] is for debugging 150/// purposes, set [debugPaintPointersEnabled] to true. 151class GestureDetector extends StatelessWidget { 152 /// Creates a widget that detects gestures. 153 /// 154 /// Pan and scale callbacks cannot be used simultaneously because scale is a 155 /// superset of pan. Simply use the scale callbacks instead. 156 /// 157 /// Horizontal and vertical drag callbacks cannot be used simultaneously 158 /// because a combination of a horizontal and vertical drag is a pan. Simply 159 /// use the pan callbacks instead. 160 /// 161 /// By default, gesture detectors contribute semantic information to the tree 162 /// that is used by assistive technology. 163 GestureDetector({ 164 Key key, 165 this.child, 166 this.onTapDown, 167 this.onTapUp, 168 this.onTap, 169 this.onTapCancel, 170 this.onSecondaryTapDown, 171 this.onSecondaryTapUp, 172 this.onSecondaryTapCancel, 173 this.onDoubleTap, 174 this.onLongPress, 175 this.onLongPressStart, 176 this.onLongPressMoveUpdate, 177 this.onLongPressUp, 178 this.onLongPressEnd, 179 this.onVerticalDragDown, 180 this.onVerticalDragStart, 181 this.onVerticalDragUpdate, 182 this.onVerticalDragEnd, 183 this.onVerticalDragCancel, 184 this.onHorizontalDragDown, 185 this.onHorizontalDragStart, 186 this.onHorizontalDragUpdate, 187 this.onHorizontalDragEnd, 188 this.onHorizontalDragCancel, 189 this.onForcePressStart, 190 this.onForcePressPeak, 191 this.onForcePressUpdate, 192 this.onForcePressEnd, 193 this.onPanDown, 194 this.onPanStart, 195 this.onPanUpdate, 196 this.onPanEnd, 197 this.onPanCancel, 198 this.onScaleStart, 199 this.onScaleUpdate, 200 this.onScaleEnd, 201 this.behavior, 202 this.excludeFromSemantics = false, 203 this.dragStartBehavior = DragStartBehavior.start, 204 }) : assert(excludeFromSemantics != null), 205 assert(dragStartBehavior != null), 206 assert(() { 207 final bool haveVerticalDrag = onVerticalDragStart != null || onVerticalDragUpdate != null || onVerticalDragEnd != null; 208 final bool haveHorizontalDrag = onHorizontalDragStart != null || onHorizontalDragUpdate != null || onHorizontalDragEnd != null; 209 final bool havePan = onPanStart != null || onPanUpdate != null || onPanEnd != null; 210 final bool haveScale = onScaleStart != null || onScaleUpdate != null || onScaleEnd != null; 211 if (havePan || haveScale) { 212 if (havePan && haveScale) { 213 throw FlutterError( 214 'Incorrect GestureDetector arguments.\n' 215 '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.' 216 ); 217 } 218 final String recognizer = havePan ? 'pan' : 'scale'; 219 if (haveVerticalDrag && haveHorizontalDrag) { 220 throw FlutterError( 221 'Incorrect GestureDetector arguments.\n' 222 'Simultaneously having a vertical drag gesture recognizer, a horizontal drag gesture recognizer, and a $recognizer gesture recognizer ' 223 'will result in the $recognizer gesture recognizer being ignored, since the other two will catch all drags.' 224 ); 225 } 226 } 227 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 /// A pointer that might cause a tap with a primary button has contacted the 237 /// screen at a particular location. 238 /// 239 /// This is called after a short timeout, even if the winning gesture has not 240 /// yet been selected. If the tap gesture wins, [onTapUp] will be called, 241 /// otherwise [onTapCancel] will be called. 242 /// 243 /// See also: 244 /// 245 /// * [kPrimaryButton], the button this callback responds to. 246 final GestureTapDownCallback onTapDown; 247 248 /// A pointer that will trigger a tap with a primary button has stopped 249 /// contacting the screen at a particular location. 250 /// 251 /// This triggers immediately before [onTap] in the case of the tap gesture 252 /// winning. If the tap gesture did not win, [onTapCancel] is called instead. 253 /// 254 /// See also: 255 /// 256 /// * [kPrimaryButton], the button this callback responds to. 257 final GestureTapUpCallback onTapUp; 258 259 /// A tap with a primary button has occurred. 260 /// 261 /// This triggers when the tap gesture wins. If the tap gesture did not win, 262 /// [onTapCancel] is called instead. 263 /// 264 /// See also: 265 /// 266 /// * [kPrimaryButton], the button this callback responds to. 267 /// * [onTapUp], which is called at the same time but includes details 268 /// regarding the pointer position. 269 final GestureTapCallback onTap; 270 271 /// The pointer that previously triggered [onTapDown] will not end up causing 272 /// a tap. 273 /// 274 /// This is called after [onTapDown], and instead of [onTapUp] and [onTap], if 275 /// the tap gesture did not win. 276 /// 277 /// See also: 278 /// 279 /// * [kPrimaryButton], the button this callback responds to. 280 final GestureTapCancelCallback onTapCancel; 281 282 /// A pointer that might cause a tap with a secondary button has contacted the 283 /// screen at a particular location. 284 /// 285 /// This is called after a short timeout, even if the winning gesture has not 286 /// yet been selected. If the tap gesture wins, [onSecondaryTapUp] will be 287 /// called, otherwise [onSecondaryTapCancel] will be called. 288 /// 289 /// See also: 290 /// 291 /// * [kSecondaryButton], the button this callback responds to. 292 final GestureTapDownCallback onSecondaryTapDown; 293 294 /// A pointer that will trigger a tap with a secondary button has stopped 295 /// contacting the screen at a particular location. 296 /// 297 /// This triggers in the case of the tap gesture winning. If the tap gesture 298 /// did not win, [onSecondaryTapCancel] is called instead. 299 /// 300 /// See also: 301 /// 302 /// * [kSecondaryButton], the button this callback responds to. 303 final GestureTapUpCallback onSecondaryTapUp; 304 305 /// The pointer that previously triggered [onSecondaryTapDown] will not end up 306 /// causing a tap. 307 /// 308 /// This is called after [onSecondaryTapDown], and instead of 309 /// [onSecondaryTapUp], if the tap gesture did not win. 310 /// 311 /// See also: 312 /// 313 /// * [kSecondaryButton], the button this callback responds to. 314 final GestureTapCancelCallback onSecondaryTapCancel; 315 316 /// The user has tapped the screen with a primary button at the same location 317 /// twice in quick succession. 318 /// 319 /// See also: 320 /// 321 /// * [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 /// Triggered when a pointer has remained in contact with the screen at the 327 /// same location for a long period of time. 328 /// 329 /// See also: 330 /// 331 /// * [kPrimaryButton], the button this callback responds to. 332 /// * [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 /// Triggered when a pointer has remained in contact with the screen at the 338 /// same location for a long period of time. 339 /// 340 /// See also: 341 /// 342 /// * [kPrimaryButton], the button this callback responds to. 343 /// * [onLongPress], which has the same timing but without the gesture details. 344 final GestureLongPressStartCallback onLongPressStart; 345 346 /// A pointer has been drag-moved after a long press with a primary button. 347 /// 348 /// See also: 349 /// 350 /// * [kPrimaryButton], the button this callback responds to. 351 final GestureLongPressMoveUpdateCallback onLongPressMoveUpdate; 352 353 /// A pointer that has triggered a long-press with a primary button has 354 /// stopped contacting the screen. 355 /// 356 /// See also: 357 /// 358 /// * [kPrimaryButton], the button this callback responds to. 359 /// * [onLongPressEnd], which has the same timing but has gesture details. 360 final GestureLongPressUpCallback onLongPressUp; 361 362 /// A pointer that has triggered a long-press with a primary button has 363 /// stopped contacting the screen. 364 /// 365 /// See also: 366 /// 367 /// * [kPrimaryButton], the button this callback responds to. 368 /// * [onLongPressUp], which has the same timing but without the gesture 369 /// details. 370 final GestureLongPressEndCallback onLongPressEnd; 371 372 /// A pointer has contacted the screen with a primary button and might begin 373 /// to move vertically. 374 /// 375 /// See also: 376 /// 377 /// * [kPrimaryButton], the button this callback responds to. 378 final GestureDragDownCallback onVerticalDragDown; 379 380 /// A pointer has contacted the screen with a primary button and has begun to 381 /// move vertically. 382 /// 383 /// See also: 384 /// 385 /// * [kPrimaryButton], the button this callback responds to. 386 final GestureDragStartCallback onVerticalDragStart; 387 388 /// A pointer that is in contact with the screen with a primary button and 389 /// moving vertically has moved in the vertical direction. 390 /// 391 /// See also: 392 /// 393 /// * [kPrimaryButton], the button this callback responds to. 394 final GestureDragUpdateCallback onVerticalDragUpdate; 395 396 /// A pointer that was previously in contact with the screen with a primary 397 /// button and moving vertically is no longer in contact with the screen and 398 /// was moving at a specific velocity when it stopped contacting the screen. 399 /// 400 /// See also: 401 /// 402 /// * [kPrimaryButton], the button this callback responds to. 403 final GestureDragEndCallback onVerticalDragEnd; 404 405 /// The pointer that previously triggered [onVerticalDragDown] did not 406 /// complete. 407 /// 408 /// See also: 409 /// 410 /// * [kPrimaryButton], the button this callback responds to. 411 final GestureDragCancelCallback onVerticalDragCancel; 412 413 /// A pointer has contacted the screen with a primary button and might begin 414 /// to move horizontally. 415 /// 416 /// See also: 417 /// 418 /// * [kPrimaryButton], the button this callback responds to. 419 final GestureDragDownCallback onHorizontalDragDown; 420 421 /// A pointer has contacted the screen with a primary button and has begun to 422 /// move horizontally. 423 /// 424 /// See also: 425 /// 426 /// * [kPrimaryButton], the button this callback responds to. 427 final GestureDragStartCallback onHorizontalDragStart; 428 429 /// A pointer that is in contact with the screen with a primary button and 430 /// moving horizontally has moved in the horizontal direction. 431 /// 432 /// See also: 433 /// 434 /// * [kPrimaryButton], the button this callback responds to. 435 final GestureDragUpdateCallback onHorizontalDragUpdate; 436 437 /// A pointer that was previously in contact with the screen with a primary 438 /// button and moving horizontally is no longer in contact with the screen and 439 /// was moving at a specific velocity when it stopped contacting the screen. 440 /// 441 /// See also: 442 /// 443 /// * [kPrimaryButton], the button this callback responds to. 444 final GestureDragEndCallback onHorizontalDragEnd; 445 446 /// The pointer that previously triggered [onHorizontalDragDown] did not 447 /// complete. 448 /// 449 /// See also: 450 /// 451 /// * [kPrimaryButton], the button this callback responds to. 452 final GestureDragCancelCallback onHorizontalDragCancel; 453 454 /// A pointer has contacted the screen with a primary button and might begin 455 /// to move. 456 /// 457 /// See also: 458 /// 459 /// * [kPrimaryButton], the button this callback responds to. 460 final GestureDragDownCallback onPanDown; 461 462 /// A pointer has contacted the screen with a primary button and has begun to 463 /// move. 464 /// 465 /// See also: 466 /// 467 /// * [kPrimaryButton], the button this callback responds to. 468 final GestureDragStartCallback onPanStart; 469 470 /// A pointer that is in contact with the screen with a primary button and 471 /// moving has moved again. 472 /// 473 /// See also: 474 /// 475 /// * [kPrimaryButton], the button this callback responds to. 476 final GestureDragUpdateCallback onPanUpdate; 477 478 /// A pointer that was previously in contact with the screen with a primary 479 /// button and moving is no longer in contact with the screen and was moving 480 /// at a specific velocity when it stopped contacting the screen. 481 /// 482 /// See also: 483 /// 484 /// * [kPrimaryButton], the button this callback responds to. 485 final GestureDragEndCallback onPanEnd; 486 487 /// The pointer that previously triggered [onPanDown] did not complete. 488 /// 489 /// See also: 490 /// 491 /// * [kPrimaryButton], the button this callback responds to. 492 final GestureDragCancelCallback onPanCancel; 493 494 /// The pointers in contact with the screen have established a focal point and 495 /// initial scale of 1.0. 496 final GestureScaleStartCallback onScaleStart; 497 498 /// The pointers in contact with the screen have indicated a new focal point 499 /// and/or scale. 500 final GestureScaleUpdateCallback onScaleUpdate; 501 502 /// The pointers are no longer in contact with the screen. 503 final GestureScaleEndCallback onScaleEnd; 504 505 /// The pointer is in contact with the screen and has pressed with sufficient 506 /// force to initiate a force press. The amount of force is at least 507 /// [ForcePressGestureRecognizer.startPressure]. 508 /// 509 /// Note that this callback will only be fired on devices with pressure 510 /// detecting screens. 511 final GestureForcePressStartCallback onForcePressStart; 512 513 /// The pointer is in contact with the screen and has pressed with the maximum 514 /// force. The amount of force is at least 515 /// [ForcePressGestureRecognizer.peakPressure]. 516 /// 517 /// Note that this callback will only be fired on devices with pressure 518 /// detecting screens. 519 final GestureForcePressPeakCallback onForcePressPeak; 520 521 /// A pointer is in contact with the screen, has previously passed the 522 /// [ForcePressGestureRecognizer.startPressure] and is either moving on the 523 /// plane of the screen, pressing the screen with varying forces or both 524 /// simultaneously. 525 /// 526 /// Note that this callback will only be fired on devices with pressure 527 /// detecting screens. 528 final GestureForcePressUpdateCallback onForcePressUpdate; 529 530 /// The pointer is no longer in contact with the screen. 531 /// 532 /// Note that this callback will only be fired on devices with pressure 533 /// detecting screens. 534 final GestureForcePressEndCallback onForcePressEnd; 535 536 /// How this gesture detector should behave during hit testing. 537 /// 538 /// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and 539 /// [HitTestBehavior.translucent] if child is null. 540 final HitTestBehavior behavior; 541 542 /// Whether to exclude these gestures from the semantics tree. For 543 /// example, the long-press gesture for showing a tooltip is 544 /// excluded because the tooltip itself is included in the semantics 545 /// tree directly and so having a gesture to show it would result in 546 /// duplication of information. 547 final bool excludeFromSemantics; 548 549 /// Determines the way that drag start behavior is handled. 550 /// 551 /// If set to [DragStartBehavior.start], gesture drag behavior will 552 /// begin upon the detection of a drag gesture. If set to 553 /// [DragStartBehavior.down] it will begin when a down event is first detected. 554 /// 555 /// In general, setting this to [DragStartBehavior.start] will make drag 556 /// animation smoother and setting it to [DragStartBehavior.down] will make 557 /// drag behavior feel slightly more reactive. 558 /// 559 /// By default, the drag start behavior is [DragStartBehavior.start]. 560 /// 561 /// Only the [onStart] callbacks for the [VerticalDragGestureRecognizer], 562 /// [HorizontalDragGestureRecognizer] and [PanGestureRecognizer] are affected 563 /// by this setting. 564 /// 565 /// See also: 566 /// 567 /// * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors. 568 final DragStartBehavior dragStartBehavior; 569 570 @override 571 Widget build(BuildContext context) { 572 final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{}; 573 574 if ( 575 onTapDown != null || 576 onTapUp != null || 577 onTap != null || 578 onTapCancel != null || 579 onSecondaryTapDown != null || 580 onSecondaryTapUp != null || 581 onSecondaryTapCancel != null 582 ) { 583 gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>( 584 () => TapGestureRecognizer(debugOwner: this), 585 (TapGestureRecognizer instance) { 586 instance 587 ..onTapDown = onTapDown 588 ..onTapUp = onTapUp 589 ..onTap = onTap 590 ..onTapCancel = onTapCancel 591 ..onSecondaryTapDown = onSecondaryTapDown 592 ..onSecondaryTapUp = onSecondaryTapUp 593 ..onSecondaryTapCancel = onSecondaryTapCancel; 594 }, 595 ); 596 } 597 598 if (onDoubleTap != null) { 599 gestures[DoubleTapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>( 600 () => DoubleTapGestureRecognizer(debugOwner: this), 601 (DoubleTapGestureRecognizer instance) { 602 instance 603 ..onDoubleTap = onDoubleTap; 604 }, 605 ); 606 } 607 608 if (onLongPress != null || 609 onLongPressUp != null || 610 onLongPressStart != null || 611 onLongPressMoveUpdate != null || 612 onLongPressEnd != null) { 613 gestures[LongPressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>( 614 () => LongPressGestureRecognizer(debugOwner: this), 615 (LongPressGestureRecognizer instance) { 616 instance 617 ..onLongPress = onLongPress 618 ..onLongPressStart = onLongPressStart 619 ..onLongPressMoveUpdate = onLongPressMoveUpdate 620 ..onLongPressEnd =onLongPressEnd 621 ..onLongPressUp = onLongPressUp; 622 }, 623 ); 624 } 625 626 if (onVerticalDragDown != null || 627 onVerticalDragStart != null || 628 onVerticalDragUpdate != null || 629 onVerticalDragEnd != null || 630 onVerticalDragCancel != null) { 631 gestures[VerticalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>( 632 () => VerticalDragGestureRecognizer(debugOwner: this), 633 (VerticalDragGestureRecognizer instance) { 634 instance 635 ..onDown = onVerticalDragDown 636 ..onStart = onVerticalDragStart 637 ..onUpdate = onVerticalDragUpdate 638 ..onEnd = onVerticalDragEnd 639 ..onCancel = onVerticalDragCancel 640 ..dragStartBehavior = dragStartBehavior; 641 }, 642 ); 643 } 644 645 if (onHorizontalDragDown != null || 646 onHorizontalDragStart != null || 647 onHorizontalDragUpdate != null || 648 onHorizontalDragEnd != null || 649 onHorizontalDragCancel != null) { 650 gestures[HorizontalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>( 651 () => HorizontalDragGestureRecognizer(debugOwner: this), 652 (HorizontalDragGestureRecognizer instance) { 653 instance 654 ..onDown = onHorizontalDragDown 655 ..onStart = onHorizontalDragStart 656 ..onUpdate = onHorizontalDragUpdate 657 ..onEnd = onHorizontalDragEnd 658 ..onCancel = onHorizontalDragCancel 659 ..dragStartBehavior = dragStartBehavior; 660 }, 661 ); 662 } 663 664 if (onPanDown != null || 665 onPanStart != null || 666 onPanUpdate != null || 667 onPanEnd != null || 668 onPanCancel != null) { 669 gestures[PanGestureRecognizer] = GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>( 670 () => PanGestureRecognizer(debugOwner: this), 671 (PanGestureRecognizer instance) { 672 instance 673 ..onDown = onPanDown 674 ..onStart = onPanStart 675 ..onUpdate = onPanUpdate 676 ..onEnd = onPanEnd 677 ..onCancel = onPanCancel 678 ..dragStartBehavior = dragStartBehavior; 679 }, 680 ); 681 } 682 683 if (onScaleStart != null || onScaleUpdate != null || onScaleEnd != null) { 684 gestures[ScaleGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ScaleGestureRecognizer>( 685 () => ScaleGestureRecognizer(debugOwner: this), 686 (ScaleGestureRecognizer instance) { 687 instance 688 ..onStart = onScaleStart 689 ..onUpdate = onScaleUpdate 690 ..onEnd = onScaleEnd; 691 }, 692 ); 693 } 694 695 if (onForcePressStart != null || 696 onForcePressPeak != null || 697 onForcePressUpdate != null || 698 onForcePressEnd != null) { 699 gestures[ForcePressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ForcePressGestureRecognizer>( 700 () => ForcePressGestureRecognizer(debugOwner: this), 701 (ForcePressGestureRecognizer instance) { 702 instance 703 ..onStart = onForcePressStart 704 ..onPeak = onForcePressPeak 705 ..onUpdate = onForcePressUpdate 706 ..onEnd = onForcePressEnd; 707 }, 708 ); 709 } 710 711 return RawGestureDetector( 712 gestures: gestures, 713 behavior: behavior, 714 excludeFromSemantics: excludeFromSemantics, 715 child: child, 716 ); 717 } 718 @override 719 void debugFillProperties(DiagnosticPropertiesBuilder properties) { 720 super.debugFillProperties(properties); 721 properties.add(EnumProperty<DragStartBehavior>('startBehavior', dragStartBehavior)); 722 } 723} 724 725/// A widget that detects gestures described by the given gesture 726/// factories. 727/// 728/// For common gestures, use a [GestureRecognizer]. 729/// [RawGestureDetector] is useful primarily when developing your 730/// own gesture recognizers. 731/// 732/// Configuring the gesture recognizers requires a carefully constructed map, as 733/// described in [gestures] and as shown in the example below. 734/// 735/// {@tool sample} 736/// 737/// This example shows how to hook up a [TapGestureRecognizer]. It assumes that 738/// the code is being used inside a [State] object with a `_last` field that is 739/// then displayed as the child of the gesture detector. 740/// 741/// ```dart 742/// RawGestureDetector( 743/// gestures: <Type, GestureRecognizerFactory>{ 744/// TapGestureRecognizer: GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>( 745/// () => TapGestureRecognizer(), 746/// (TapGestureRecognizer instance) { 747/// instance 748/// ..onTapDown = (TapDownDetails details) { setState(() { _last = 'down'; }); } 749/// ..onTapUp = (TapUpDetails details) { setState(() { _last = 'up'; }); } 750/// ..onTap = () { setState(() { _last = 'tap'; }); } 751/// ..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/// See also: 761/// 762/// * [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. 765class RawGestureDetector extends StatefulWidget { 766 /// Creates a widget that detects gestures. 767 /// 768 /// Gesture detectors can contribute semantic information to the tree that is 769 /// used by assistive technology. The behavior can be configured by 770 /// [semantics], or disabled with [excludeFromSemantics]. 771 const RawGestureDetector({ 772 Key key, 773 this.child, 774 this.gestures = const <Type, GestureRecognizerFactory>{}, 775 this.behavior, 776 this.excludeFromSemantics = false, 777 this.semantics, 778 }) : assert(gestures != null), 779 assert(excludeFromSemantics != null), 780 super(key: key); 781 782 /// The widget below this widget in the tree. 783 /// 784 /// {@macro flutter.widgets.child} 785 final Widget child; 786 787 /// The gestures that this widget will attempt to recognize. 788 /// 789 /// This should be a map from [GestureRecognizer] subclasses to 790 /// [GestureRecognizerFactory] subclasses specialized with the same type. 791 /// 792 /// This value can be late-bound at layout time using 793 /// [RawGestureDetectorState.replaceGestureRecognizers]. 794 final Map<Type, GestureRecognizerFactory> gestures; 795 796 /// How this gesture detector should behave during hit testing. 797 /// 798 /// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and 799 /// [HitTestBehavior.translucent] if child is null. 800 final HitTestBehavior behavior; 801 802 /// Whether to exclude these gestures from the semantics tree. For 803 /// example, the long-press gesture for showing a tooltip is 804 /// excluded because the tooltip itself is included in the semantics 805 /// tree directly and so having a gesture to show it would result in 806 /// duplication of information. 807 final bool excludeFromSemantics; 808 809 /// Describes the semantics notations that should be added to the underlying 810 /// render object [RenderSemanticsGestureHandler]. 811 /// 812 /// It has no effect if [excludeFromSemantics] is true. 813 /// 814 /// When [semantics] is null, [RawGestureDetector] will fall back to a 815 /// default delegate which checks if the detector owns certain gesture 816 /// recognizers and calls their callbacks if they exist: 817 /// 818 /// * During a semantic tap, it calls [TapGestureRecognizer]'s 819 /// `onTapDown`, `onTapUp`, and `onTap`. 820 /// * During a semantic long press, it calls [LongPressGestureRecognizer]'s 821 /// `onLongPressStart`, `onLongPress`, `onLongPressEnd` and `onLongPressUp`. 822 /// * During a semantic horizontal drag, it calls [HorizontalDragGestureRecognizer]'s 823 /// `onDown`, `onStart`, `onUpdate` and `onEnd`, then 824 /// [PanGestureRecognizer]'s `onDown`, `onStart`, `onUpdate` and `onEnd`. 825 /// * During a semantic vertical drag, it calls [VerticalDragGestureRecognizer]'s 826 /// `onDown`, `onStart`, `onUpdate` and `onEnd`, then 827 /// [PanGestureRecognizer]'s `onDown`, `onStart`, `onUpdate` and `onEnd`. 828 /// 829 /// {@tool sample} 830 /// This custom gesture detector listens to force presses, while also allows 831 /// the same callback to be triggered by semantic long presses. 832 /// 833 /// ```dart 834 /// class ForcePressGestureDetectorWithSemantics extends StatelessWidget { 835 /// const ForcePressGestureDetectorWithSemantics({ 836 /// this.child, 837 /// this.onForcePress, 838 /// }); 839 /// 840 /// final Widget child; 841 /// final VoidCallback onForcePress; 842 /// 843 /// @override 844 /// Widget build(BuildContext context) { 845 /// return RawGestureDetector( 846 /// gestures: <Type, GestureRecognizerFactory>{ 847 /// ForcePressGestureRecognizer: GestureRecognizerFactoryWithHandlers<ForcePressGestureRecognizer>( 848 /// () => ForcePressGestureRecognizer(debugOwner: this), 849 /// (ForcePressGestureRecognizer instance) { 850 /// instance.onStart = (_) => onForcePress(); 851 /// } 852 /// ), 853 /// }, 854 /// behavior: HitTestBehavior.opaque, 855 /// semantics: _LongPressSemanticsDelegate(onForcePress), 856 /// child: child, 857 /// ); 858 /// } 859 /// } 860 /// 861 /// class _LongPressSemanticsDelegate extends SemanticsGestureDelegate { 862 /// _LongPressSemanticsDelegate(this.onLongPress); 863 /// 864 /// VoidCallback onLongPress; 865 /// 866 /// @override 867 /// void assignSemantics(RenderSemanticsGestureHandler renderObject) { 868 /// renderObject.onLongPress = onLongPress; 869 /// } 870 /// } 871 /// ``` 872 /// {@end-tool} 873 final SemanticsGestureDelegate semantics; 874 875 @override 876 RawGestureDetectorState createState() => RawGestureDetectorState(); 877} 878 879/// State for a [RawGestureDetector]. 880class RawGestureDetectorState extends State<RawGestureDetector> { 881 Map<Type, GestureRecognizer> _recognizers = const <Type, GestureRecognizer>{}; 882 SemanticsGestureDelegate _semantics; 883 884 @override 885 void initState() { 886 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 if (!(oldWidget.semantics == null && widget.semantics == null)) { 895 _semantics = widget.semantics ?? _DefaultSemanticsGestureDelegate(this); 896 } 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 /// gesture detector, to update the list of active gesture 903 /// recognizers. 904 /// 905 /// The typical use case is [Scrollable]s, which put their viewport 906 /// in their gesture detector, and then need to know the dimensions 907 /// of the viewport and the viewport's child to determine whether 908 /// the gesture detector should be enabled. 909 /// 910 /// The argument should follow the same conventions as 911 /// [RawGestureDetector.gestures]. It acts like a temporary replacement for 912 /// that value until the next build. 913 void replaceGestureRecognizers(Map<Type, GestureRecognizerFactory> gestures) { 914 assert(() { 915 if (!context.findRenderObject().owner.debugDoingLayout) { 916 throw FlutterError( 917 'Unexpected call to replaceGestureRecognizers() method of RawGestureDetectorState.\n' 918 'The replaceGestureRecognizers() method can only be called during the layout phase. ' 919 'To set the gesture recognizers at other times, trigger a new build using setState() ' 920 'and provide the new gesture recognizers as constructor arguments to the corresponding ' 921 'RawGestureDetector or GestureDetector object.' 922 ); 923 } 924 return true; 925 }()); 926 _syncAll(gestures); 927 if (!widget.excludeFromSemantics) { 928 final RenderSemanticsGestureHandler semanticsGestureHandler = context.findRenderObject(); 929 _updateSemanticsForRenderObject(semanticsGestureHandler); 930 } 931 } 932 933 /// This method can be called outside of the build phase to filter the list of 934 /// available semantic actions. 935 /// 936 /// The actual filtering is happening in the next frame and a frame will be 937 /// scheduled if non is pending. 938 /// 939 /// This is used by [Scrollable] to configure system accessibility tools so 940 /// that they know in which direction a particular list can be scrolled. 941 /// 942 /// 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 void replaceSemanticsActions(Set<SemanticsAction> actions) { 945 assert(() { 946 final Element element = context; 947 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 if (!widget.excludeFromSemantics) { 956 final RenderSemanticsGestureHandler semanticsGestureHandler = context.findRenderObject(); 957 semanticsGestureHandler.validActions = actions; // will call _markNeedsSemanticsUpdate(), if required. 958 } 959 } 960 961 @override 962 void dispose() { 963 for (GestureRecognizer recognizer in _recognizers.values) 964 recognizer.dispose(); 965 _recognizers = null; 966 super.dispose(); 967 } 968 969 void _syncAll(Map<Type, GestureRecognizerFactory> gestures) { 970 assert(_recognizers != null); 971 final Map<Type, GestureRecognizer> oldRecognizers = _recognizers; 972 _recognizers = <Type, GestureRecognizer>{}; 973 for (Type type in gestures.keys) { 974 assert(gestures[type] != null); 975 assert(gestures[type]._debugAssertTypeMatches(type)); 976 assert(!_recognizers.containsKey(type)); 977 _recognizers[type] = oldRecognizers[type] ?? gestures[type].constructor(); 978 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.'); 979 gestures[type].initializer(_recognizers[type]); 980 } 981 for (Type type in oldRecognizers.keys) { 982 if (!_recognizers.containsKey(type)) 983 oldRecognizers[type].dispose(); 984 } 985 } 986 987 void _handlePointerDown(PointerDownEvent event) { 988 assert(_recognizers != null); 989 for (GestureRecognizer recognizer in _recognizers.values) 990 recognizer.addPointer(event); 991 } 992 993 HitTestBehavior get _defaultBehavior { 994 return widget.child == null ? HitTestBehavior.translucent : HitTestBehavior.deferToChild; 995 } 996 997 void _updateSemanticsForRenderObject(RenderSemanticsGestureHandler renderObject) { 998 assert(!widget.excludeFromSemantics); 999 assert(_semantics != null); 1000 _semantics.assignSemantics(renderObject); 1001 } 1002 1003 @override 1004 Widget build(BuildContext context) { 1005 Widget result = Listener( 1006 onPointerDown: _handlePointerDown, 1007 behavior: widget.behavior ?? _defaultBehavior, 1008 child: widget.child, 1009 ); 1010 if (!widget.excludeFromSemantics) 1011 result = _GestureSemantics( 1012 child: result, 1013 assignSemantics: _updateSemanticsForRenderObject, 1014 ); 1015 return result; 1016 } 1017 1018 @override 1019 void debugFillProperties(DiagnosticPropertiesBuilder properties) { 1020 super.debugFillProperties(properties); 1021 if (_recognizers == null) { 1022 properties.add(DiagnosticsNode.message('DISPOSED')); 1023 } else { 1024 final List<String> gestures = _recognizers.values.map<String>((GestureRecognizer recognizer) => recognizer.debugDescription).toList(); 1025 properties.add(IterableProperty<String>('gestures', gestures, ifEmpty: '<none>')); 1026 properties.add(IterableProperty<GestureRecognizer>('recognizers', _recognizers.values, level: DiagnosticLevel.fine)); 1027 properties.add(DiagnosticsProperty<bool>('excludeFromSemantics', widget.excludeFromSemantics, defaultValue: false)); 1028 if (!widget.excludeFromSemantics) { 1029 properties.add(DiagnosticsProperty<SemanticsGestureDelegate>('semantics', widget.semantics, defaultValue: null)); 1030 } 1031 } 1032 properties.add(EnumProperty<HitTestBehavior>('behavior', widget.behavior, defaultValue: null)); 1033 } 1034} 1035 1036typedef _AssignSemantics = void Function(RenderSemanticsGestureHandler); 1037 1038class _GestureSemantics extends SingleChildRenderObjectWidget { 1039 const _GestureSemantics({ 1040 Key key, 1041 Widget child, 1042 @required this.assignSemantics, 1043 }) : assert(assignSemantics != null), 1044 super(key: key, child: child); 1045 1046 final _AssignSemantics assignSemantics; 1047 1048 @override 1049 RenderSemanticsGestureHandler createRenderObject(BuildContext context) { 1050 final RenderSemanticsGestureHandler renderObject = RenderSemanticsGestureHandler(); 1051 assignSemantics(renderObject); 1052 return renderObject; 1053 } 1054 1055 @override 1056 void updateRenderObject(BuildContext context, RenderSemanticsGestureHandler renderObject) { 1057 assignSemantics(renderObject); 1058 } 1059} 1060 1061/// A base class that describes what semantics notations a [RawGestureDetector] 1062/// should add to the render object [RenderSemanticsGestureHandler]. 1063/// 1064/// It is used to allow custom [GestureDetector]s to add semantics notations. 1065abstract class SemanticsGestureDelegate { 1066 /// Create a delegate of gesture semantics. 1067 const SemanticsGestureDelegate(); 1068 1069 /// Assigns semantics notations to the [RenderSemanticsGestureHandler] render 1070 /// object of the gesture detector. 1071 /// 1072 /// This method is called when the widget is created, updated, or during 1073 /// [RawGestureDetector.replaceGestureRecognizers]. 1074 void assignSemantics(RenderSemanticsGestureHandler renderObject); 1075 1076 @override 1077 String toString() => '$runtimeType()'; 1078} 1079 1080// The default semantics delegate of [RawGestureDetector]. Its behavior is 1081// described in [RawGestureDetector.semantics]. 1082// 1083// For readers who come here to learn how to write custom semantics delegates: 1084// this is not a proper sample code. It has access to the detector state as well 1085// as its private properties, which are inaccessible normally. It is designed 1086// this way in order to work independenly in a [RawGestureRecognizer] to 1087// preserve existing behavior. 1088// 1089// Instead, a normal delegate will store callbacks as properties, and use them 1090// in `assignSemantics`. 1091class _DefaultSemanticsGestureDelegate extends SemanticsGestureDelegate { 1092 _DefaultSemanticsGestureDelegate(this.detectorState); 1093 1094 final RawGestureDetectorState detectorState; 1095 1096 @override 1097 void assignSemantics(RenderSemanticsGestureHandler renderObject) { 1098 assert(!detectorState.widget.excludeFromSemantics); 1099 final Map<Type, GestureRecognizer> recognizers = detectorState._recognizers; 1100 renderObject 1101 ..onTap = _getTapHandler(recognizers) 1102 ..onLongPress = _getLongPressHandler(recognizers) 1103 ..onHorizontalDragUpdate = _getHorizontalDragUpdateHandler(recognizers) 1104 ..onVerticalDragUpdate = _getVerticalDragUpdateHandler(recognizers); 1105 } 1106 1107 GestureTapCallback _getTapHandler(Map<Type, GestureRecognizer> recognizers) { 1108 final TapGestureRecognizer tap = recognizers[TapGestureRecognizer]; 1109 if (tap == null) 1110 return null; 1111 assert(tap is TapGestureRecognizer); 1112 1113 return () { 1114 assert(tap != null); 1115 if (tap.onTapDown != null) 1116 tap.onTapDown(TapDownDetails()); 1117 if (tap.onTapUp != null) 1118 tap.onTapUp(TapUpDetails()); 1119 if (tap.onTap != null) 1120 tap.onTap(); 1121 }; 1122 } 1123 1124 GestureLongPressCallback _getLongPressHandler(Map<Type, GestureRecognizer> recognizers) { 1125 final LongPressGestureRecognizer longPress = recognizers[LongPressGestureRecognizer]; 1126 if (longPress == null) 1127 return null; 1128 1129 return () { 1130 assert(longPress is LongPressGestureRecognizer); 1131 if (longPress.onLongPressStart != null) 1132 longPress.onLongPressStart(const LongPressStartDetails()); 1133 if (longPress.onLongPress != null) 1134 longPress.onLongPress(); 1135 if (longPress.onLongPressEnd != null) 1136 longPress.onLongPressEnd(const LongPressEndDetails()); 1137 if (longPress.onLongPressUp != null) 1138 longPress.onLongPressUp(); 1139 }; 1140 } 1141 1142 GestureDragUpdateCallback _getHorizontalDragUpdateHandler(Map<Type, GestureRecognizer> recognizers) { 1143 final HorizontalDragGestureRecognizer horizontal = recognizers[HorizontalDragGestureRecognizer]; 1144 final PanGestureRecognizer pan = recognizers[PanGestureRecognizer]; 1145 1146 final GestureDragUpdateCallback horizontalHandler = horizontal == null ? 1147 null : 1148 (DragUpdateDetails details) { 1149 assert(horizontal is HorizontalDragGestureRecognizer); 1150 if (horizontal.onDown != null) 1151 horizontal.onDown(DragDownDetails()); 1152 if (horizontal.onStart != null) 1153 horizontal.onStart(DragStartDetails()); 1154 if (horizontal.onUpdate != null) 1155 horizontal.onUpdate(details); 1156 if (horizontal.onEnd != null) 1157 horizontal.onEnd(DragEndDetails(primaryVelocity: 0.0)); 1158 }; 1159 1160 final GestureDragUpdateCallback panHandler = pan == null ? 1161 null : 1162 (DragUpdateDetails details) { 1163 assert(pan is PanGestureRecognizer); 1164 if (pan.onDown != null) 1165 pan.onDown(DragDownDetails()); 1166 if (pan.onStart != null) 1167 pan.onStart(DragStartDetails()); 1168 if (pan.onUpdate != null) 1169 pan.onUpdate(details); 1170 if (pan.onEnd != null) 1171 pan.onEnd(DragEndDetails()); 1172 }; 1173 1174 if (horizontalHandler == null && panHandler == null) 1175 return null; 1176 return (DragUpdateDetails details) { 1177 if (horizontalHandler != null) 1178 horizontalHandler(details); 1179 if (panHandler != null) 1180 panHandler(details); 1181 }; 1182 } 1183 1184 GestureDragUpdateCallback _getVerticalDragUpdateHandler(Map<Type, GestureRecognizer> recognizers) { 1185 final VerticalDragGestureRecognizer vertical = recognizers[VerticalDragGestureRecognizer]; 1186 final PanGestureRecognizer pan = recognizers[PanGestureRecognizer]; 1187 1188 final GestureDragUpdateCallback verticalHandler = vertical == null ? 1189 null : 1190 (DragUpdateDetails details) { 1191 assert(vertical is VerticalDragGestureRecognizer); 1192 if (vertical.onDown != null) 1193 vertical.onDown(DragDownDetails()); 1194 if (vertical.onStart != null) 1195 vertical.onStart(DragStartDetails()); 1196 if (vertical.onUpdate != null) 1197 vertical.onUpdate(details); 1198 if (vertical.onEnd != null) 1199 vertical.onEnd(DragEndDetails(primaryVelocity: 0.0)); 1200 }; 1201 1202 final GestureDragUpdateCallback panHandler = pan == null ? 1203 null : 1204 (DragUpdateDetails details) { 1205 assert(pan is PanGestureRecognizer); 1206 if (pan.onDown != null) 1207 pan.onDown(DragDownDetails()); 1208 if (pan.onStart != null) 1209 pan.onStart(DragStartDetails()); 1210 if (pan.onUpdate != null) 1211 pan.onUpdate(details); 1212 if (pan.onEnd != null) 1213 pan.onEnd(DragEndDetails()); 1214 }; 1215 1216 if (verticalHandler == null && panHandler == null) 1217 return null; 1218 return (DragUpdateDetails details) { 1219 if (verticalHandler != null) 1220 verticalHandler(details); 1221 if (panHandler != null) 1222 panHandler(details); 1223 }; 1224 } 1225} 1226