Flutter: Allow Parent and Child to respond to Drag Gesture - flutter

In Flutter, is there a way to allow both a parent and a child to respond to a Drag Gesture and toggle between which one is receiving it mid-drag.
I have a Widget that updates its size using onVerticalDragUpdate from a GestureDetector Widget. Its child is a ListView wrapped in IgnorePointer. When the parent is the correct size I set the state to prevent the parent from responding to gestures and set ignore to false for the child to allow it to scroll.
Whilst this works, the user has to lift their finger from the screen and scroll again for the child to begin scrolling. Is there a way to achieve this with the same gesture so that if the user is still dragging and the parent reaches the correct size, the child begins to scroll instead all without having to lift the finger.
Here is a simplified example.
final ignorePointer = useState<bool>(true); // hook state
double desiredSize = 100;
GestureDetector(
onVerticalDragUpdate: ignorePointer ? (details){
if((details.globalPosition.dy / desiredSize) >= 1){
ignorePointer.value = false;
}
} : null,
child: IgnorePointer(
ignoring: ignorePointer.value,
child: ListView(
children:[for(int i = 0; i < 100; i++) Text('Boo $i')]
),
),
);

The way that Gestures and other pointer events work is that the Gesture furthest down the stack that can receive an input, will receive an input. Whilst IgnorePointer can prevent a child element receiving the input, the GestureDetector won't 'let go' of its 'win' as the receiving input until that gesture comes to an end. So changing the state of IgnorePointer mid-drag will change the state but not undo the win of GestureDetector as the receiving input until that Gesture ends.
What is needed here is a way for both the ListView and the GestureDetector to receive the user input. Then with logic decide which one (or both) is going to act on the user input.
Therefore you need a Widget that can register the user input without blocking other Widgets also receiving user input.
There's a Widget for that: Listener.
Behind the scenes GestureDetector engages in a little battle in something called the GestureArena that decides which Gesture wins in any battle of multiple gestures or multiple elements that could receive gestures. Listener does not engage in this battle.
So using Listener is the answer as you will now be able to receive all events received by any user input be it pointerDown, pointerUp, pointerMove etc.. etc.. regardless of anything else (like a ListView) that may also be directly responding to user inputs. You then need to decide what you do with those events.
Listener(
onPointerMove: (e) => // your logic
child: // your scrollable widget
)

Related

Flutter how to requestFocus for a FocusNode?

I'm trying to give focus to a FocusNode on a tap. I can do this with a GestureDetector's onTap callback but I am running into an issue of propagation in nested GestureDetectors as noted in the documents:
Given a parent GestureDetector with an onTap callback, and a child GestureDetector that also defines an onTap callback, when the inner GestureDetector is tapped, both GestureDetectors send a GestureRecognizer into the gesture arena. This is because the pointer coordinates are within the bounds of both GestureDetectors. The child GestureDetector wins in this scenario because it was the first to enter the arena, resolving as first come, first served. The child onTap is called, and the parent's is not as the gesture has been consumed.
This causes an issue for me if I have a deep section of a window to be considered "in focus" such as a row in a DataTable that will listen for certain shortcuts, like the delete key, but I also want other widgets which are higher up in the window to listen for a ESC or Enter key.
If I use a GestureDetector to request focus then it is consumed. Is there some other way to requestFocus() instead of a GestureDector so that I'm not trapping myself?

How to prevent children from getting focus when using keyboard events

This question is related to this one
Widgets inside a closed section still getting the focus
After trying with !_isOpen I tried with fixed value TRUE :
AbsorbPointer( absorbing: `TRUE`, child: content )
And I figure that the AbsorbPointer handle only the mouse events not the keyboard event, so thats why the Widgets still continue to get the Focus,
So the question is How to stop also the keyboard events RawKeyboardListener for the children ?

Is there a way to disable GestureArena in flutter?

I want to disable GestureArena which decides the preference of gestures,
is there any possible way to do this?
About Gesture disambiguation
you can try an IgnorePointer widget or an AbsorbPointer widget.
IgnorePointer prevents its children from receiving pointer events but is itself invisible to hit testing.
AbsorbPointer prevents its subtree from receiving pointer events by terminating hit testing at itself.

How to restrict dragging some elements in Flutter's RerderableListView

I'd like some elements in RerderableListView to be non-draggable. I know I can do nothing in the onReorder function, but I want to deny start dragging.
How to achieve that?
You can wrap the elements with AbsorbPointer or IgnorePointer.
AbsorbPointer class
A widget that absorbs pointers during hit testing.
When absorbing is true, this widget prevents its subtree from
receiving pointer events by terminating hit testing at itself. It
still consumes space during layout and paints its child as usual. It
just prevents its children from being the target of located events,
because it returns true from RenderBox.hitTest.
IgnorePointer class
A widget that is invisible during hit testing.
When ignoring is true, this widget (and its subtree) is invisible to
hit testing. It still consumes space during layout and paints its
child as usual. It just cannot be the target of located events,
because it returns false from RenderBox.hitTest.
I have wrapped widgets I want to be non-draggable with GestureDetector and set empty handler onLongPress: (){}.

InkWell effect starts delayed if using onDoubleTap; want to trigger that as soon as the widget gets touched

If you are using onTap & onDoubleTap side by side with an InkWell, then a single tap gets delayed (300ms). I know the time is needed for the recognition of a double tap but also the effect is delayed, and that's a bad user interaction feeling.
What I've tried:
I found out, that the InkWell effect is startet as soon as any tap event callback gets called. If I use onTap alone, the callback and the effect is started instantly on the first touch; if I use onTap and onDoubleTap, the effect gets delayed.
I also tried using onTapDown, but this also gets delayed (possilbe Bug?)
child: InkWell(
onTap: () { print("Tap"); }, // gets delayed 300ms
onDoubleTap: () { print("Double Tap"); },
onTapDown: (x) { print("Tap Down"); } // gets delayed 300ms
}
So my question:
Is it anyway possible to change the InkWell (GestureDetector) to start the effect instantly. I think the solution could be, to change the onTapDown behavior; this should be called immediately if the user touches the widget.
I've now found a simple solution. I am only using the onTap function of the InkWell and have written the onDoubleTap algorithm my self. So the splash effect runs immediately because the original onDoubleTap isn't used. The tap function itself gets delayed (as it also would have been by using the original onDoubleTap function).
Pup.dev package
I've written a pub.dev package called InkWellSplash. Additional to the splash feature, you can now adjust the maximal time between two taps to recognize a double tap.
I'm pretty sure this package comes in handy and is useful for several developer
Links
Pub.dev - InklWellSplash
GitHub - InkWellSplash
Interactive Example