gesture_detector.dart 9.85 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
  GestureTapDownCallback,
  GestureTapUpCallback,
14
  GestureTapCallback,
15
  GestureTapCancelCallback,
16 17 18 19 20 21 22 23 24 25 26 27 28 29
  GestureLongPressCallback,
  GestureDragStartCallback,
  GestureDragUpdateCallback,
  GestureDragEndCallback,
  GestureDragStartCallback,
  GestureDragUpdateCallback,
  GestureDragEndCallback,
  GesturePanStartCallback,
  GesturePanUpdateCallback,
  GesturePanEndCallback,
  GestureScaleStartCallback,
  GestureScaleUpdateCallback,
  GestureScaleEndCallback;

30 31
/// A widget that detects gestures.
///
32
/// Attempts to recognize gestures that correspond to its non-null callbacks.
33 34
///
/// See http://flutter.io/gestures/ for additional information.
35
class GestureDetector extends StatefulComponent {
36
  const GestureDetector({
37 38
    Key key,
    this.child,
39
    this.onTapDown,
40 41
    this.onTapUp,
    this.onTap,
42
    this.onTapCancel,
43
    this.onDoubleTap,
44
    this.onLongPress,
45 46 47 48 49 50
    this.onVerticalDragStart,
    this.onVerticalDragUpdate,
    this.onVerticalDragEnd,
    this.onHorizontalDragStart,
    this.onHorizontalDragUpdate,
    this.onHorizontalDragEnd,
51 52
    this.onPanStart,
    this.onPanUpdate,
53
    this.onPanEnd,
54 55
    this.onScaleStart,
    this.onScaleUpdate,
56 57
    this.onScaleEnd,
    this.behavior
58 59
  }) : super(key: key);

60
  final Widget child;
61

62 63
  /// A pointer that might cause a tap has contacted the screen at a particular
  /// location.
64
  final GestureTapDownCallback onTapDown;
65 66 67

  /// A pointer that will trigger a tap has stopped contacting the screen at a
  /// particular location.
68
  final GestureTapDownCallback onTapUp;
69 70

  /// A tap has occurred.
71
  final GestureTapCallback onTap;
72 73 74

  /// The pointer that previously triggered the [onTapDown] will not end up
  /// causing a tap.
75
  final GestureTapCancelCallback onTapCancel;
76 77 78

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

81 82
  /// A pointer has remained in contact with the screen at the same location for
  /// a long period of time.
83
  final GestureLongPressCallback onLongPress;
84

85
  /// A pointer has contacted the screen and might begin to move vertically.
86
  final GestureDragStartCallback onVerticalDragStart;
87 88 89

  /// A pointer that is in contact with the screen and moving vertically has
  /// moved in the vertical direction.
90
  final GestureDragUpdateCallback onVerticalDragUpdate;
91 92 93 94

  /// 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.
95 96
  final GestureDragEndCallback onVerticalDragEnd;

97
  /// A pointer has contacted the screen and might begin to move horizontally.
98
  final GestureDragStartCallback onHorizontalDragStart;
99 100 101

  /// A pointer that is in contact with the screen and moving horizontally has
  /// moved in the horizontal direction.
102
  final GestureDragUpdateCallback onHorizontalDragUpdate;
103 104 105 106

  /// 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.
107 108 109 110 111 112 113 114 115 116
  final GestureDragEndCallback onHorizontalDragEnd;

  final GesturePanStartCallback onPanStart;
  final GesturePanUpdateCallback onPanUpdate;
  final GesturePanEndCallback onPanEnd;

  final GestureScaleStartCallback onScaleStart;
  final GestureScaleUpdateCallback onScaleUpdate;
  final GestureScaleEndCallback onScaleEnd;

117
  /// How this gesture detector should behave during hit testing.
118 119
  final HitTestBehavior behavior;

120
  _GestureDetectorState createState() => new _GestureDetectorState();
121 122
}

123
class _GestureDetectorState extends State<GestureDetector> {
124
  PointerRouter get _router => Gesturer.instance.pointerRouter;
125 126

  TapGestureRecognizer _tap;
127
  DoubleTapGestureRecognizer _doubleTap;
128
  LongPressGestureRecognizer _longPress;
129 130
  VerticalDragGestureRecognizer _verticalDrag;
  HorizontalDragGestureRecognizer _horizontalDrag;
131
  PanGestureRecognizer _pan;
132 133 134 135 136
  ScaleGestureRecognizer _scale;

  void initState() {
    super.initState();
    _syncAll();
137 138
  }

139 140
  void didUpdateConfig(GestureDetector oldConfig) {
    _syncAll();
141 142
  }

143
  void dispose() {
144
    _tap = _ensureDisposed(_tap);
145
    _doubleTap = _ensureDisposed(_doubleTap);
146
    _longPress = _ensureDisposed(_longPress);
147 148
    _verticalDrag = _ensureDisposed(_verticalDrag);
    _horizontalDrag = _ensureDisposed(_horizontalDrag);
149
    _pan = _ensureDisposed(_pan);
150
    _scale = _ensureDisposed(_scale);
151
    super.dispose();
152 153
  }

154
  void _syncAll() {
155
    _syncTap();
156
    _syncDoubleTap();
157
    _syncLongPress();
158 159
    _syncVerticalDrag();
    _syncHorizontalDrag();
160
    _syncPan();
161
    _syncScale();
162 163 164
  }

