gesture_detector.dart 21.2 KB
Newer Older
1 2 3 4
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

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

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

11
export 'package:flutter/gestures.dart' show
12 13 14 15
  DragDownDetails,
  DragStartDetails,
  DragUpdateDetails,
  DragEndDetails,
16 17
  GestureTapDownCallback,
  GestureTapUpCallback,
18
  GestureTapCallback,
19
  GestureTapCancelCallback,
20
  GestureLongPressCallback,
21
  GestureDragDownCallback,
22 23 24
  GestureDragStartCallback,
  GestureDragUpdateCallback,
  GestureDragEndCallback,
25
  GestureDragCancelCallback,
26 27
  GestureScaleStartCallback,
  GestureScaleUpdateCallback,
28
  GestureScaleEndCallback,
29 30 31
  ScaleStartDetails,
  ScaleUpdateDetails,
  ScaleEndDetails,
32 33
  TapDownDetails,
  TapUpDetails,
34
  Velocity;
35

36 37 38 39
/// Signature for creating gesture recognizers.
///
/// The `recognizer` argument is the gesture recognizer that currently occupies
/// the slot for which a gesture recognizer is being created.
40
typedef GestureRecognizer GestureRecognizerFactory(GestureRecognizer recognizer);
41

42 43
/// A widget that detects gestures.
///
44
/// Attempts to recognize gestures that correspond to its non-null callbacks.
45
///
46 47 48
/// 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.
///
Hixie's avatar
Hixie committed
49 50 51 52
/// GestureDetector also listens for accessibility events and maps
/// them to the callbacks. To ignore accessibility events, set
/// [excludeFromSemantics] to true.
///
53
/// See http://flutter.io/gestures/ for additional information.
54
class GestureDetector extends StatelessWidget {
55 56 57 58 59 60 61 62 63 64 65
  /// 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.
66
  GestureDetector({
67 68
    Key key,
    this.child,
69
    this.onTapDown,
70 71
    this.onTapUp,
    this.onTap,
72
    this.onTapCancel,
73
    this.onDoubleTap,
74
    this.onLongPress,
75
    this.onVerticalDragDown,
76 77 78
    this.onVerticalDragStart,
    this.onVerticalDragUpdate,
    this.onVerticalDragEnd,
79 80
    this.onVerticalDragCancel,
    this.onHorizontalDragDown,
81 82 83
    this.onHorizontalDragStart,
    this.onHorizontalDragUpdate,
    this.onHorizontalDragEnd,
84 85
    this.onHorizontalDragCancel,
    this.onPanDown,
86 87
    this.onPanStart,
    this.onPanUpdate,
88
    this.onPanEnd,
89
    this.onPanCancel,
90 91
    this.onScaleStart,
    this.onScaleUpdate,
92
    this.onScaleEnd,
Hixie's avatar
Hixie committed
93 94
    this.behavior,
    this.excludeFromSemantics: false
95 96 97 98 99 100 101 102
  }) : super(key: key) {
    assert(excludeFromSemantics != null);
    assert(() {
      bool haveVerticalDrag = onVerticalDragStart != null || onVerticalDragUpdate != null || onVerticalDragEnd != null;
      bool haveHorizontalDrag = onHorizontalDragStart != null || onHorizontalDragUpdate != null || onHorizontalDragEnd != null;
      bool havePan = onPanStart != null || onPanUpdate != null || onPanEnd != null;
      bool haveScale = onScaleStart != null || onScaleUpdate != null || onScaleEnd != null;
      if (havePan || haveScale) {
103
        if (havePan && haveScale) {
104
          throw new FlutterError(
105 106 107 108
            '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.'
          );
        }
109
        String recognizer = havePan ? 'pan' : 'scale';
110
        if (haveVerticalDrag && haveHorizontalDrag) {
111
          throw new FlutterError(
112 113 114 115 116
            '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.'
          );
        }
117 118 119 120
      }
      return true;
    });
  }
121

122
  /// The widget below this widget in the tree.
123
  final Widget child;
124

125 126
  /// A pointer that might cause a tap has contacted the screen at a particular
  /// location.
127
  final GestureTapDownCallback onTapDown;
128 129 130

  /// A pointer that will trigger a tap has stopped contacting the screen at a
  /// particular location.
Hixie's avatar
Hixie committed
131
  final GestureTapUpCallback onTapUp;
132 133

