-
-
Save Nash0x7E2/08acca529096d93f3df0f60f9c034056 to your computer and use it in GitHub Desktop.
import 'package:flutter/gestures.dart'; | |
import 'package:flutter/material.dart'; | |
//Main function. The entry point for your Flutter app. | |
void main() { | |
runApp( | |
MaterialApp( | |
home: Scaffold( | |
body: DemoApp(), | |
), | |
), | |
); | |
} | |
// Simple demo app which consists of two containers. The goal is to allow multiple gestures into the arena. | |
// Everything is handled manually with the use of `RawGestureDetector` and a custom `GestureRecognizer`(It extends `TapGestureRecognizer`). | |
// The custom GestureRecognizer, `AllowMultipleGestureRecognizer` is added to the gesture list and creates a `GestureRecognizerFactoryWithHandlers` of type `AllowMultipleGestureRecognizer`. | |
// It creates a gesture recognizer factory with the given callbacks, in this case, an `onTap`. | |
// It listens for an instance of `onTap` then prints text to the console when it is called. Note that the `RawGestureDetector` code is the same for both | |
// containers. The only difference being the text that is printed(Used as a way to identify the widget) | |
class DemoApp extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return RawGestureDetector( | |
gestures: { | |
AllowMultipleGestureRecognizer: GestureRecognizerFactoryWithHandlers< | |
AllowMultipleGestureRecognizer>( | |
() => AllowMultipleGestureRecognizer(), | |
(AllowMultipleGestureRecognizer instance) { | |
instance.onTap = () => print('Episode 4 is best! (parent container) '); | |
}, | |
) | |
}, | |
behavior: HitTestBehavior.opaque, | |
//Parent Container | |
child: Container( | |
color: Colors.blueAccent, | |
child: Center( | |
//Wraps the second container in RawGestureDetector | |
child: RawGestureDetector( | |
gestures: { | |
AllowMultipleGestureRecognizer: | |
GestureRecognizerFactoryWithHandlers< | |
AllowMultipleGestureRecognizer>( | |
() => AllowMultipleGestureRecognizer(), //constructor | |
(AllowMultipleGestureRecognizer instance) { //initializer | |
instance.onTap = () => print('Episode 8 is best! (nested container)'); | |
}, | |
) | |
}, | |
//Creates the nested container within the first. | |
child: Container( | |
color: Colors.yellowAccent, | |
width: 300.0, | |
height: 400.0, | |
), | |
), | |
), | |
), | |
); | |
} | |
} | |
// Custom Gesture Recognizer. | |
// rejectGesture() is overridden. When a gesture is rejected, this is the function that is called. By default, it disposes of the | |
// Recognizer and runs clean up. However we modified it so that instead the Recognizer is disposed of, it is actually manually added. | |
// The result is instead you have one Recognizer winning the Arena, you have two. It is a win-win. | |
class AllowMultipleGestureRecognizer extends TapGestureRecognizer { | |
@override | |
void rejectGesture(int pointer) { | |
acceptGesture(pointer); | |
} | |
} |
@ Nash0x7E2 This is brilliant. Thank you putting this together. Cannot believe how much code have to go in to achieve a simple nested gesture events.
The inner gesture recognizer does not need to be a custom one. This also works:
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
//Main function. The entry point for your Flutter app.
void main() {
runApp(
MaterialApp(
home: Scaffold(
body: DemoApp(),
),
),
);
}
// Simple demo app which consists of two containers. The goal is to allow multiple gestures into the arena.
// Everything is handled manually with the use of `RawGestureDetector` and a custom `GestureRecognizer`(It extends `TapGestureRecognizer`).
// The custom GestureRecognizer, `AllowMultipleGestureRecognizer` is added to the gesture list and creates a `GestureRecognizerFactoryWithHandlers` of type `AllowMultipleGestureRecognizer`.
// It creates a gesture recognizer factory with the given callbacks, in this case, an `onTap`.
// It listens for an instance of `onTap` then prints text to the console when it is called. Note that the `RawGestureDetector` code is the same for both
// containers. The only difference being the text that is printed(Used as a way to identify the widget)
class DemoApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return RawGestureDetector(
gestures: {
AllowMultipleGestureRecognizer: GestureRecognizerFactoryWithHandlers<
AllowMultipleGestureRecognizer>(
() => AllowMultipleGestureRecognizer(),
(AllowMultipleGestureRecognizer instance) {
instance.onTap =
() => print('Episode 4 is best! (parent container) ');
},
)
},
behavior: HitTestBehavior.opaque,
//Parent Container
child: Container(
color: Colors.blueAccent,
child: Center(
//Wraps the second container in RawGestureDetector
child: RawGestureDetector(
gestures: {
TapGestureRecognizer:
GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
() => TapGestureRecognizer(), //constructor
(TapGestureRecognizer instance) {
//initializer
instance.onTap =
() => print('Episode 8 is best! (nested container)');
},
)
},
//Creates the nested container within the first.
child: Container(
color: Colors.yellowAccent,
width: 300.0,
height: 400.0,
),
),
),
),
);
}
}
// Custom Gesture Recognizer.
// rejectGesture() is overridden. When a gesture is rejected, this is the function that is called. By default, it disposes of the
// Recognizer and runs clean up. However we modified it so that instead the Recognizer is disposed of, it is actually manually added.
// The result is instead you have one Recognizer winning the Arena, you have two. It is a win-win.
class AllowMultipleGestureRecognizer extends TapGestureRecognizer {
@override
void rejectGesture(int pointer) {
acceptGesture(pointer);
}
}
Thanks, what if there is a longPress and onTap ?
Thanks, what if there is a longPress and onTap ?
Yeah I want to know too. I have a pageview which has children with pinch controls and sometimes the pageview takes the pinch gesture and tries to change the page. So I need to resolve for drag and scale gestures
Awesome, thanks for sharing it! 🚀
Thanks, what if there is a longPress and onTap ?
Yeah I want to know too. I have a pageview which has children with pinch controls and sometimes the pageview takes the pinch gesture and tries to change the page. So I need to resolve for drag and scale gestures
Just Do this:
Change:
class AllowMultipleGestureRecognizer extends TapGestureRecognizer
to
class AllowMultipleGestureRecognizer extends LongPressGestureRecognizer
!!! DON'T USE THIS !!!
This causes internal crashes in Flutter and upstream has officially decided this usecase is unsupported and will not provide a fix to those crashes: flutter/flutter#82107 (comment)
For anyone that may stumble on this, @jhass is correct, do not use this. I used a different approach in flutter_html
at #695:
class MultipleTapGestureDetector extends InheritedWidget {
final void Function()? onTap;
const MultipleTapGestureDetector({
Key? key,
required Widget child,
required this.onTap,
}) : super(key: key, child: child);
static MultipleTapGestureDetector? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<MultipleTapGestureDetector>();
}
@override
bool updateShouldNotify(MultipleTapGestureDetector oldWidget) => false;
}
and
MultipleTapGestureDetector(
onTap: () {
print("top level tapped");
}
child: Builder(
builder: (context) {
return GestureDetector(
onTap: () {
if (MultipleTapGestureDetector.of(context) != null) {
MultipleTapGestureDetector.of(context)!.onTap?.call();
}
print("bottom level tapped");
},
child: Container(height: 150, color: Colors.red),
)
}
)
);
This way you can access the nearest parent GestureDetector
in the child GestureDetector
and call both onTap
s.
This doesn't use any weird workarounds, DropdownButtonHideUnderline
uses the same approach.
Hi there!
Thank you everyone for sharing that useful information.
I am working on a project where I need to detect any gestures (tap, long-press, drag ...) the user is performing on the screen, without interfering with the UX.
Currently, I am using the approach that @jhass and @tneotia are saying to avoid: Overriding the rejectGesture
method
Here is a gist with a simplified version of what I do: https://gist.github.com/guitoof/0cc362984e8d60b51064bca6672c0bb5
It's very similar to the solution proposed here by @Nash0x7E2 but the major difference is this: the code is split in 2:
- the "parent" widget: in
global_gestures_listener.dart
- the "child" widget: in
main.dart
My project consists in developing the parent widget:global_gestures_listener.dart
which I actually develop in a standalone flutter lib.
The code of the "child" widget is the code written by my clients and any developer that uses my lib on their Flutter app.
Because of this, I don't own the code for the "child" widget and therefore CANNOT modify its code at all.
This constraint makes it impossible to use the approach using an InheritedWidget
as suggested by @tneotia because I won't be able to override the tap methods from EVERY gesture handling widgets of my clients' apps.
Given this constraint, I have 2 questions for you @jhass & @tneotia:
- Could you explain to me why overriding the
rejectGesture
method like I do it is considered "illegal"? - How would you do this in my context: without being able to modify the child widget and without using any illegal code?
Thank you very much for your help.
Let me know if it is not clear enough
Hello everyone,
I'm currently facing the same problem as @guitoof .
I need to log UI events, and send them to server, but changing every callback inside the project is not a good way to handle it.
I initially thought that onTap would generate something like Notifications, that would bubble up to the root (something like HTML DOM click events). Sadly, this assumption wasn't true, and I'm still trying to find a nice solution to listen for every gesture on the app and capture it.
@guitoof, did you find any solution?
Hi @TheManuz ,
I did find a solution to this problem !
The issue actually comes from the fact that in theGesture Arena, only 1 widget can win...
The solution is to use the lower API of the Listener
widget.
It requires that you handle directly PointerEvent
like DownPointerEvent
or UpPointerEvent
... but provided you pass the hitTestBehavior: HitTestBehaviour.transluscent
property, you can handle each pointer events without interfering with any underlying widget that needs it too !
The caveat is that, since Listener is a lower level API (using pointerEvents) you will need to re-implement the behavior of higher level api such as Tap, LongPress ...
I had to do just this for my client and got my inspiration from this widget:
https://github.com/taodo2291/xgesture_flutter/blob/master/lib/gesture_x_detector.dart
You can directly use the gesture_x_detector library but I quickly encountered limitations and preferred recoding my own widget inspired by this one.
I hope it helps you. Let me know if you have further questions
Wow, thanks!
I'll give gesture_x_detector a quick try to see if it's suitable for my need, and then I'll evaluate if I need to rewrite my own.
Great, let me know how it goes 😉
I've added hitTestBehavior: HitTestBehaviour.transluscent
to the XGestureDetector Listener, and finally both events are firing.
Sadly, I've got a problem with the order of firing: apparently, the child fires always before the parent.
My current configuration is to have the parent widget as the one responsible for logging the UI events, and the child is the original button, unchanged.
Since the actual original button triggers a navigation event (also logged), the result is something like this:
child: execute navigationnavigation log event firesparent: onTap log event fires
so I'm logging that the navigation happened before the click, and this is obviously wrong.
Yay, it works! The order is correct, all these tries confused me!
Now I'm up for some cleaning and it'll be done!
Thank you very much for your help @guitoof
Are there any alternative codes for this?
Hi @2p31-1 ,
To achieve what I wanted, I drilled down to use Flutter's lower level: Listener widget. As @TheManuz said, in this case it fires pretty well.
After struggling with this, I decided to make a talk about it to explain what I've understood from the subject. If you want to take a look, maybe that'll clarify some things for you:
https://www.droidcon.com/2023/08/06/understanding-flutters-handling-of-gestures/
Hii @Nash0x7E2m,
I've applied this solution for the session management and now I'm getting error like:
'package:flutter/src/gestures/tap.dart': Failed assertion: line 213 pos 14: '_down == null && _up == null': is not true.
This error happens, when I try to scroll the scrollable widgets and then after not able to detect the instance.onTap also.
Man, you saved my day :). Pretty difficult, should be just a property on GestureDetector to allow multiple wins.
https://stackoverflow.com/questions/58138114/receive-onverticaldragupdate-on-nested-gesturedetectors-in-flutter