1 | // Copyright 2014 The Flutter 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 | |
5 | /// @docImport 'package:flutter/material.dart'; |
6 | /// |
7 | /// @docImport 'container.dart'; |
8 | /// @docImport 'scrollable.dart'; |
9 | library; |
10 | |
11 | import 'package:flutter/foundation.dart'; |
12 | import 'package:flutter/gestures.dart'; |
13 | import 'package:flutter/rendering.dart'; |
14 | |
15 | import 'basic.dart'; |
16 | import 'framework.dart'; |
17 | import 'media_query.dart'; |
18 | import 'scroll_configuration.dart'; |
19 | |
20 | export 'package:flutter/gestures.dart' show |
21 | DragDownDetails, |
22 | DragEndDetails, |
23 | DragStartDetails, |
24 | DragUpdateDetails, |
25 | ForcePressDetails, |
26 | GestureDragCancelCallback, |
27 | GestureDragDownCallback, |
28 | GestureDragEndCallback, |
29 | GestureDragStartCallback, |
30 | GestureDragUpdateCallback, |
31 | GestureForcePressEndCallback, |
32 | GestureForcePressPeakCallback, |
33 | GestureForcePressStartCallback, |
34 | GestureForcePressUpdateCallback, |
35 | GestureLongPressCallback, |
36 | GestureLongPressEndCallback, |
37 | GestureLongPressMoveUpdateCallback, |
38 | GestureLongPressStartCallback, |
39 | GestureLongPressUpCallback, |
40 | GestureScaleEndCallback, |
41 | GestureScaleStartCallback, |
42 | GestureScaleUpdateCallback, |
43 | GestureTapCallback, |
44 | GestureTapCancelCallback, |
45 | GestureTapDownCallback, |
46 | GestureTapUpCallback, |
47 | LongPressEndDetails, |
48 | LongPressMoveUpdateDetails, |
49 | LongPressStartDetails, |
50 | ScaleEndDetails, |
51 | ScaleStartDetails, |
52 | ScaleUpdateDetails, |
53 | TapDownDetails, |
54 | TapUpDetails, |
55 | Velocity; |
56 | export 'package:flutter/rendering.dart' show RenderSemanticsGestureHandler; |
57 | |
58 | // Examples can assume: |
59 | // late bool _lights; |
60 | // void setState(VoidCallback fn) { } |
61 | // late String _last; |
62 | // late Color _color; |
63 | |
64 | /// Factory for creating gesture recognizers. |
65 | /// |
66 | /// `T` is the type of gesture recognizer this class manages. |
67 | /// |
68 | /// Used by [RawGestureDetector.gestures]. |
69 | @optionalTypeArgs |
70 | abstract class GestureRecognizerFactory<T extends GestureRecognizer> { |
71 | /// Abstract const constructor. This constructor enables subclasses to provide |
72 | /// const constructors so that they can be used in const expressions. |
73 | const GestureRecognizerFactory(); |
74 | |
75 | /// Must return an instance of T. |
76 | T constructor(); |
77 | |
78 | /// Must configure the given instance (which will have been created by |
79 | /// `constructor`). |
80 | /// |
81 | /// This normally means setting the callbacks. |
82 | void initializer(T instance); |
83 | |
84 | bool _debugAssertTypeMatches(Type type) { |
85 | assert(type == T, 'GestureRecognizerFactory of type $T was used where type $type was specified.' ); |
86 | return true; |
87 | } |
88 | } |
89 | |
90 | /// Signature for closures that implement [GestureRecognizerFactory.constructor]. |
91 | typedef GestureRecognizerFactoryConstructor<T extends GestureRecognizer> = T Function(); |
92 | |
93 | /// Signature for closures that implement [GestureRecognizerFactory.initializer]. |
94 | typedef GestureRecognizerFactoryInitializer<T extends GestureRecognizer> = void Function(T instance); |
95 | |
96 | /// Factory for creating gesture recognizers that delegates to callbacks. |
97 | /// |
98 | /// Used by [RawGestureDetector.gestures]. |
99 | class GestureRecognizerFactoryWithHandlers<T extends GestureRecognizer> extends GestureRecognizerFactory<T> { |
100 | /// Creates a gesture recognizer factory with the given callbacks. |
101 | const GestureRecognizerFactoryWithHandlers(this._constructor, this._initializer); |
102 | |
103 | final GestureRecognizerFactoryConstructor<T> _constructor; |
104 | |
105 | final GestureRecognizerFactoryInitializer<T> _initializer; |
106 | |
107 | @override |
108 | T constructor() => _constructor(); |
109 | |
110 | @override |
111 | void initializer(T instance) => _initializer(instance); |
112 | } |
113 | |
114 | /// A widget that detects gestures. |
115 | /// |
116 | /// Attempts to recognize gestures that correspond to its non-null callbacks. |
117 | /// |
118 | /// If this widget has a child, it defers to that child for its sizing behavior. |
119 | /// If it does not have a child, it grows to fit the parent instead. |
120 | /// |
121 | /// {@youtube 560 315 https://www.youtube.com/watch?v=WhVXkCFPmK4} |
122 | /// |
123 | /// By default a GestureDetector with an invisible child ignores touches; |
124 | /// this behavior can be controlled with [behavior]. |
125 | /// |
126 | /// GestureDetector also listens for accessibility events and maps |
127 | /// them to the callbacks. To ignore accessibility events, set |
128 | /// [excludeFromSemantics] to true. |
129 | /// |
130 | /// See <http://flutter.dev/to/gestures> for additional information. |
131 | /// |
132 | /// Material design applications typically react to touches with ink splash |
133 | /// effects. The [InkWell] class implements this effect and can be used in place |
134 | /// of a [GestureDetector] for handling taps. |
135 | /// |
136 | /// {@tool dartpad} |
137 | /// This example contains a black light bulb wrapped in a [GestureDetector]. It |
138 | /// turns the light bulb yellow when the "TURN LIGHT ON" button is tapped by |
139 | /// setting the `_lights` field, and off again when "TURN LIGHT OFF" is tapped. |
140 | /// |
141 | /// ** See code in examples/api/lib/widgets/gesture_detector/gesture_detector.0.dart ** |
142 | /// {@end-tool} |
143 | /// |
144 | /// {@tool dartpad} |
145 | /// This example uses a [Container] that wraps a [GestureDetector] widget which |
146 | /// detects a tap. |
147 | /// |
148 | /// Since the [GestureDetector] does not have a child, it takes on the size of its |
149 | /// parent, making the entire area of the surrounding [Container] clickable. When |
150 | /// tapped, the [Container] turns yellow by setting the `_color` field. When |
151 | /// tapped again, it goes back to white. |
152 | /// |
153 | /// ** See code in examples/api/lib/widgets/gesture_detector/gesture_detector.1.dart ** |
154 | /// {@end-tool} |
155 | /// |
156 | /// ### Troubleshooting |
157 | /// |
158 | /// Why isn't my parent [GestureDetector.onTap] method called? |
159 | /// |
160 | /// Given a parent [GestureDetector] with an onTap callback, and a child |
161 | /// GestureDetector that also defines an onTap callback, when the inner |
162 | /// GestureDetector is tapped, both GestureDetectors send a [GestureRecognizer] |
163 | /// into the gesture arena. This is because the pointer coordinates are within the |
164 | /// bounds of both GestureDetectors. The child GestureDetector wins in this |
165 | /// scenario because it was the first to enter the arena, resolving as first come, |
166 | /// first served. The child onTap is called, and the parent's is not as the gesture has |
167 | /// been consumed. |
168 | /// For more information on gesture disambiguation see: |
169 | /// [Gesture disambiguation](https://flutter.dev/to/gesture-disambiguation). |
170 | /// |
171 | /// Setting [GestureDetector.behavior] to [HitTestBehavior.opaque] |
172 | /// or [HitTestBehavior.translucent] has no impact on parent-child relationships: |
173 | /// both GestureDetectors send a GestureRecognizer into the gesture arena, only one wins. |
174 | /// |
175 | /// Some callbacks (e.g. onTapDown) can fire before a recognizer wins the arena, |
176 | /// and others (e.g. onTapCancel) fire even when it loses the arena. Therefore, |
177 | /// the parent detector in the example above may call some of its callbacks even |
178 | /// though it loses in the arena. |
179 | /// |
180 | /// {@tool dartpad} |
181 | /// This example uses a [GestureDetector] that wraps a green [Container] and a second |
182 | /// GestureDetector that wraps a yellow Container. The second GestureDetector is |
183 | /// a child of the green Container. |
184 | /// Both GestureDetectors define an onTap callback. When the callback is called it |
185 | /// adds a red border to the corresponding Container. |
186 | /// |
187 | /// When the green Container is tapped, it's parent GestureDetector enters |
188 | /// the gesture arena. It wins because there is no competing GestureDetector and |
189 | /// the green Container shows a red border. |
190 | /// When the yellow Container is tapped, it's parent GestureDetector enters |
191 | /// the gesture arena. The GestureDetector that wraps the green Container also |
192 | /// enters the gesture arena (the pointer events coordinates are inside both |
193 | /// GestureDetectors bounds). The GestureDetector that wraps the yellow Container |
194 | /// wins because it was the first detector to enter the arena. |
195 | /// |
196 | /// This example sets [debugPrintGestureArenaDiagnostics] to true. |
197 | /// This flag prints useful information about gesture arenas. |
198 | /// |
199 | /// Changing the [GestureDetector.behavior] property to [HitTestBehavior.translucent] |
200 | /// or [HitTestBehavior.opaque] has no impact: both GestureDetectors send a [GestureRecognizer] |
201 | /// into the gesture arena, only one wins. |
202 | /// |
203 | /// ** See code in examples/api/lib/widgets/gesture_detector/gesture_detector.2.dart ** |
204 | /// {@end-tool} |
205 | /// |
206 | /// ## Debugging |
207 | /// |
208 | /// To see how large the hit test box of a [GestureDetector] is for debugging |
209 | /// purposes, set [debugPaintPointersEnabled] to true. |
210 | /// |
211 | /// See also: |
212 | /// |
213 | /// * [Listener], a widget for listening to lower-level raw pointer events. |
214 | /// * [MouseRegion], a widget that tracks the movement of mice, even when no |
215 | /// button is pressed. |
216 | /// * [RawGestureDetector], a widget that is used to detect custom gestures. |
217 | class GestureDetector extends StatelessWidget { |
218 | /// Creates a widget that detects gestures. |
219 | /// |
220 | /// Pan and scale callbacks cannot be used simultaneously because scale is a |
221 | /// superset of pan. Use the scale callbacks instead. |
222 | /// |
223 | /// Horizontal and vertical drag callbacks cannot be used simultaneously |
224 | /// because a combination of a horizontal and vertical drag is a pan. |
225 | /// Use the pan callbacks instead. |
226 | /// |
227 | /// {@youtube 560 315 https://www.youtube.com/watch?v=WhVXkCFPmK4} |
228 | /// |
229 | /// By default, gesture detectors contribute semantic information to the tree |
230 | /// that is used by assistive technology. |
231 | GestureDetector({ |
232 | super.key, |
233 | this.child, |
234 | this.onTapDown, |
235 | this.onTapUp, |
236 | this.onTap, |
237 | this.onTapCancel, |
238 | this.onSecondaryTap, |
239 | this.onSecondaryTapDown, |
240 | this.onSecondaryTapUp, |
241 | this.onSecondaryTapCancel, |
242 | this.onTertiaryTapDown, |
243 | this.onTertiaryTapUp, |
244 | this.onTertiaryTapCancel, |
245 | this.onDoubleTapDown, |
246 | this.onDoubleTap, |
247 | this.onDoubleTapCancel, |
248 | this.onLongPressDown, |
249 | this.onLongPressCancel, |
250 | this.onLongPress, |
251 | this.onLongPressStart, |
252 | this.onLongPressMoveUpdate, |
253 | this.onLongPressUp, |
254 | this.onLongPressEnd, |
255 | this.onSecondaryLongPressDown, |
256 | this.onSecondaryLongPressCancel, |
257 | this.onSecondaryLongPress, |
258 | this.onSecondaryLongPressStart, |
259 | this.onSecondaryLongPressMoveUpdate, |
260 | this.onSecondaryLongPressUp, |
261 | this.onSecondaryLongPressEnd, |
262 | this.onTertiaryLongPressDown, |
263 | this.onTertiaryLongPressCancel, |
264 | this.onTertiaryLongPress, |
265 | this.onTertiaryLongPressStart, |
266 | this.onTertiaryLongPressMoveUpdate, |
267 | this.onTertiaryLongPressUp, |
268 | this.onTertiaryLongPressEnd, |
269 | this.onVerticalDragDown, |
270 | this.onVerticalDragStart, |
271 | this.onVerticalDragUpdate, |
272 | this.onVerticalDragEnd, |
273 | this.onVerticalDragCancel, |
274 | this.onHorizontalDragDown, |
275 | this.onHorizontalDragStart, |
276 | this.onHorizontalDragUpdate, |
277 | this.onHorizontalDragEnd, |
278 | this.onHorizontalDragCancel, |
279 | this.onForcePressStart, |
280 | this.onForcePressPeak, |
281 | this.onForcePressUpdate, |
282 | this.onForcePressEnd, |
283 | this.onPanDown, |
284 | this.onPanStart, |
285 | this.onPanUpdate, |
286 | this.onPanEnd, |
287 | this.onPanCancel, |
288 | this.onScaleStart, |
289 | this.onScaleUpdate, |
290 | this.onScaleEnd, |
291 | this.behavior, |
292 | this.excludeFromSemantics = false, |
293 | this.dragStartBehavior = DragStartBehavior.start, |
294 | this.trackpadScrollCausesScale = false, |
295 | this.trackpadScrollToScaleFactor = kDefaultTrackpadScrollToScaleFactor, |
296 | this.supportedDevices, |
297 | }) : assert(() { |
298 | final bool haveVerticalDrag = onVerticalDragStart != null || onVerticalDragUpdate != null || onVerticalDragEnd != null; |
299 | final bool haveHorizontalDrag = onHorizontalDragStart != null || onHorizontalDragUpdate != null || onHorizontalDragEnd != null; |
300 | final bool havePan = onPanStart != null || onPanUpdate != null || onPanEnd != null; |
301 | final bool haveScale = onScaleStart != null || onScaleUpdate != null || onScaleEnd != null; |
302 | if (havePan || haveScale) { |
303 | if (havePan && haveScale) { |
304 | throw FlutterError.fromParts(<DiagnosticsNode>[ |
305 | ErrorSummary('Incorrect GestureDetector arguments.' ), |
306 | ErrorDescription( |
307 | 'Having both a pan gesture recognizer and a scale gesture recognizer is redundant; scale is a superset of pan.' , |
308 | ), |
309 | ErrorHint('Just use the scale gesture recognizer.' ), |
310 | ]); |
311 | } |
312 | final String recognizer = havePan ? 'pan' : 'scale' ; |
313 | if (haveVerticalDrag && haveHorizontalDrag) { |
314 | throw FlutterError( |
315 | 'Incorrect GestureDetector arguments.\n' |
316 | 'Simultaneously having a vertical drag gesture recognizer, a horizontal drag gesture recognizer, and a $recognizer gesture recognizer ' |
317 | 'will result in the $recognizer gesture recognizer being ignored, since the other two will catch all drags.' , |
318 | ); |
319 | } |
320 | } |
321 | return true; |
322 | }()); |
323 | |
324 | /// The widget below this widget in the tree. |
325 | /// |
326 | /// {@macro flutter.widgets.ProxyWidget.child} |
327 | final Widget? child; |
328 | |
329 | /// A pointer that might cause a tap with a primary button has contacted the |
330 | /// screen at a particular location. |
331 | /// |
332 | /// This is called after a short timeout, even if the winning gesture has not |
333 | /// yet been selected. If the tap gesture wins, [onTapUp] will be called, |
334 | /// otherwise [onTapCancel] will be called. |
335 | /// |
336 | /// See also: |
337 | /// |
338 | /// * [kPrimaryButton], the button this callback responds to. |
339 | final GestureTapDownCallback? onTapDown; |
340 | |
341 | /// A pointer that will trigger a tap with a primary button has stopped |
342 | /// contacting the screen at a particular location. |
343 | /// |
344 | /// This triggers immediately before [onTap] in the case of the tap gesture |
345 | /// winning. If the tap gesture did not win, [onTapCancel] is called instead. |
346 | /// |
347 | /// See also: |
348 | /// |
349 | /// * [kPrimaryButton], the button this callback responds to. |
350 | final GestureTapUpCallback? onTapUp; |
351 | |
352 | /// A tap with a primary button has occurred. |
353 | /// |
354 | /// This triggers when the tap gesture wins. If the tap gesture did not win, |
355 | /// [onTapCancel] is called instead. |
356 | /// |
357 | /// See also: |
358 | /// |
359 | /// * [kPrimaryButton], the button this callback responds to. |
360 | /// * [onTapUp], which is called at the same time but includes details |
361 | /// regarding the pointer position. |
362 | final GestureTapCallback? onTap; |
363 | |
364 | /// The pointer that previously triggered [onTapDown] will not end up causing |
365 | /// a tap. |
366 | /// |
367 | /// This is called after [onTapDown], and instead of [onTapUp] and [onTap], if |
368 | /// the tap gesture did not win. |
369 | /// |
370 | /// See also: |
371 | /// |
372 | /// * [kPrimaryButton], the button this callback responds to. |
373 | final GestureTapCancelCallback? onTapCancel; |
374 | |
375 | /// A tap with a secondary button has occurred. |
376 | /// |
377 | /// This triggers when the tap gesture wins. If the tap gesture did not win, |
378 | /// [onSecondaryTapCancel] is called instead. |
379 | /// |
380 | /// See also: |
381 | /// |
382 | /// * [kSecondaryButton], the button this callback responds to. |
383 | /// * [onSecondaryTapUp], which is called at the same time but includes details |
384 | /// regarding the pointer position. |
385 | final GestureTapCallback? onSecondaryTap; |
386 | |
387 | /// A pointer that might cause a tap with a secondary button has contacted the |
388 | /// screen at a particular location. |
389 | /// |
390 | /// This is called after a short timeout, even if the winning gesture has not |
391 | /// yet been selected. If the tap gesture wins, [onSecondaryTapUp] will be |
392 | /// called, otherwise [onSecondaryTapCancel] will be called. |
393 | /// |
394 | /// See also: |
395 | /// |
396 | /// * [kSecondaryButton], the button this callback responds to. |
397 | final GestureTapDownCallback? onSecondaryTapDown; |
398 | |
399 | /// A pointer that will trigger a tap with a secondary button has stopped |
400 | /// contacting the screen at a particular location. |
401 | /// |
402 | /// This triggers in the case of the tap gesture winning. If the tap gesture |
403 | /// did not win, [onSecondaryTapCancel] is called instead. |
404 | /// |
405 | /// See also: |
406 | /// |
407 | /// * [onSecondaryTap], a handler triggered right after this one that doesn't |
408 | /// pass any details about the tap. |
409 | /// * [kSecondaryButton], the button this callback responds to. |
410 | final GestureTapUpCallback? onSecondaryTapUp; |
411 | |
412 | /// The pointer that previously triggered [onSecondaryTapDown] will not end up |
413 | /// causing a tap. |
414 | /// |
415 | /// This is called after [onSecondaryTapDown], and instead of |
416 | /// [onSecondaryTapUp], if the tap gesture did not win. |
417 | /// |
418 | /// See also: |
419 | /// |
420 | /// * [kSecondaryButton], the button this callback responds to. |
421 | final GestureTapCancelCallback? onSecondaryTapCancel; |
422 | |
423 | /// A pointer that might cause a tap with a tertiary button has contacted the |
424 | /// screen at a particular location. |
425 | /// |
426 | /// This is called after a short timeout, even if the winning gesture has not |
427 | /// yet been selected. If the tap gesture wins, [onTertiaryTapUp] will be |
428 | /// called, otherwise [onTertiaryTapCancel] will be called. |
429 | /// |
430 | /// See also: |
431 | /// |
432 | /// * [kTertiaryButton], the button this callback responds to. |
433 | final GestureTapDownCallback? onTertiaryTapDown; |
434 | |
435 | /// A pointer that will trigger a tap with a tertiary button has stopped |
436 | /// contacting the screen at a particular location. |
437 | /// |
438 | /// This triggers in the case of the tap gesture winning. If the tap gesture |
439 | /// did not win, [onTertiaryTapCancel] is called instead. |
440 | /// |
441 | /// See also: |
442 | /// |
443 | /// * [kTertiaryButton], the button this callback responds to. |
444 | final GestureTapUpCallback? onTertiaryTapUp; |
445 | |
446 | /// The pointer that previously triggered [onTertiaryTapDown] will not end up |
447 | /// causing a tap. |
448 | /// |
449 | /// This is called after [onTertiaryTapDown], and instead of |
450 | /// [onTertiaryTapUp], if the tap gesture did not win. |
451 | /// |
452 | /// See also: |
453 | /// |
454 | /// * [kTertiaryButton], the button this callback responds to. |
455 | final GestureTapCancelCallback? onTertiaryTapCancel; |
456 | |
457 | /// A pointer that might cause a double tap has contacted the screen at a |
458 | /// particular location. |
459 | /// |
460 | /// Triggered immediately after the down event of the second tap. |
461 | /// |
462 | /// If the user completes the double tap and the gesture wins, [onDoubleTap] |
463 | /// will be called after this callback. Otherwise, [onDoubleTapCancel] will |
464 | /// be called after this callback. |
465 | /// |
466 | /// See also: |
467 | /// |
468 | /// * [kPrimaryButton], the button this callback responds to. |
469 | final GestureTapDownCallback? onDoubleTapDown; |
470 | |
471 | /// The user has tapped the screen with a primary button at the same location |
472 | /// twice in quick succession. |
473 | /// |
474 | /// See also: |
475 | /// |
476 | /// * [kPrimaryButton], the button this callback responds to. |
477 | final GestureTapCallback? onDoubleTap; |
478 | |
479 | /// The pointer that previously triggered [onDoubleTapDown] will not end up |
480 | /// causing a double tap. |
481 | /// |
482 | /// See also: |
483 | /// |
484 | /// * [kPrimaryButton], the button this callback responds to. |
485 | final GestureTapCancelCallback? onDoubleTapCancel; |
486 | |
487 | /// The pointer has contacted the screen with a primary button, which might |
488 | /// be the start of a long-press. |
489 | /// |
490 | /// This triggers after the pointer down event. |
491 | /// |
492 | /// If the user completes the long-press, and this gesture wins, |
493 | /// [onLongPressStart] will be called after this callback. Otherwise, |
494 | /// [onLongPressCancel] will be called after this callback. |
495 | /// |
496 | /// See also: |
497 | /// |
498 | /// * [kPrimaryButton], the button this callback responds to. |
499 | /// * [onSecondaryLongPressDown], a similar callback but for a secondary button. |
500 | /// * [onTertiaryLongPressDown], a similar callback but for a tertiary button. |
501 | /// * [LongPressGestureRecognizer.onLongPressDown], which exposes this |
502 | /// callback at the gesture layer. |
503 | final GestureLongPressDownCallback? onLongPressDown; |
504 | |
505 | /// A pointer that previously triggered [onLongPressDown] will not end up |
506 | /// causing a long-press. |
507 | /// |
508 | /// This triggers once the gesture loses if [onLongPressDown] has previously |
509 | /// been triggered. |
510 | /// |
511 | /// If the user completed the long-press, and the gesture won, then |
512 | /// [onLongPressStart] and [onLongPress] are called instead. |
513 | /// |
514 | /// See also: |
515 | /// |
516 | /// * [kPrimaryButton], the button this callback responds to. |
517 | /// * [LongPressGestureRecognizer.onLongPressCancel], which exposes this |
518 | /// callback at the gesture layer. |
519 | final GestureLongPressCancelCallback? onLongPressCancel; |
520 | |
521 | /// Called when a long press gesture with a primary button has been recognized. |
522 | /// |
523 | /// Triggered when a pointer has remained in contact with the screen at the |
524 | /// same location for a long period of time. |
525 | /// |
526 | /// This is equivalent to (and is called immediately after) [onLongPressStart]. |
527 | /// The only difference between the two is that this callback does not |
528 | /// contain details of the position at which the pointer initially contacted |
529 | /// the screen. |
530 | /// |
531 | /// See also: |
532 | /// |
533 | /// * [kPrimaryButton], the button this callback responds to. |
534 | /// * [LongPressGestureRecognizer.onLongPress], which exposes this |
535 | /// callback at the gesture layer. |
536 | final GestureLongPressCallback? onLongPress; |
537 | |
538 | /// Called when a long press gesture with a primary button has been recognized. |
539 | /// |
540 | /// Triggered when a pointer has remained in contact with the screen at the |
541 | /// same location for a long period of time. |
542 | /// |
543 | /// This is equivalent to (and is called immediately before) [onLongPress]. |
544 | /// The only difference between the two is that this callback contains |
545 | /// details of the position at which the pointer initially contacted the |
546 | /// screen, whereas [onLongPress] does not. |
547 | /// |
548 | /// See also: |
549 | /// |
550 | /// * [kPrimaryButton], the button this callback responds to. |
551 | /// * [LongPressGestureRecognizer.onLongPressStart], which exposes this |
552 | /// callback at the gesture layer. |
553 | final GestureLongPressStartCallback? onLongPressStart; |
554 | |
555 | /// A pointer has been drag-moved after a long-press with a primary button. |
556 | /// |
557 | /// See also: |
558 | /// |
559 | /// * [kPrimaryButton], the button this callback responds to. |
560 | /// * [LongPressGestureRecognizer.onLongPressMoveUpdate], which exposes this |
561 | /// callback at the gesture layer. |
562 | final GestureLongPressMoveUpdateCallback? onLongPressMoveUpdate; |
563 | |
564 | /// A pointer that has triggered a long-press with a primary button has |
565 | /// stopped contacting the screen. |
566 | /// |
567 | /// This is equivalent to (and is called immediately after) [onLongPressEnd]. |
568 | /// The only difference between the two is that this callback does not |
569 | /// contain details of the state of the pointer when it stopped contacting |
570 | /// the screen. |
571 | /// |
572 | /// See also: |
573 | /// |
574 | /// * [kPrimaryButton], the button this callback responds to. |
575 | /// * [LongPressGestureRecognizer.onLongPressUp], which exposes this |
576 | /// callback at the gesture layer. |
577 | final GestureLongPressUpCallback? onLongPressUp; |
578 | |
579 | /// A pointer that has triggered a long-press with a primary button has |
580 | /// stopped contacting the screen. |
581 | /// |
582 | /// This is equivalent to (and is called immediately before) [onLongPressUp]. |
583 | /// The only difference between the two is that this callback contains |
584 | /// details of the state of the pointer when it stopped contacting the |
585 | /// screen, whereas [onLongPressUp] does not. |
586 | /// |
587 | /// See also: |
588 | /// |
589 | /// * [kPrimaryButton], the button this callback responds to. |
590 | /// * [LongPressGestureRecognizer.onLongPressEnd], which exposes this |
591 | /// callback at the gesture layer. |
592 | final GestureLongPressEndCallback? onLongPressEnd; |
593 | |
594 | /// The pointer has contacted the screen with a secondary button, which might |
595 | /// be the start of a long-press. |
596 | /// |
597 | /// This triggers after the pointer down event. |
598 | /// |
599 | /// If the user completes the long-press, and this gesture wins, |
600 | /// [onSecondaryLongPressStart] will be called after this callback. Otherwise, |
601 | /// [onSecondaryLongPressCancel] will be called after this callback. |
602 | /// |
603 | /// See also: |
604 | /// |
605 | /// * [kSecondaryButton], the button this callback responds to. |
606 | /// * [onLongPressDown], a similar callback but for a secondary button. |
607 | /// * [onTertiaryLongPressDown], a similar callback but for a tertiary button. |
608 | /// * [LongPressGestureRecognizer.onSecondaryLongPressDown], which exposes |
609 | /// this callback at the gesture layer. |
610 | final GestureLongPressDownCallback? onSecondaryLongPressDown; |
611 | |
612 | /// A pointer that previously triggered [onSecondaryLongPressDown] will not |
613 | /// end up causing a long-press. |
614 | /// |
615 | /// This triggers once the gesture loses if [onSecondaryLongPressDown] has |
616 | /// previously been triggered. |
617 | /// |
618 | /// If the user completed the long-press, and the gesture won, then |
619 | /// [onSecondaryLongPressStart] and [onSecondaryLongPress] are called instead. |
620 | /// |
621 | /// See also: |
622 | /// |
623 | /// * [kSecondaryButton], the button this callback responds to. |
624 | /// * [LongPressGestureRecognizer.onSecondaryLongPressCancel], which exposes |
625 | /// this callback at the gesture layer. |
626 | final GestureLongPressCancelCallback? onSecondaryLongPressCancel; |
627 | |
628 | /// Called when a long press gesture with a secondary button has been |
629 | /// recognized. |
630 | /// |
631 | /// Triggered when a pointer has remained in contact with the screen at the |
632 | /// same location for a long period of time. |
633 | /// |
634 | /// This is equivalent to (and is called immediately after) |
635 | /// [onSecondaryLongPressStart]. The only difference between the two is that |
636 | /// this callback does not contain details of the position at which the |
637 | /// pointer initially contacted the screen. |
638 | /// |
639 | /// See also: |
640 | /// |
641 | /// * [kSecondaryButton], the button this callback responds to. |
642 | /// * [LongPressGestureRecognizer.onSecondaryLongPress], which exposes |
643 | /// this callback at the gesture layer. |
644 | final GestureLongPressCallback? onSecondaryLongPress; |
645 | |
646 | /// Called when a long press gesture with a secondary button has been |
647 | /// recognized. |
648 | /// |
649 | /// Triggered when a pointer has remained in contact with the screen at the |
650 | /// same location for a long period of time. |
651 | /// |
652 | /// This is equivalent to (and is called immediately before) |
653 | /// [onSecondaryLongPress]. The only difference between the two is that this |
654 | /// callback contains details of the position at which the pointer initially |
655 | /// contacted the screen, whereas [onSecondaryLongPress] does not. |
656 | /// |
657 | /// See also: |
658 | /// |
659 | /// * [kSecondaryButton], the button this callback responds to. |
660 | /// * [LongPressGestureRecognizer.onSecondaryLongPressStart], which exposes |
661 | /// this callback at the gesture layer. |
662 | final GestureLongPressStartCallback? onSecondaryLongPressStart; |
663 | |
664 | /// A pointer has been drag-moved after a long press with a secondary button. |
665 | /// |
666 | /// See also: |
667 | /// |
668 | /// * [kSecondaryButton], the button this callback responds to. |
669 | /// * [LongPressGestureRecognizer.onSecondaryLongPressMoveUpdate], which exposes |
670 | /// this callback at the gesture layer. |
671 | final GestureLongPressMoveUpdateCallback? onSecondaryLongPressMoveUpdate; |
672 | |
673 | /// A pointer that has triggered a long-press with a secondary button has |
674 | /// stopped contacting the screen. |
675 | /// |
676 | /// This is equivalent to (and is called immediately after) |
677 | /// [onSecondaryLongPressEnd]. The only difference between the two is that |
678 | /// this callback does not contain details of the state of the pointer when |
679 | /// it stopped contacting the screen. |
680 | /// |
681 | /// See also: |
682 | /// |
683 | /// * [kSecondaryButton], the button this callback responds to. |
684 | /// * [LongPressGestureRecognizer.onSecondaryLongPressUp], which exposes |
685 | /// this callback at the gesture layer. |
686 | final GestureLongPressUpCallback? onSecondaryLongPressUp; |
687 | |
688 | /// A pointer that has triggered a long-press with a secondary button has |
689 | /// stopped contacting the screen. |
690 | /// |
691 | /// This is equivalent to (and is called immediately before) |
692 | /// [onSecondaryLongPressUp]. The only difference between the two is that |
693 | /// this callback contains details of the state of the pointer when it |
694 | /// stopped contacting the screen, whereas [onSecondaryLongPressUp] does not. |
695 | /// |
696 | /// See also: |
697 | /// |
698 | /// * [kSecondaryButton], the button this callback responds to. |
699 | /// * [LongPressGestureRecognizer.onSecondaryLongPressEnd], which exposes |
700 | /// this callback at the gesture layer. |
701 | final GestureLongPressEndCallback? onSecondaryLongPressEnd; |
702 | |
703 | /// The pointer has contacted the screen with a tertiary button, which might |
704 | /// be the start of a long-press. |
705 | /// |
706 | /// This triggers after the pointer down event. |
707 | /// |
708 | /// If the user completes the long-press, and this gesture wins, |
709 | /// [onTertiaryLongPressStart] will be called after this callback. Otherwise, |
710 | /// [onTertiaryLongPressCancel] will be called after this callback. |
711 | /// |
712 | /// See also: |
713 | /// |
714 | /// * [kTertiaryButton], the button this callback responds to. |
715 | /// * [onLongPressDown], a similar callback but for a primary button. |
716 | /// * [onSecondaryLongPressDown], a similar callback but for a secondary button. |
717 | /// * [LongPressGestureRecognizer.onTertiaryLongPressDown], which exposes |
718 | /// this callback at the gesture layer. |
719 | final GestureLongPressDownCallback? onTertiaryLongPressDown; |
720 | |
721 | /// A pointer that previously triggered [onTertiaryLongPressDown] will not |
722 | /// end up causing a long-press. |
723 | /// |
724 | /// This triggers once the gesture loses if [onTertiaryLongPressDown] has |
725 | /// previously been triggered. |
726 | /// |
727 | /// If the user completed the long-press, and the gesture won, then |
728 | /// [onTertiaryLongPressStart] and [onTertiaryLongPress] are called instead. |
729 | /// |
730 | /// See also: |
731 | /// |
732 | /// * [kTertiaryButton], the button this callback responds to. |
733 | /// * [LongPressGestureRecognizer.onTertiaryLongPressCancel], which exposes |
734 | /// this callback at the gesture layer. |
735 | final GestureLongPressCancelCallback? onTertiaryLongPressCancel; |
736 | |
737 | /// Called when a long press gesture with a tertiary button has been |
738 | /// recognized. |
739 | /// |
740 | /// Triggered when a pointer has remained in contact with the screen at the |
741 | /// same location for a long period of time. |
742 | /// |
743 | /// This is equivalent to (and is called immediately after) |
744 | /// [onTertiaryLongPressStart]. The only difference between the two is that |
745 | /// this callback does not contain details of the position at which the |
746 | /// pointer initially contacted the screen. |
747 | /// |
748 | /// See also: |
749 | /// |
750 | /// * [kTertiaryButton], the button this callback responds to. |
751 | /// * [LongPressGestureRecognizer.onTertiaryLongPress], which exposes |
752 | /// this callback at the gesture layer. |
753 | final GestureLongPressCallback? onTertiaryLongPress; |
754 | |
755 | /// Called when a long press gesture with a tertiary button has been |
756 | /// recognized. |
757 | /// |
758 | /// Triggered when a pointer has remained in contact with the screen at the |
759 | /// same location for a long period of time. |
760 | /// |
761 | /// This is equivalent to (and is called immediately before) |
762 | /// [onTertiaryLongPress]. The only difference between the two is that this |
763 | /// callback contains details of the position at which the pointer initially |
764 | /// contacted the screen, whereas [onTertiaryLongPress] does not. |
765 | /// |
766 | /// See also: |
767 | /// |
768 | /// * [kTertiaryButton], the button this callback responds to. |
769 | /// * [LongPressGestureRecognizer.onTertiaryLongPressStart], which exposes |
770 | /// this callback at the gesture layer. |
771 | final GestureLongPressStartCallback? onTertiaryLongPressStart; |
772 | |
773 | /// A pointer has been drag-moved after a long press with a tertiary button. |
774 | /// |
775 | /// See also: |
776 | /// |
777 | /// * [kTertiaryButton], the button this callback responds to. |
778 | /// * [LongPressGestureRecognizer.onTertiaryLongPressMoveUpdate], which exposes |
779 | /// this callback at the gesture layer. |
780 | final GestureLongPressMoveUpdateCallback? onTertiaryLongPressMoveUpdate; |
781 | |
782 | /// A pointer that has triggered a long-press with a tertiary button has |
783 | /// stopped contacting the screen. |
784 | /// |
785 | /// This is equivalent to (and is called immediately after) |
786 | /// [onTertiaryLongPressEnd]. The only difference between the two is that |
787 | /// this callback does not contain details of the state of the pointer when |
788 | /// it stopped contacting the screen. |
789 | /// |
790 | /// See also: |
791 | /// |
792 | /// * [kTertiaryButton], the button this callback responds to. |
793 | /// * [LongPressGestureRecognizer.onTertiaryLongPressUp], which exposes |
794 | /// this callback at the gesture layer. |
795 | final GestureLongPressUpCallback? onTertiaryLongPressUp; |
796 | |
797 | /// A pointer that has triggered a long-press with a tertiary button has |
798 | /// stopped contacting the screen. |
799 | /// |
800 | /// This is equivalent to (and is called immediately before) |
801 | /// [onTertiaryLongPressUp]. The only difference between the two is that |
802 | /// this callback contains details of the state of the pointer when it |
803 | /// stopped contacting the screen, whereas [onTertiaryLongPressUp] does not. |
804 | /// |
805 | /// See also: |
806 | /// |
807 | /// * [kTertiaryButton], the button this callback responds to. |
808 | /// * [LongPressGestureRecognizer.onTertiaryLongPressEnd], which exposes |
809 | /// this callback at the gesture layer. |
810 | final GestureLongPressEndCallback? onTertiaryLongPressEnd; |
811 | |
812 | /// A pointer has contacted the screen with a primary button and might begin |
813 | /// to move vertically. |
814 | /// |
815 | /// See also: |
816 | /// |
817 | /// * [kPrimaryButton], the button this callback responds to. |
818 | final GestureDragDownCallback? onVerticalDragDown; |
819 | |
820 | /// A pointer has contacted the screen with a primary button and has begun to |
821 | /// move vertically. |
822 | /// |
823 | /// See also: |
824 | /// |
825 | /// * [kPrimaryButton], the button this callback responds to. |
826 | final GestureDragStartCallback? onVerticalDragStart; |
827 | |
828 | /// A pointer that is in contact with the screen with a primary button and |
829 | /// moving vertically has moved in the vertical direction. |
830 | /// |
831 | /// See also: |
832 | /// |
833 | /// * [kPrimaryButton], the button this callback responds to. |
834 | final GestureDragUpdateCallback? onVerticalDragUpdate; |
835 | |
836 | /// A pointer that was previously in contact with the screen with a primary |
837 | /// button and moving vertically is no longer in contact with the screen and |
838 | /// was moving at a specific velocity when it stopped contacting the screen. |
839 | /// |
840 | /// See also: |
841 | /// |
842 | /// * [kPrimaryButton], the button this callback responds to. |
843 | final GestureDragEndCallback? onVerticalDragEnd; |
844 | |
845 | /// The pointer that previously triggered [onVerticalDragDown] did not |
846 | /// complete. |
847 | /// |
848 | /// See also: |
849 | /// |
850 | /// * [kPrimaryButton], the button this callback responds to. |
851 | final GestureDragCancelCallback? onVerticalDragCancel; |
852 | |
853 | /// A pointer has contacted the screen with a primary button and might begin |
854 | /// to move horizontally. |
855 | /// |
856 | /// See also: |
857 | /// |
858 | /// * [kPrimaryButton], the button this callback responds to. |
859 | final GestureDragDownCallback? onHorizontalDragDown; |
860 | |
861 | /// A pointer has contacted the screen with a primary button and has begun to |
862 | /// move horizontally. |
863 | /// |
864 | /// See also: |
865 | /// |
866 | /// * [kPrimaryButton], the button this callback responds to. |
867 | final GestureDragStartCallback? onHorizontalDragStart; |
868 | |
869 | /// A pointer that is in contact with the screen with a primary button and |
870 | /// moving horizontally has moved in the horizontal direction. |
871 | /// |
872 | /// See also: |
873 | /// |
874 | /// * [kPrimaryButton], the button this callback responds to. |
875 | final GestureDragUpdateCallback? onHorizontalDragUpdate; |
876 | |
877 | /// A pointer that was previously in contact with the screen with a primary |
878 | /// button and moving horizontally is no longer in contact with the screen and |
879 | /// was moving at a specific velocity when it stopped contacting the screen. |
880 | /// |
881 | /// See also: |
882 | /// |
883 | /// * [kPrimaryButton], the button this callback responds to. |
884 | final GestureDragEndCallback? onHorizontalDragEnd; |
885 | |
886 | /// The pointer that previously triggered [onHorizontalDragDown] did not |
887 | /// complete. |
888 | /// |
889 | /// See also: |
890 | /// |
891 | /// * [kPrimaryButton], the button this callback responds to. |
892 | final GestureDragCancelCallback? onHorizontalDragCancel; |
893 | |
894 | /// A pointer has contacted the screen with a primary button and might begin |
895 | /// to move. |
896 | /// |
897 | /// See also: |
898 | /// |
899 | /// * [kPrimaryButton], the button this callback responds to. |
900 | final GestureDragDownCallback? onPanDown; |
901 | |
902 | /// A pointer has contacted the screen with a primary button and has begun to |
903 | /// move. |
904 | /// |
905 | /// See also: |
906 | /// |
907 | /// * [kPrimaryButton], the button this callback responds to. |
908 | final GestureDragStartCallback? onPanStart; |
909 | |
910 | /// A pointer that is in contact with the screen with a primary button and |
911 | /// moving has moved again. |
912 | /// |
913 | /// See also: |
914 | /// |
915 | /// * [kPrimaryButton], the button this callback responds to. |
916 | final GestureDragUpdateCallback? onPanUpdate; |
917 | |
918 | /// A pointer that was previously in contact with the screen with a primary |
919 | /// button and moving is no longer in contact with the screen and was moving |
920 | /// at a specific velocity when it stopped contacting the screen. |
921 | /// |
922 | /// See also: |
923 | /// |
924 | /// * [kPrimaryButton], the button this callback responds to. |
925 | final GestureDragEndCallback? onPanEnd; |
926 | |
927 | /// The pointer that previously triggered [onPanDown] did not complete. |
928 | /// |
929 | /// See also: |
930 | /// |
931 | /// * [kPrimaryButton], the button this callback responds to. |
932 | final GestureDragCancelCallback? onPanCancel; |
933 | |
934 | /// The pointers in contact with the screen have established a focal point and |
935 | /// initial scale of 1.0. |
936 | final GestureScaleStartCallback? onScaleStart; |
937 | |
938 | /// The pointers in contact with the screen have indicated a new focal point |
939 | /// and/or scale. |
940 | final GestureScaleUpdateCallback? onScaleUpdate; |
941 | |
942 | /// The pointers are no longer in contact with the screen. |
943 | final GestureScaleEndCallback? onScaleEnd; |
944 | |
945 | /// The pointer is in contact with the screen and has pressed with sufficient |
946 | /// force to initiate a force press. The amount of force is at least |
947 | /// [ForcePressGestureRecognizer.startPressure]. |
948 | /// |
949 | /// This callback will only be fired on devices with pressure |
950 | /// detecting screens. |
951 | final GestureForcePressStartCallback? onForcePressStart; |
952 | |
953 | /// The pointer is in contact with the screen and has pressed with the maximum |
954 | /// force. The amount of force is at least |
955 | /// [ForcePressGestureRecognizer.peakPressure]. |
956 | /// |
957 | /// This callback will only be fired on devices with pressure |
958 | /// detecting screens. |
959 | final GestureForcePressPeakCallback? onForcePressPeak; |
960 | |
961 | /// A pointer is in contact with the screen, has previously passed the |
962 | /// [ForcePressGestureRecognizer.startPressure] and is either moving on the |
963 | /// plane of the screen, pressing the screen with varying forces or both |
964 | /// simultaneously. |
965 | /// |
966 | /// This callback will only be fired on devices with pressure |
967 | /// detecting screens. |
968 | final GestureForcePressUpdateCallback? onForcePressUpdate; |
969 | |
970 | /// The pointer tracked by [onForcePressStart] is no longer in contact with the screen. |
971 | /// |
972 | /// This callback will only be fired on devices with pressure |
973 | /// detecting screens. |
974 | final GestureForcePressEndCallback? onForcePressEnd; |
975 | |
976 | /// How this gesture detector should behave during hit testing when deciding |
977 | /// how the hit test propagates to children and whether to consider targets |
978 | /// behind this one. |
979 | /// |
980 | /// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and |
981 | /// [HitTestBehavior.translucent] if child is null. |
982 | /// |
983 | /// See [HitTestBehavior] for the allowed values and their meanings. |
984 | final HitTestBehavior? behavior; |
985 | |
986 | /// Whether to exclude these gestures from the semantics tree. For |
987 | /// example, the long-press gesture for showing a tooltip is |
988 | /// excluded because the tooltip itself is included in the semantics |
989 | /// tree directly and so having a gesture to show it would result in |
990 | /// duplication of information. |
991 | final bool excludeFromSemantics; |
992 | |
993 | /// Determines the way that drag start behavior is handled. |
994 | /// |
995 | /// If set to [DragStartBehavior.start], gesture drag behavior will |
996 | /// begin at the position where the drag gesture won the arena. If set to |
997 | /// [DragStartBehavior.down] it will begin at the position where a down event |
998 | /// is first detected. |
999 | /// |
1000 | /// In general, setting this to [DragStartBehavior.start] will make drag |
1001 | /// animation smoother and setting it to [DragStartBehavior.down] will make |
1002 | /// drag behavior feel slightly more reactive. |
1003 | /// |
1004 | /// By default, the drag start behavior is [DragStartBehavior.start]. |
1005 | /// |
1006 | /// Only the [DragGestureRecognizer.onStart] callbacks for the |
1007 | /// [VerticalDragGestureRecognizer], [HorizontalDragGestureRecognizer] and |
1008 | /// [PanGestureRecognizer] are affected by this setting. |
1009 | /// |
1010 | /// See also: |
1011 | /// |
1012 | /// * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors. |
1013 | final DragStartBehavior dragStartBehavior; |
1014 | |
1015 | /// The kind of devices that are allowed to be recognized. |
1016 | /// |
1017 | /// If set to null, events from all device types will be recognized. Defaults to null. |
1018 | final Set<PointerDeviceKind>? supportedDevices; |
1019 | |
1020 | /// {@macro flutter.gestures.scale.trackpadScrollCausesScale} |
1021 | final bool trackpadScrollCausesScale; |
1022 | |
1023 | /// {@macro flutter.gestures.scale.trackpadScrollToScaleFactor} |
1024 | final Offset trackpadScrollToScaleFactor; |
1025 | |
1026 | @override |
1027 | Widget build(BuildContext context) { |
1028 | final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{}; |
1029 | final DeviceGestureSettings? gestureSettings = MediaQuery.maybeGestureSettingsOf(context); |
1030 | final ScrollBehavior configuration = ScrollConfiguration.of(context); |
1031 | |
1032 | if (onTapDown != null || |
1033 | onTapUp != null || |
1034 | onTap != null || |
1035 | onTapCancel != null || |
1036 | onSecondaryTap != null || |
1037 | onSecondaryTapDown != null || |
1038 | onSecondaryTapUp != null || |
1039 | onSecondaryTapCancel != null|| |
1040 | onTertiaryTapDown != null || |
1041 | onTertiaryTapUp != null || |
1042 | onTertiaryTapCancel != null |
1043 | ) { |
1044 | gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>( |
1045 | () => TapGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices), |
1046 | (TapGestureRecognizer instance) { |
1047 | instance |
1048 | ..onTapDown = onTapDown |
1049 | ..onTapUp = onTapUp |
1050 | ..onTap = onTap |
1051 | ..onTapCancel = onTapCancel |
1052 | ..onSecondaryTap = onSecondaryTap |
1053 | ..onSecondaryTapDown = onSecondaryTapDown |
1054 | ..onSecondaryTapUp = onSecondaryTapUp |
1055 | ..onSecondaryTapCancel = onSecondaryTapCancel |
1056 | ..onTertiaryTapDown = onTertiaryTapDown |
1057 | ..onTertiaryTapUp = onTertiaryTapUp |
1058 | ..onTertiaryTapCancel = onTertiaryTapCancel |
1059 | ..gestureSettings = gestureSettings |
1060 | ..supportedDevices = supportedDevices; |
1061 | }, |
1062 | ); |
1063 | } |
1064 | |
1065 | if (onDoubleTap != null || |
1066 | onDoubleTapDown != null || |
1067 | onDoubleTapCancel != null) { |
1068 | gestures[DoubleTapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>( |
1069 | () => DoubleTapGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices), |
1070 | (DoubleTapGestureRecognizer instance) { |
1071 | instance |
1072 | ..onDoubleTapDown = onDoubleTapDown |
1073 | ..onDoubleTap = onDoubleTap |
1074 | ..onDoubleTapCancel = onDoubleTapCancel |
1075 | ..gestureSettings = gestureSettings |
1076 | ..supportedDevices = supportedDevices; |
1077 | }, |
1078 | ); |
1079 | } |
1080 | |
1081 | if (onLongPressDown != null || |
1082 | onLongPressCancel != null || |
1083 | onLongPress != null || |
1084 | onLongPressStart != null || |
1085 | onLongPressMoveUpdate != null || |
1086 | onLongPressUp != null || |
1087 | onLongPressEnd != null || |
1088 | onSecondaryLongPressDown != null || |
1089 | onSecondaryLongPressCancel != null || |
1090 | onSecondaryLongPress != null || |
1091 | onSecondaryLongPressStart != null || |
1092 | onSecondaryLongPressMoveUpdate != null || |
1093 | onSecondaryLongPressUp != null || |
1094 | onSecondaryLongPressEnd != null || |
1095 | onTertiaryLongPressDown != null || |
1096 | onTertiaryLongPressCancel != null || |
1097 | onTertiaryLongPress != null || |
1098 | onTertiaryLongPressStart != null || |
1099 | onTertiaryLongPressMoveUpdate != null || |
1100 | onTertiaryLongPressUp != null || |
1101 | onTertiaryLongPressEnd != null) { |
1102 | gestures[LongPressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>( |
1103 | () => LongPressGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices), |
1104 | (LongPressGestureRecognizer instance) { |
1105 | instance |
1106 | ..onLongPressDown = onLongPressDown |
1107 | ..onLongPressCancel = onLongPressCancel |
1108 | ..onLongPress = onLongPress |
1109 | ..onLongPressStart = onLongPressStart |
1110 | ..onLongPressMoveUpdate = onLongPressMoveUpdate |
1111 | ..onLongPressUp = onLongPressUp |
1112 | ..onLongPressEnd = onLongPressEnd |
1113 | ..onSecondaryLongPressDown = onSecondaryLongPressDown |
1114 | ..onSecondaryLongPressCancel = onSecondaryLongPressCancel |
1115 | ..onSecondaryLongPress = onSecondaryLongPress |
1116 | ..onSecondaryLongPressStart = onSecondaryLongPressStart |
1117 | ..onSecondaryLongPressMoveUpdate = onSecondaryLongPressMoveUpdate |
1118 | ..onSecondaryLongPressUp = onSecondaryLongPressUp |
1119 | ..onSecondaryLongPressEnd = onSecondaryLongPressEnd |
1120 | ..onTertiaryLongPressDown = onTertiaryLongPressDown |
1121 | ..onTertiaryLongPressCancel = onTertiaryLongPressCancel |
1122 | ..onTertiaryLongPress = onTertiaryLongPress |
1123 | ..onTertiaryLongPressStart = onTertiaryLongPressStart |
1124 | ..onTertiaryLongPressMoveUpdate = onTertiaryLongPressMoveUpdate |
1125 | ..onTertiaryLongPressUp = onTertiaryLongPressUp |
1126 | ..onTertiaryLongPressEnd = onTertiaryLongPressEnd |
1127 | ..gestureSettings = gestureSettings |
1128 | ..supportedDevices = supportedDevices; |
1129 | }, |
1130 | ); |
1131 | } |
1132 | |
1133 | if (onVerticalDragDown != null || |
1134 | onVerticalDragStart != null || |
1135 | onVerticalDragUpdate != null || |
1136 | onVerticalDragEnd != null || |
1137 | onVerticalDragCancel != null) { |
1138 | gestures[VerticalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>( |
1139 | () => VerticalDragGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices), |
1140 | (VerticalDragGestureRecognizer instance) { |
1141 | instance |
1142 | ..onDown = onVerticalDragDown |
1143 | ..onStart = onVerticalDragStart |
1144 | ..onUpdate = onVerticalDragUpdate |
1145 | ..onEnd = onVerticalDragEnd |
1146 | ..onCancel = onVerticalDragCancel |
1147 | ..dragStartBehavior = dragStartBehavior |
1148 | ..multitouchDragStrategy = configuration.getMultitouchDragStrategy(context) |
1149 | ..gestureSettings = gestureSettings |
1150 | ..supportedDevices = supportedDevices; |
1151 | }, |
1152 | ); |
1153 | } |
1154 | |
1155 | if (onHorizontalDragDown != null || |
1156 | onHorizontalDragStart != null || |
1157 | onHorizontalDragUpdate != null || |
1158 | onHorizontalDragEnd != null || |
1159 | onHorizontalDragCancel != null) { |
1160 | gestures[HorizontalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>( |
1161 | () => HorizontalDragGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices), |
1162 | (HorizontalDragGestureRecognizer instance) { |
1163 | instance |
1164 | ..onDown = onHorizontalDragDown |
1165 | ..onStart = onHorizontalDragStart |
1166 | ..onUpdate = onHorizontalDragUpdate |
1167 | ..onEnd = onHorizontalDragEnd |
1168 | ..onCancel = onHorizontalDragCancel |
1169 | ..dragStartBehavior = dragStartBehavior |
1170 | ..multitouchDragStrategy = configuration.getMultitouchDragStrategy(context) |
1171 | ..gestureSettings = gestureSettings |
1172 | ..supportedDevices = supportedDevices; |
1173 | }, |
1174 | ); |
1175 | } |
1176 | |
1177 | if (onPanDown != null || |
1178 | onPanStart != null || |
1179 | onPanUpdate != null || |
1180 | onPanEnd != null || |
1181 | onPanCancel != null) { |
1182 | gestures[PanGestureRecognizer] = GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>( |
1183 | () => PanGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices), |
1184 | (PanGestureRecognizer instance) { |
1185 | instance |
1186 | ..onDown = onPanDown |
1187 | ..onStart = onPanStart |
1188 | ..onUpdate = onPanUpdate |
1189 | ..onEnd = onPanEnd |
1190 | ..onCancel = onPanCancel |
1191 | ..dragStartBehavior = dragStartBehavior |
1192 | ..multitouchDragStrategy = configuration.getMultitouchDragStrategy(context) |
1193 | ..gestureSettings = gestureSettings |
1194 | ..supportedDevices = supportedDevices; |
1195 | }, |
1196 | ); |
1197 | } |
1198 | |
1199 | if (onScaleStart != null || onScaleUpdate != null || onScaleEnd != null) { |
1200 | gestures[ScaleGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ScaleGestureRecognizer>( |
1201 | () => ScaleGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices), |
1202 | (ScaleGestureRecognizer instance) { |
1203 | instance |
1204 | ..onStart = onScaleStart |
1205 | ..onUpdate = onScaleUpdate |
1206 | ..onEnd = onScaleEnd |
1207 | ..dragStartBehavior = dragStartBehavior |
1208 | ..gestureSettings = gestureSettings |
1209 | ..trackpadScrollCausesScale = trackpadScrollCausesScale |
1210 | ..trackpadScrollToScaleFactor = trackpadScrollToScaleFactor |
1211 | ..supportedDevices = supportedDevices; |
1212 | }, |
1213 | ); |
1214 | } |
1215 | |
1216 | if (onForcePressStart != null || |
1217 | onForcePressPeak != null || |
1218 | onForcePressUpdate != null || |
1219 | onForcePressEnd != null) { |
1220 | gestures[ForcePressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<ForcePressGestureRecognizer>( |
1221 | () => ForcePressGestureRecognizer(debugOwner: this, supportedDevices: supportedDevices), |
1222 | (ForcePressGestureRecognizer instance) { |
1223 | instance |
1224 | ..onStart = onForcePressStart |
1225 | ..onPeak = onForcePressPeak |
1226 | ..onUpdate = onForcePressUpdate |
1227 | ..onEnd = onForcePressEnd |
1228 | ..gestureSettings = gestureSettings |
1229 | ..supportedDevices = supportedDevices; |
1230 | }, |
1231 | ); |
1232 | } |
1233 | |
1234 | return RawGestureDetector( |
1235 | gestures: gestures, |
1236 | behavior: behavior, |
1237 | excludeFromSemantics: excludeFromSemantics, |
1238 | child: child, |
1239 | ); |
1240 | } |
1241 | |
1242 | @override |
1243 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
1244 | super.debugFillProperties(properties); |
1245 | properties.add(EnumProperty<DragStartBehavior>('startBehavior' , dragStartBehavior)); |
1246 | } |
1247 | } |
1248 | |
1249 | /// A widget that detects gestures described by the given gesture |
1250 | /// factories. |
1251 | /// |
1252 | /// For common gestures, use a [GestureDetector]. |
1253 | /// [RawGestureDetector] is useful primarily when developing your |
1254 | /// own gesture recognizers. |
1255 | /// |
1256 | /// Configuring the gesture recognizers requires a carefully constructed map, as |
1257 | /// described in [gestures] and as shown in the example below. |
1258 | /// |
1259 | /// {@tool snippet} |
1260 | /// |
1261 | /// This example shows how to hook up a [TapGestureRecognizer]. It assumes that |
1262 | /// the code is being used inside a [State] object with a `_last` field that is |
1263 | /// then displayed as the child of the gesture detector. |
1264 | /// |
1265 | /// ```dart |
1266 | /// RawGestureDetector( |
1267 | /// gestures: <Type, GestureRecognizerFactory>{ |
1268 | /// TapGestureRecognizer: GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>( |
1269 | /// () => TapGestureRecognizer(), |
1270 | /// (TapGestureRecognizer instance) { |
1271 | /// instance |
1272 | /// ..onTapDown = (TapDownDetails details) { setState(() { _last = 'down'; }); } |
1273 | /// ..onTapUp = (TapUpDetails details) { setState(() { _last = 'up'; }); } |
1274 | /// ..onTap = () { setState(() { _last = 'tap'; }); } |
1275 | /// ..onTapCancel = () { setState(() { _last = 'cancel'; }); }; |
1276 | /// }, |
1277 | /// ), |
1278 | /// }, |
1279 | /// child: Container(width: 300.0, height: 300.0, color: Colors.yellow, child: Text(_last)), |
1280 | /// ) |
1281 | /// ``` |
1282 | /// {@end-tool} |
1283 | /// |
1284 | /// See also: |
1285 | /// |
1286 | /// * [GestureDetector], a less flexible but much simpler widget that does the same thing. |
1287 | /// * [Listener], a widget that reports raw pointer events. |
1288 | /// * [GestureRecognizer], the class that you extend to create a custom gesture recognizer. |
1289 | class RawGestureDetector extends StatefulWidget { |
1290 | /// Creates a widget that detects gestures. |
1291 | /// |
1292 | /// Gesture detectors can contribute semantic information to the tree that is |
1293 | /// used by assistive technology. The behavior can be configured by |
1294 | /// [semantics], or disabled with [excludeFromSemantics]. |
1295 | const RawGestureDetector({ |
1296 | super.key, |
1297 | this.child, |
1298 | this.gestures = const <Type, GestureRecognizerFactory>{}, |
1299 | this.behavior, |
1300 | this.excludeFromSemantics = false, |
1301 | this.semantics, |
1302 | }); |
1303 | |
1304 | /// The widget below this widget in the tree. |
1305 | /// |
1306 | /// {@macro flutter.widgets.ProxyWidget.child} |
1307 | final Widget? child; |
1308 | |
1309 | /// The gestures that this widget will attempt to recognize. |
1310 | /// |
1311 | /// This should be a map from [GestureRecognizer] subclasses to |
1312 | /// [GestureRecognizerFactory] subclasses specialized with the same type. |
1313 | /// |
1314 | /// This value can be late-bound at layout time using |
1315 | /// [RawGestureDetectorState.replaceGestureRecognizers]. |
1316 | final Map<Type, GestureRecognizerFactory> gestures; |
1317 | |
1318 | /// How this gesture detector should behave during hit testing. |
1319 | /// |
1320 | /// This defaults to [HitTestBehavior.deferToChild] if [child] is not null and |
1321 | /// [HitTestBehavior.translucent] if child is null. |
1322 | final HitTestBehavior? behavior; |
1323 | |
1324 | /// Whether to exclude these gestures from the semantics tree. For |
1325 | /// example, the long-press gesture for showing a tooltip is |
1326 | /// excluded because the tooltip itself is included in the semantics |
1327 | /// tree directly and so having a gesture to show it would result in |
1328 | /// duplication of information. |
1329 | final bool excludeFromSemantics; |
1330 | |
1331 | /// Describes the semantics notations that should be added to the underlying |
1332 | /// render object [RenderSemanticsGestureHandler]. |
1333 | /// |
1334 | /// It has no effect if [excludeFromSemantics] is true. |
1335 | /// |
1336 | /// When [semantics] is null, [RawGestureDetector] will fall back to a |
1337 | /// default delegate which checks if the detector owns certain gesture |
1338 | /// recognizers and calls their callbacks if they exist: |
1339 | /// |
1340 | /// * During a semantic tap, it calls [TapGestureRecognizer]'s |
1341 | /// `onTapDown`, `onTapUp`, and `onTap`. |
1342 | /// * During a semantic long press, it calls [LongPressGestureRecognizer]'s |
1343 | /// `onLongPressDown`, `onLongPressStart`, `onLongPress`, `onLongPressEnd` |
1344 | /// and `onLongPressUp`. |
1345 | /// * During a semantic horizontal drag, it calls [HorizontalDragGestureRecognizer]'s |
1346 | /// `onDown`, `onStart`, `onUpdate` and `onEnd`, then |
1347 | /// [PanGestureRecognizer]'s `onDown`, `onStart`, `onUpdate` and `onEnd`. |
1348 | /// * During a semantic vertical drag, it calls [VerticalDragGestureRecognizer]'s |
1349 | /// `onDown`, `onStart`, `onUpdate` and `onEnd`, then |
1350 | /// [PanGestureRecognizer]'s `onDown`, `onStart`, `onUpdate` and `onEnd`. |
1351 | /// |
1352 | /// {@tool snippet} |
1353 | /// This custom gesture detector listens to force presses, while also allows |
1354 | /// the same callback to be triggered by semantic long presses. |
1355 | /// |
1356 | /// ```dart |
1357 | /// class ForcePressGestureDetectorWithSemantics extends StatelessWidget { |
1358 | /// const ForcePressGestureDetectorWithSemantics({ |
1359 | /// super.key, |
1360 | /// required this.child, |
1361 | /// required this.onForcePress, |
1362 | /// }); |
1363 | /// |
1364 | /// final Widget child; |
1365 | /// final VoidCallback onForcePress; |
1366 | /// |
1367 | /// @override |
1368 | /// Widget build(BuildContext context) { |
1369 | /// return RawGestureDetector( |
1370 | /// gestures: <Type, GestureRecognizerFactory>{ |
1371 | /// ForcePressGestureRecognizer: GestureRecognizerFactoryWithHandlers<ForcePressGestureRecognizer>( |
1372 | /// () => ForcePressGestureRecognizer(debugOwner: this), |
1373 | /// (ForcePressGestureRecognizer instance) { |
1374 | /// instance.onStart = (_) => onForcePress(); |
1375 | /// } |
1376 | /// ), |
1377 | /// }, |
1378 | /// behavior: HitTestBehavior.opaque, |
1379 | /// semantics: _LongPressSemanticsDelegate(onForcePress), |
1380 | /// child: child, |
1381 | /// ); |
1382 | /// } |
1383 | /// } |
1384 | /// |
1385 | /// class _LongPressSemanticsDelegate extends SemanticsGestureDelegate { |
1386 | /// _LongPressSemanticsDelegate(this.onLongPress); |
1387 | /// |
1388 | /// VoidCallback onLongPress; |
1389 | /// |
1390 | /// @override |
1391 | /// void assignSemantics(RenderSemanticsGestureHandler renderObject) { |
1392 | /// renderObject.onLongPress = onLongPress; |
1393 | /// } |
1394 | /// } |
1395 | /// ``` |
1396 | /// {@end-tool} |
1397 | final SemanticsGestureDelegate? semantics; |
1398 | |
1399 | @override |
1400 | RawGestureDetectorState createState() => RawGestureDetectorState(); |
1401 | } |
1402 | |
1403 | /// State for a [RawGestureDetector]. |
1404 | class RawGestureDetectorState extends State<RawGestureDetector> { |
1405 | Map<Type, GestureRecognizer>? _recognizers = const <Type, GestureRecognizer>{}; |
1406 | SemanticsGestureDelegate? _semantics; |
1407 | |
1408 | @override |
1409 | void initState() { |
1410 | super.initState(); |
1411 | _semantics = widget.semantics ?? _DefaultSemanticsGestureDelegate(this); |
1412 | _syncAll(widget.gestures); |
1413 | } |
1414 | |
1415 | @override |
1416 | void didUpdateWidget(RawGestureDetector oldWidget) { |
1417 | super.didUpdateWidget(oldWidget); |
1418 | if (!(oldWidget.semantics == null && widget.semantics == null)) { |
1419 | _semantics = widget.semantics ?? _DefaultSemanticsGestureDelegate(this); |
1420 | } |
1421 | _syncAll(widget.gestures); |
1422 | } |
1423 | |
1424 | /// This method can be called after the build phase, during the |
1425 | /// layout of the nearest descendant [RenderObjectWidget] of the |
1426 | /// gesture detector, to update the list of active gesture |
1427 | /// recognizers. |
1428 | /// |
1429 | /// The typical use case is [Scrollable]s, which put their viewport |
1430 | /// in their gesture detector, and then need to know the dimensions |
1431 | /// of the viewport and the viewport's child to determine whether |
1432 | /// the gesture detector should be enabled. |
1433 | /// |
1434 | /// The argument should follow the same conventions as |
1435 | /// [RawGestureDetector.gestures]. It acts like a temporary replacement for |
1436 | /// that value until the next build. |
1437 | void replaceGestureRecognizers(Map<Type, GestureRecognizerFactory> gestures) { |
1438 | assert(() { |
1439 | if (!context.findRenderObject()!.owner!.debugDoingLayout) { |
1440 | throw FlutterError.fromParts(<DiagnosticsNode>[ |
1441 | ErrorSummary('Unexpected call to replaceGestureRecognizers() method of RawGestureDetectorState.' ), |
1442 | ErrorDescription('The replaceGestureRecognizers() method can only be called during the layout phase.' ), |
1443 | ErrorHint( |
1444 | 'To set the gesture recognizers at other times, trigger a new build using setState() ' |
1445 | 'and provide the new gesture recognizers as constructor arguments to the corresponding ' |
1446 | 'RawGestureDetector or GestureDetector object.' , |
1447 | ), |
1448 | ]); |
1449 | } |
1450 | return true; |
1451 | }()); |
1452 | _syncAll(gestures); |
1453 | if (!widget.excludeFromSemantics) { |
1454 | final RenderSemanticsGestureHandler semanticsGestureHandler = context.findRenderObject()! as RenderSemanticsGestureHandler; |
1455 | _updateSemanticsForRenderObject(semanticsGestureHandler); |
1456 | } |
1457 | } |
1458 | |
1459 | /// This method can be called to filter the list of available semantic actions, |
1460 | /// after the render object was created. |
1461 | /// |
1462 | /// The actual filtering is happening in the next frame and a frame will be |
1463 | /// scheduled if non is pending. |
1464 | /// |
1465 | /// This is used by [Scrollable] to configure system accessibility tools so |
1466 | /// that they know in which direction a particular list can be scrolled. |
1467 | /// |
1468 | /// If this is never called, then the actions are not filtered. If the list of |
1469 | /// actions to filter changes, it must be called again. |
1470 | void replaceSemanticsActions(Set<SemanticsAction> actions) { |
1471 | if (widget.excludeFromSemantics) { |
1472 | return; |
1473 | } |
1474 | |
1475 | final RenderSemanticsGestureHandler? semanticsGestureHandler = context.findRenderObject() as RenderSemanticsGestureHandler?; |
1476 | assert(() { |
1477 | if (semanticsGestureHandler == null) { |
1478 | throw FlutterError( |
1479 | 'Unexpected call to replaceSemanticsActions() method of RawGestureDetectorState.\n' |
1480 | 'The replaceSemanticsActions() method can only be called after the RenderSemanticsGestureHandler has been created.' , |
1481 | ); |
1482 | } |
1483 | return true; |
1484 | }()); |
1485 | |
1486 | semanticsGestureHandler!.validActions = actions; // will call _markNeedsSemanticsUpdate(), if required. |
1487 | } |
1488 | |
1489 | @override |
1490 | void dispose() { |
1491 | for (final GestureRecognizer recognizer in _recognizers!.values) { |
1492 | recognizer.dispose(); |
1493 | } |
1494 | _recognizers = null; |
1495 | super.dispose(); |
1496 | } |
1497 | |
1498 | void _syncAll(Map<Type, GestureRecognizerFactory> gestures) { |
1499 | assert(_recognizers != null); |
1500 | final Map<Type, GestureRecognizer> oldRecognizers = _recognizers!; |
1501 | _recognizers = <Type, GestureRecognizer>{}; |
1502 | for (final Type type in gestures.keys) { |
1503 | assert(gestures[type] != null); |
1504 | assert(gestures[type]!._debugAssertTypeMatches(type)); |
1505 | assert(!_recognizers!.containsKey(type)); |
1506 | _recognizers![type] = oldRecognizers[type] ?? gestures[type]!.constructor(); |
1507 | 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.' ); |
1508 | gestures[type]!.initializer(_recognizers![type]!); |
1509 | } |
1510 | for (final Type type in oldRecognizers.keys) { |
1511 | if (!_recognizers!.containsKey(type)) { |
1512 | oldRecognizers[type]!.dispose(); |
1513 | } |
1514 | } |
1515 | } |
1516 | |
1517 | void _handlePointerDown(PointerDownEvent event) { |
1518 | assert(_recognizers != null); |
1519 | for (final GestureRecognizer recognizer in _recognizers!.values) { |
1520 | recognizer.addPointer(event); |
1521 | } |
1522 | } |
1523 | |
1524 | void _handlePointerPanZoomStart(PointerPanZoomStartEvent event) { |
1525 | assert(_recognizers != null); |
1526 | for (final GestureRecognizer recognizer in _recognizers!.values) { |
1527 | recognizer.addPointerPanZoom(event); |
1528 | } |
1529 | } |
1530 | |
1531 | HitTestBehavior get _defaultBehavior { |
1532 | return widget.child == null ? HitTestBehavior.translucent : HitTestBehavior.deferToChild; |
1533 | } |
1534 | |
1535 | void _updateSemanticsForRenderObject(RenderSemanticsGestureHandler renderObject) { |
1536 | assert(!widget.excludeFromSemantics); |
1537 | assert(_semantics != null); |
1538 | _semantics!.assignSemantics(renderObject); |
1539 | } |
1540 | |
1541 | @override |
1542 | Widget build(BuildContext context) { |
1543 | Widget result = Listener( |
1544 | onPointerDown: _handlePointerDown, |
1545 | onPointerPanZoomStart: _handlePointerPanZoomStart, |
1546 | behavior: widget.behavior ?? _defaultBehavior, |
1547 | child: widget.child, |
1548 | ); |
1549 | if (!widget.excludeFromSemantics) { |
1550 | result = _GestureSemantics( |
1551 | behavior: widget.behavior ?? _defaultBehavior, |
1552 | assignSemantics: _updateSemanticsForRenderObject, |
1553 | child: result, |
1554 | ); |
1555 | } |
1556 | return result; |
1557 | } |
1558 | |
1559 | @override |
1560 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
1561 | super.debugFillProperties(properties); |
1562 | if (_recognizers == null) { |
1563 | properties.add(DiagnosticsNode.message('DISPOSED' )); |
1564 | } else { |
1565 | final List<String> gestures = _recognizers!.values.map<String>((GestureRecognizer recognizer) => recognizer.debugDescription).toList(); |
1566 | properties.add(IterableProperty<String>('gestures' , gestures, ifEmpty: '<none>' )); |
1567 | properties.add(IterableProperty<GestureRecognizer>('recognizers' , _recognizers!.values, level: DiagnosticLevel.fine)); |
1568 | properties.add(DiagnosticsProperty<bool>('excludeFromSemantics' , widget.excludeFromSemantics, defaultValue: false)); |
1569 | if (!widget.excludeFromSemantics) { |
1570 | properties.add(DiagnosticsProperty<SemanticsGestureDelegate>('semantics' , widget.semantics, defaultValue: null)); |
1571 | } |
1572 | } |
1573 | properties.add(EnumProperty<HitTestBehavior>('behavior' , widget.behavior, defaultValue: null)); |
1574 | } |
1575 | } |
1576 | |
1577 | typedef _AssignSemantics = void Function(RenderSemanticsGestureHandler); |
1578 | |
1579 | class _GestureSemantics extends SingleChildRenderObjectWidget { |
1580 | const _GestureSemantics({ |
1581 | super.child, |
1582 | required this.behavior, |
1583 | required this.assignSemantics, |
1584 | }); |
1585 | |
1586 | final HitTestBehavior behavior; |
1587 | final _AssignSemantics assignSemantics; |
1588 | |
1589 | @override |
1590 | RenderSemanticsGestureHandler createRenderObject(BuildContext context) { |
1591 | final RenderSemanticsGestureHandler renderObject = RenderSemanticsGestureHandler() |
1592 | ..behavior = behavior; |
1593 | assignSemantics(renderObject); |
1594 | return renderObject; |
1595 | } |
1596 | |
1597 | @override |
1598 | void updateRenderObject(BuildContext context, RenderSemanticsGestureHandler renderObject) { |
1599 | renderObject.behavior = behavior; |
1600 | assignSemantics(renderObject); |
1601 | } |
1602 | } |
1603 | |
1604 | /// A base class that describes what semantics notations a [RawGestureDetector] |
1605 | /// should add to the render object [RenderSemanticsGestureHandler]. |
1606 | /// |
1607 | /// It is used to allow custom [GestureDetector]s to add semantics notations. |
1608 | abstract class SemanticsGestureDelegate { |
1609 | /// Create a delegate of gesture semantics. |
1610 | const SemanticsGestureDelegate(); |
1611 | |
1612 | /// Assigns semantics notations to the [RenderSemanticsGestureHandler] render |
1613 | /// object of the gesture detector. |
1614 | /// |
1615 | /// This method is called when the widget is created, updated, or during |
1616 | /// [RawGestureDetectorState.replaceGestureRecognizers]. |
1617 | void assignSemantics(RenderSemanticsGestureHandler renderObject); |
1618 | |
1619 | @override |
1620 | String toString() => ' ${objectRuntimeType(this, 'SemanticsGestureDelegate' )}()' ; |
1621 | } |
1622 | |
1623 | // The default semantics delegate of [RawGestureDetector]. Its behavior is |
1624 | // described in [RawGestureDetector.semantics]. |
1625 | // |
1626 | // For readers who come here to learn how to write custom semantics delegates: |
1627 | // this is not a proper sample code. It has access to the detector state as well |
1628 | // as its private properties, which are inaccessible normally. It is designed |
1629 | // this way in order to work independently in a [RawGestureRecognizer] to |
1630 | // preserve existing behavior. |
1631 | // |
1632 | // Instead, a normal delegate will store callbacks as properties, and use them |
1633 | // in `assignSemantics`. |
1634 | class _DefaultSemanticsGestureDelegate extends SemanticsGestureDelegate { |
1635 | _DefaultSemanticsGestureDelegate(this.detectorState); |
1636 | |
1637 | final RawGestureDetectorState detectorState; |
1638 | |
1639 | @override |
1640 | void assignSemantics(RenderSemanticsGestureHandler renderObject) { |
1641 | assert(!detectorState.widget.excludeFromSemantics); |
1642 | final Map<Type, GestureRecognizer> recognizers = detectorState._recognizers!; |
1643 | renderObject |
1644 | ..onTap = _getTapHandler(recognizers) |
1645 | ..onLongPress = _getLongPressHandler(recognizers) |
1646 | ..onHorizontalDragUpdate = _getHorizontalDragUpdateHandler(recognizers) |
1647 | ..onVerticalDragUpdate = _getVerticalDragUpdateHandler(recognizers); |
1648 | } |
1649 | |
1650 | GestureTapCallback? _getTapHandler(Map<Type, GestureRecognizer> recognizers) { |
1651 | final TapGestureRecognizer? tap = recognizers[TapGestureRecognizer] as TapGestureRecognizer?; |
1652 | if (tap == null) { |
1653 | return null; |
1654 | } |
1655 | |
1656 | return () { |
1657 | tap.onTapDown?.call(TapDownDetails()); |
1658 | tap.onTapUp?.call(TapUpDetails(kind: PointerDeviceKind.unknown)); |
1659 | tap.onTap?.call(); |
1660 | }; |
1661 | } |
1662 | |
1663 | GestureLongPressCallback? _getLongPressHandler(Map<Type, GestureRecognizer> recognizers) { |
1664 | final LongPressGestureRecognizer? longPress = recognizers[LongPressGestureRecognizer] as LongPressGestureRecognizer?; |
1665 | if (longPress == null) { |
1666 | return null; |
1667 | } |
1668 | |
1669 | return () { |
1670 | longPress.onLongPressDown?.call(const LongPressDownDetails()); |
1671 | longPress.onLongPressStart?.call(const LongPressStartDetails()); |
1672 | longPress.onLongPress?.call(); |
1673 | longPress.onLongPressEnd?.call(const LongPressEndDetails()); |
1674 | longPress.onLongPressUp?.call(); |
1675 | }; |
1676 | } |
1677 | |
1678 | GestureDragUpdateCallback? _getHorizontalDragUpdateHandler(Map<Type, GestureRecognizer> recognizers) { |
1679 | final HorizontalDragGestureRecognizer? horizontal = recognizers[HorizontalDragGestureRecognizer] as HorizontalDragGestureRecognizer?; |
1680 | final PanGestureRecognizer? pan = recognizers[PanGestureRecognizer] as PanGestureRecognizer?; |
1681 | |
1682 | final GestureDragUpdateCallback? horizontalHandler = horizontal == null ? |
1683 | null : |
1684 | (DragUpdateDetails details) { |
1685 | horizontal.onDown?.call(DragDownDetails()); |
1686 | horizontal.onStart?.call(DragStartDetails()); |
1687 | horizontal.onUpdate?.call(details); |
1688 | horizontal.onEnd?.call(DragEndDetails(primaryVelocity: 0.0)); |
1689 | }; |
1690 | |
1691 | final GestureDragUpdateCallback? panHandler = pan == null ? |
1692 | null : |
1693 | (DragUpdateDetails details) { |
1694 | pan.onDown?.call(DragDownDetails()); |
1695 | pan.onStart?.call(DragStartDetails()); |
1696 | pan.onUpdate?.call(details); |
1697 | pan.onEnd?.call(DragEndDetails()); |
1698 | }; |
1699 | |
1700 | if (horizontalHandler == null && panHandler == null) { |
1701 | return null; |
1702 | } |
1703 | return (DragUpdateDetails details) { |
1704 | horizontalHandler?.call(details); |
1705 | panHandler?.call(details); |
1706 | }; |
1707 | } |
1708 | |
1709 | GestureDragUpdateCallback? _getVerticalDragUpdateHandler(Map<Type, GestureRecognizer> recognizers) { |
1710 | final VerticalDragGestureRecognizer? vertical = recognizers[VerticalDragGestureRecognizer] as VerticalDragGestureRecognizer?; |
1711 | final PanGestureRecognizer? pan = recognizers[PanGestureRecognizer] as PanGestureRecognizer?; |
1712 | |
1713 | final GestureDragUpdateCallback? verticalHandler = vertical == null ? |
1714 | null : |
1715 | (DragUpdateDetails details) { |
1716 | vertical.onDown?.call(DragDownDetails()); |
1717 | vertical.onStart?.call(DragStartDetails()); |
1718 | vertical.onUpdate?.call(details); |
1719 | vertical.onEnd?.call(DragEndDetails(primaryVelocity: 0.0)); |
1720 | }; |
1721 | |
1722 | final GestureDragUpdateCallback? panHandler = pan == null ? |
1723 | null : |
1724 | (DragUpdateDetails details) { |
1725 | pan.onDown?.call(DragDownDetails()); |
1726 | pan.onStart?.call(DragStartDetails()); |
1727 | pan.onUpdate?.call(details); |
1728 | pan.onEnd?.call(DragEndDetails()); |
1729 | }; |
1730 | |
1731 | if (verticalHandler == null && panHandler == null) { |
1732 | return null; |
1733 | } |
1734 | return (DragUpdateDetails details) { |
1735 | verticalHandler?.call(details); |
1736 | panHandler?.call(details); |
1737 | }; |
1738 | } |
1739 | } |
1740 | |