  /// A tap has occurred.
134
  final GestureTapCallback onTap;
135

136 137
  /// The pointer that previously triggered [onTapDown] will not end up causing
  /// a tap.
138
  final GestureTapCancelCallback onTapCancel;
139 140 141

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

144 145
  /// A pointer has remained in contact with the screen at the same location for
  /// a long period of time.
146
  final GestureLongPressCallback onLongPress;
147

148
  /// A pointer has contacted the screen and might begin to move vertically.
149 150 151
  final GestureDragDownCallback onVerticalDragDown;

  /// A pointer has contacted the screen and has begun to move vertically.
152
  final GestureDragStartCallback onVerticalDragStart;
153 154 155

  /// A pointer that is in contact with the screen and moving vertically has
  /// moved in the vertical direction.
156
  final GestureDragUpdateCallback onVerticalDragUpdate;
157 158 159 160

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

163 164
  /// The pointer that previously triggered [onVerticalDragDown] did not
  /// complete.
165 166
  final GestureDragCancelCallback onVerticalDragCancel;

167
  /// A pointer has contacted the screen and might begin to move horizontally.
168 169 170
  final GestureDragDownCallback onHorizontalDragDown;

  /// A pointer has contacted the screen and has begun to move horizontally.
171
  final GestureDragStartCallback onHorizontalDragStart;
172 173 174

  /// A pointer that is in contact with the screen and moving horizontally has
  /// moved in the horizontal direction.
175
  final GestureDragUpdateCallback onHorizontalDragUpdate;
176 177 178 179

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

182 183
  /// The pointer that previously triggered [onHorizontalDragDown] did not
  /// complete.
184 185
  final GestureDragCancelCallback onHorizontalDragCancel;

186
  /// A pointer has contacted the screen and might begin to move.
187
  final GestureDragDownCallback onPanDown;
188 189

  /// A pointer has contacted the screen and has begun to move.
190
  final GestureDragStartCallback onPanStart;
191 192

  /// A pointer that is in contact with the screen and moving has moved again.
193
  final GestureDragUpdateCallback onPanUpdate;
194 195 196 197

  /// A pointer that was previously in contact with the screen and moving
  /// is no longer in contact with the screen and was moving at a specific
  /// velocity when it stopped contacting the screen.
198
  final GestureDragEndCallback onPanEnd;
199 200

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

203 204
  /// The pointers in contact with the screen have established a focal point and
  /// initial scale of 1.0.
205
  final GestureScaleStartCallback onScaleStart;
206 207 208

  /// The pointers in contact with the screen have indicated a new focal point
  /// and/or scale.
209
  final GestureScaleUpdateCallback onScaleUpdate;
210 211

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

214
  /// How this gesture detector should behave during hit testing.
215 216
  final HitTestBehavior behavior;

Hixie's avatar
Hixie committed
217 218 219 220 221 222 223
  /// 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;

224
  @override
225 226 227 228
  Widget build(BuildContext context) {
    Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};