  void _syncTap() {
165
    if (config.onTapDown == null && config.onTapUp == null && config.onTap == null && config.onTapCancel == null) {
166
      _tap = _ensureDisposed(_tap);
167
    } else {
168
      _tap ??= new TapGestureRecognizer(router: _router, gestureArena: Gesturer.instance.gestureArena);
169 170
      _tap
        ..onTapDown = config.onTapDown
171 172
        ..onTapUp = config.onTapUp
        ..onTap = config.onTap
173
        ..onTapCancel = config.onTapCancel;
174
    }
175 176
  }

177
  void _syncDoubleTap() {
178
    if (config.onDoubleTap == null) {
179
      _doubleTap = _ensureDisposed(_doubleTap);
180
    } else {
181
      _doubleTap ??= new DoubleTapGestureRecognizer(router: _router, gestureArena: Gesturer.instance.gestureArena);
182 183
      _doubleTap.onDoubleTap = config.onDoubleTap;
    }
184 185
  }

186
  void _syncLongPress() {
187
    if (config.onLongPress == null) {
188
      _longPress = _ensureDisposed(_longPress);
189
    } else {
190
      _longPress ??= new LongPressGestureRecognizer(router: _router, gestureArena: Gesturer.instance.gestureArena);
191
      _longPress.onLongPress = config.onLongPress;
192
    }
193 194
  }

195
  void _syncVerticalDrag() {
196
    if (config.onVerticalDragStart == null && config.onVerticalDragUpdate == null && config.onVerticalDragEnd == null) {
197
      _verticalDrag = _ensureDisposed(_verticalDrag);
198
    } else {
199
      _verticalDrag ??= new VerticalDragGestureRecognizer(router: _router, gestureArena: Gesturer.instance.gestureArena);
200
      _verticalDrag
201 202 203
        ..onStart = config.onVerticalDragStart
        ..onUpdate = config.onVerticalDragUpdate
        ..onEnd = config.onVerticalDragEnd;
204 205 206
    }
  }

207
  void _syncHorizontalDrag() {
208
    if (config.onHorizontalDragStart == null && config.onHorizontalDragUpdate == null && config.onHorizontalDragEnd == null) {
209
      _horizontalDrag = _ensureDisposed(_horizontalDrag);
210
    } else {
211
      _horizontalDrag ??= new HorizontalDragGestureRecognizer(router: _router, gestureArena: Gesturer.instance.gestureArena);
212
      _horizontalDrag
213 214 215
        ..onStart = config.onHorizontalDragStart
        ..onUpdate = config.onHorizontalDragUpdate
        ..onEnd = config.onHorizontalDragEnd;
216 217 218 219
    }
  }

  void _syncPan() {
220
    if (config.onPanStart == null && config.onPanUpdate == null && config.onPanEnd == null) {
221
      _pan = _ensureDisposed(_pan);
222
    } else {
223
      assert(_scale == null);  // Scale is a superset of pan; just use scale
224
      _pan ??= new PanGestureRecognizer(router: _router, gestureArena: Gesturer.instance.gestureArena);
225
      _pan
226 227 228
        ..onStart = config.onPanStart
        ..onUpdate = config.onPanUpdate
        ..onEnd = config.onPanEnd;
229 230 231
    }
  }

232
  void _syncScale() {
233
    if (config.onScaleStart == null && config.onScaleUpdate == null && config.onScaleEnd == null) {
234
      _scale = _ensureDisposed(_scale);
235
    } else {
236
      assert(_pan == null);  // Scale is a superset of pan; just use scale
237
      _scale ??= new ScaleGestureRecognizer(router: _router, gestureArena: Gesturer.instance.gestureArena);
238
      _scale
239 240 241
        ..onStart = config.onScaleStart
        ..onUpdate = config.onScaleUpdate
        ..onEnd = config.onScaleEnd;
242 243 244
    }
  }

245
  GestureRecognizer _ensureDisposed(GestureRecognizer recognizer) {
246
    recognizer?.dispose();
247 248 249
    return null;
  }

Ian Hickson's avatar
Ian Hickson committed
250
  void _handlePointerDown(PointerDownEvent event) {
251 252
    if (_tap != null)
      _tap.addPointer(event);
253 254
    if (_doubleTap != null)
      _doubleTap.addPointer(event);
255 256
    if (_longPress != null)
      _longPress.addPointer(event);
257 258 259 260
    if (_verticalDrag != null)
      _verticalDrag.addPointer(event);
    if (_horizontalDrag != null)
      _horizontalDrag.addPointer(event);
261 262
    if (_pan != null)
      _pan.addPointer(event);
263 264
    if (_scale != null)
      _scale.addPointer(event);
265 266
  }

267 268 269 270
  HitTestBehavior get _defaultBehavior {
    return config.child == null ? HitTestBehavior.translucent : HitTestBehavior.deferToChild;
  }

271
  Widget build(BuildContext context) {
272 273
    return new Listener(
      onPointerDown: _handlePointerDown,
274
      behavior: config.behavior ?? _defaultBehavior,
275
      child: config.child
276 277
    );
  }
Hixie's avatar
Hixie committed
278 279

  void debugFillDescription(List<String> description) {
Ian Hickson's avatar
Ian Hickson committed
280
    super.debugFillDescription(description);
Hixie's avatar
Hixie committed
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
    List<String> gestures = <String>[];
    if (_tap != null)
      gestures.add('tap');
    if (_doubleTap != null)
      gestures.add('double tap');
    if (_longPress != null)
      gestures.add('long press');
    if (_verticalDrag != null)
      gestures.add('vertical drag');
    if (_horizontalDrag != null)
      gestures.add('horizontal drag');
    if (_pan != null)
      gestures.add('pan');
    if (_scale != null)
      gestures.add('scale');
Ian Hickson's avatar
Ian Hickson committed
296
    if (gestures.isEmpty)
Hixie's avatar
Hixie committed
297 298
      gestures.add('<none>');
    description.add('gestures: ${gestures.join(", ")}');
299 300 301 302 303 304 305 306 307 308 309
    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
310
  }
311
}