Search code examples
flutterscrollablegesturedetector

Flutter: Allow Parent and Child to respond to Drag Gesture


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')]
    ),
  ),
);

Solution

  • 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
        )