    if (onTapDown != null || onTapUp != null || onTap != null || onTapCancel != null) {
229
      gestures[TapGestureRecognizer] = (TapGestureRecognizer recognizer) { // ignore: invalid_assignment, https://github.com/flutter/flutter/issues/5771
230
        return (recognizer ??= new TapGestureRecognizer())
231 232 233 234 235 236
          ..onTapDown = onTapDown
          ..onTapUp = onTapUp
          ..onTap = onTap
          ..onTapCancel = onTapCancel;
      };
    }
237

238
    if (onDoubleTap != null) {
239
      gestures[DoubleTapGestureRecognizer] = (DoubleTapGestureRecognizer recognizer) { // ignore: invalid_assignment, https://github.com/flutter/flutter/issues/5771
240
        return (recognizer ??= new DoubleTapGestureRecognizer())
241 242 243
          ..onDoubleTap = onDoubleTap;
      };
    }
244

245
    if (onLongPress != null) {
246
      gestures[LongPressGestureRecognizer] = (LongPressGestureRecognizer recognizer) { // ignore: invalid_assignment, https://github.com/flutter/flutter/issues/5771
247
        return (recognizer ??= new LongPressGestureRecognizer())
248 249 250
          ..onLongPress = onLongPress;
      };
    }
251

252 253 254 255 256
    if (onVerticalDragDown != null ||
        onVerticalDragStart != null ||
        onVerticalDragUpdate != null ||
        onVerticalDragEnd != null ||
        onVerticalDragCancel != null) {
257
      gestures[VerticalDragGestureRecognizer] = (VerticalDragGestureRecognizer recognizer) { // ignore: invalid_assignment, https://github.com/flutter/flutter/issues/5771
258
        return (recognizer ??= new VerticalDragGestureRecognizer())
259
          ..onDown = onVerticalDragDown
260 261
          ..onStart = onVerticalDragStart
          ..onUpdate = onVerticalDragUpdate
262 263
          ..onEnd = onVerticalDragEnd
          ..onCancel = onVerticalDragCancel;
264 265
      };
    }
266

267 268 269 270 271
    if (onHorizontalDragDown != null ||
        onHorizontalDragStart != null ||
        onHorizontalDragUpdate != null ||
        onHorizontalDragEnd != null ||
        onHorizontalDragCancel != null) {
272
      gestures[HorizontalDragGestureRecognizer] = (HorizontalDragGestureRecognizer recognizer) { // ignore: invalid_assignment, https://github.com/flutter/flutter/issues/5771
273
        return (recognizer ??= new HorizontalDragGestureRecognizer())
274
          ..onDown = onHorizontalDragDown
275 276
          ..onStart = onHorizontalDragStart
          ..onUpdate = onHorizontalDragUpdate
277 278
          ..onEnd = onHorizontalDragEnd
          ..onCancel = onHorizontalDragCancel;
279 280
      };
    }
281

282 283 284 285 286
    if (onPanDown != null ||
        onPanStart != null ||
        onPanUpdate != null ||
        onPanEnd != null ||
        onPanCancel != null) {
287
      gestures[PanGestureRecognizer] = (PanGestureRecognizer recognizer) { // ignore: invalid_assignment, https://github.com/flutter/flutter/issues/5771
288
        return (recognizer ??= new PanGestureRecognizer())
289
          ..onDown = onPanDown
290 291
          ..onStart = onPanStart
          ..onUpdate = onPanUpdate
292 293
          ..onEnd = onPanEnd
          ..onCancel = onPanCancel;
294
      };
295
    }
296

297
    if (onScaleStart != null || onScaleUpdate != null || onScaleEnd != null) {
298
      gestures[ScaleGestureRecognizer] = (ScaleGestureRecognizer recognizer) { // ignore: invalid_assignment, https://github.com/flutter/flutter/issues/5771
299
        return (recognizer ??= new ScaleGestureRecognizer())
300 301 302 303
          ..onStart = onScaleStart
          ..onUpdate = onScaleUpdate
          ..onEnd = onScaleEnd;
      };
304
    }
305

306 307 308 309 310 311
    return new RawGestureDetector(
      gestures: gestures,
      behavior: behavior,
      excludeFromSemantics: excludeFromSemantics,
      child: child
    );
312
  }
313
}
314

315 316 317 318 319 320
/// A widget that detects gestures described by the given gesture
/// factories.
///
/// For common gestures, use a [GestureRecognizer].
/// RawGestureDetector is useful primarily when developing your
/// own gesture recognizers.
321
class RawGestureDetector extends StatefulWidget {
322 323 324 325
  /// Creates a widget that detects gestures.
  ///
  /// By default, gesture detectors contribute semantic information to the tree
  /// that is used by assistive technology.
326 327 328 329 330 331 332 333 334
  RawGestureDetector({
    Key key,
    this.child,
    this.gestures: const <Type, GestureRecognizerFactory>{},
    this.behavior,
    this.excludeFromSemantics: false
  }) : super(key: key) {
    assert(gestures != null);
    assert(excludeFromSemantics != null);
335 336
  }

337
  /// The widget below this widget in the tree.
338 339
  final Widget child;

340
  /// The gestures that this widget will attempt to recognize.
341 342 343 344 345 346 347 348 349 350 351 352
  final Map<Type, GestureRecognizerFactory> gestures;

  /// How this gesture detector should behave during hit testing.
  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;

353
  @override
354 355 356
  RawGestureDetectorState createState() => new RawGestureDetectorState();
}

357
/// State for a [RawGestureDetector].
358 359 360
class RawGestureDetectorState extends State<RawGestureDetector> {
  Map<Type, GestureRecognizer> _recognizers = const <Type, GestureRecognizer>{};

361
  @override
362 363 364
  void initState() {
    super.initState();
    _syncAll(config.gestures);
365 366
  }

367
  @override
368 369
  void didUpdateConfig(RawGestureDetector oldConfig) {
    _syncAll(config.gestures);
370 371
  }

372 373 374 375 376 377 378 379 380 381 382
  /// This method can be called after the build phase, during the
  /// layout of the nearest descendant RenderObjectWidget of the
  /// 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.
  void replaceGestureRecognizers(Map<Type, GestureRecognizerFactory> gestures) {
    assert(() {
383 384 385 386
      // TODO kgiesing This assert will trigger if the owner of the current
      // tree is different from the owner assigned to the renderer instance.
      // Once elements have a notion of owners this assertion can be written
      // more clearly.
387
      if (!RendererBinding.instance.pipelineOwner.debugDoingLayout) {
388
        throw new FlutterError(
389 390 391 392 393 394 395
          'Unexpected call to replaceGestureRecognizers() method of RawGestureDetectorState.\n'
          'The replaceGestureRecognizers() method can only be called during the layout phase. '
          'To set the gesture recognisers at other times, trigger a new build using setState() '
          'and provide the new gesture recognisers as constructor arguments to the corresponding '
          'RawGestureDetector or GestureDetector object.'
        );
      }
396 397 398 399
      return true;
    });
    _syncAll(gestures);
    if (!config.excludeFromSemantics) {
400 401
      RenderSemanticsGestureHandler semanticsGestureHandler = context.findRenderObject();
      context.visitChildElements((RenderObjectElement element) {
402 403
        _GestureSemantics widget = element.widget;
        widget._updateHandlers(semanticsGestureHandler, _recognizers);
404
      });
405 406 407
    }
  }

408
  @override
409 410 411 412 413
  void dispose() {
    for (GestureRecognizer recognizer in _recognizers.values)
      recognizer.dispose();
    _recognizers = null;
    super.dispose();
414 415
  }

416 417 418 419 420 421
  void _syncAll(Map<Type, GestureRecognizerFactory> gestures) {
    assert(_recognizers != null);
    Map<Type, GestureRecognizer> oldRecognizers = _recognizers;
    _recognizers = <Type, GestureRecognizer>{};
    for (Type type in gestures.keys) {
      assert(!_recognizers.containsKey(type));
422
      _recognizers[type] = gestures[type](oldRecognizers[type]);
423 424 425 426 427 428
      assert(_recognizers[type].runtimeType == type);
    }
    for (Type type in oldRecognizers.keys) {
      if (!_recognizers.containsKey(type))
        oldRecognizers[type].dispose();
    }
429 430
  }

Ian Hickson's avatar
Ian Hickson committed
431
  void _handlePointerDown(PointerDownEvent event) {
432 433 434
    assert(_recognizers != null);
    for (GestureRecognizer recognizer in _recognizers.values)
      recognizer.addPointer(event);
435 436
  }

437 438 439 440
  HitTestBehavior get _defaultBehavior {
    return config.child == null ? HitTestBehavior.translucent : HitTestBehavior.deferToChild;
  }

441
  @override
442
  Widget build(BuildContext context) {
Hixie's avatar
Hixie committed
443
    Widget result = new Listener(
444
      onPointerDown: _handlePointerDown,
445
      behavior: config.behavior ?? _defaultBehavior,
446
      child: config.child
447
    );
448 449
    if (!config.excludeFromSemantics)
      result = new _GestureSemantics(owner: this, child: result);
Hixie's avatar
Hixie committed
450
    return result;
451
  }
Hixie's avatar
Hixie committed
452

453
  @override
Hixie's avatar
Hixie committed
454
  void debugFillDescription(List<String> description) {
Ian Hickson's avatar
Ian Hickson committed
455
    super.debugFillDescription(description);
456 457 458
    if (_recognizers == null) {
      description.add('DISPOSED');
    } else {
Ian Hickson's avatar
Ian Hickson committed
459
      List<String> gestures = _recognizers.values.map/*<String>*/((GestureRecognizer recognizer) => recognizer.toStringShort()).toList();
460 461 462 463
      if (gestures.isEmpty)
        gestures.add('<none>');
      description.add('gestures: ${gestures.join(", ")}');
    }
464 465 466 467 468 469 470 471 472 473 474
    switch (config.behavior) {
      case HitTestBehavior.translucent:
        description.add('behavior: translucent');
        break;
      case HitTestBehavior.opaque:
        description.add('behavior: opaque');
        break;
      case HitTestBehavior.deferToChild:
        description.add('behavior: defer-to-child');
        break;
    }
Hixie's avatar
Hixie committed
475
  }
476
}
Hixie's avatar
Hixie committed
477

478
class _GestureSemantics extends SingleChildRenderObjectWidget {
Hixie's avatar
Hixie committed
479 480
  _GestureSemantics({
    Key key,
481 482
    Widget child,
    this.owner
Hixie's avatar
Hixie committed
483 484
  }) : super(key: key, child: child);

485
  final RawGestureDetectorState owner;
Hixie's avatar
Hixie committed
486

487 488 489 490
  void _handleTap() {
    TapGestureRecognizer recognizer = owner._recognizers[TapGestureRecognizer];
    assert(recognizer != null);
    if (recognizer.onTapDown != null)
491
      recognizer.onTapDown(new TapDownDetails());
492
    if (recognizer.onTapUp != null)
493
      recognizer.onTapUp(new TapUpDetails());
494 495
    if (recognizer.onTap != null)
      recognizer.onTap();
Hixie's avatar
Hixie committed
496 497
  }

498 499 500 501 502
  void _handleLongPress() {
    LongPressGestureRecognizer recognizer = owner._recognizers[LongPressGestureRecognizer];
    assert(recognizer != null);
    if (recognizer.onLongPress != null)
      recognizer.onLongPress();
Hixie's avatar
Hixie committed
503 504
  }

505
  void _handleHorizontalDragUpdate(DragUpdateDetails updateDetails) {
506 507 508 509
    {
      HorizontalDragGestureRecognizer recognizer = owner._recognizers[HorizontalDragGestureRecognizer];
      if (recognizer != null) {
        if (recognizer.onStart != null)
510
          recognizer.onStart(new DragStartDetails());
511
        if (recognizer.onUpdate != null)
512
          recognizer.onUpdate(updateDetails);
513
        if (recognizer.onEnd != null)
514
          recognizer.onEnd(new DragEndDetails());
515 516 517 518 519 520 521
        return;
      }
    }
    {
      PanGestureRecognizer recognizer = owner._recognizers[PanGestureRecognizer];
      if (recognizer != null) {
        if (recognizer.onStart != null)
522
          recognizer.onStart(new DragStartDetails());
523
        if (recognizer.onUpdate != null)
524
          recognizer.onUpdate(updateDetails);
525
        if (recognizer.onEnd != null)
526
          recognizer.onEnd(new DragEndDetails());
527 528
        return;
      }
Hixie's avatar
Hixie committed
529
    }
530
    assert(false);
Hixie's avatar
Hixie committed
531 532
  }

533
  void _handleVerticalDragUpdate(DragUpdateDetails updateDetails) {
534 535 536 537
    {
      VerticalDragGestureRecognizer recognizer = owner._recognizers[VerticalDragGestureRecognizer];
      if (recognizer != null) {
        if (recognizer.onStart != null)
538
          recognizer.onStart(new DragStartDetails());
539
        if (recognizer.onUpdate != null)
540
          recognizer.onUpdate(updateDetails);
541
        if (recognizer.onEnd != null)
542
          recognizer.onEnd(new DragEndDetails());
543 544
        return;
      }
Hixie's avatar
Hixie committed
545
    }
546 547 548 549
    {
      PanGestureRecognizer recognizer = owner._recognizers[PanGestureRecognizer];
      if (recognizer != null) {
        if (recognizer.onStart != null)
550
          recognizer.onStart(new DragStartDetails());
551
        if (recognizer.onUpdate != null)
552
          recognizer.onUpdate(updateDetails);
553
        if (recognizer.onEnd != null)
554
          recognizer.onEnd(new DragEndDetails());
555 556 557 558
        return;
      }
    }
    assert(false);
Hixie's avatar
Hixie committed
559 560
  }

561
  @override
562
  RenderSemanticsGestureHandler createRenderObject(BuildContext context) {
563
    RenderSemanticsGestureHandler result = new RenderSemanticsGestureHandler();
564
    updateRenderObject(context, result);
565 566
    return result;
  }
Hixie's avatar
Hixie committed
567

568
  void _updateHandlers(RenderSemanticsGestureHandler renderObject, Map<Type, GestureRecognizer> recognizers) {
569 570 571 572 573 574 575
    renderObject
      ..onTap = recognizers.containsKey(TapGestureRecognizer) ? _handleTap : null
      ..onLongPress = recognizers.containsKey(LongPressGestureRecognizer) ? _handleLongPress : null
      ..onHorizontalDragUpdate = recognizers.containsKey(VerticalDragGestureRecognizer) ||
          recognizers.containsKey(PanGestureRecognizer) ? _handleHorizontalDragUpdate : null
      ..onVerticalDragUpdate = recognizers.containsKey(VerticalDragGestureRecognizer) ||
          recognizers.containsKey(PanGestureRecognizer) ? _handleVerticalDragUpdate : null;
Hixie's avatar
Hixie committed
576
  }
577 578 579 580 581

  @override
  void updateRenderObject(BuildContext context, RenderSemanticsGestureHandler renderObject) {
    _updateHandlers(renderObject, owner._recognizers);
  }
Hixie's avatar
Hixie committed
582
}