Flutter: GestureDetector apparently doesn't receive Drag events when it has a ListView in it
Asked Answered
U

2

14

Am I missing something? The documentation says that events bubble up from the innermost child to the ancestors, but below code will not print "dragged" to the console. It does print "tapped" though. Applying NeverScrollablePhyiscs to the ListView does work, but i want to listen on the event on both levels. Applying HitTestBehavior.translucent to the GestureDetector doesn't change anything.

import "package:flutter/material.dart";

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
    Widget build(BuildContext context) {
      return MaterialApp(
        home: MyHomePage(),
      );
    }
}

class MyHomePage extends StatelessWidget {
  @override
    Widget build(BuildContext context) {
      return Scaffold(
        body: GestureDetector(
          onVerticalDragUpdate: (DragUpdateDetails details) {
            print("dragged");
          },
          onTap: () {
            print("tapped");
          },
          child: ListView.builder(
            itemBuilder: (context, index) {
              return Container(
                padding: EdgeInsets.all(20.0),
                child: Text(
                  "The GestureDetector above me does not react to drag events. Maybe my parent is at fault?"
                )
              );
            },
          )
        )
      );
    }
}
Uird answered 20/10, 2018 at 21:37 Comment(2)
gesture detection are from children to parents. The listview catch drag events before your gesturedetector. It is not possible to have multiple widget listen the same gesture either, at least not easilyVogt
@RémiRousselet Ok, that makes sense. But why then does it work with GridView, which also catches the gesture?Uird
E
19

You can listen for raw pointer events using Listener

      return Scaffold(
            body: Listener(onPointerMove: (opm) {
              print("onPointerMove .. ${opm.position}");
        }, child: ListView.builder(
          itemBuilder: (context, index) {
            return Container(
                padding: EdgeInsets.all(20.0),
                child: Text(
                    "The GestureDetector above me does not react to drag events. Maybe my parent is at fault?"));
          },
        )));
Earwax answered 20/10, 2018 at 22:20 Comment(2)
Thanks alot. Following your answer, I dug up the part of the docs for raw pointer events and now understand that the bubbling applies to them, not to the gesture, am I right? But why does it work then with the GridView inside the GestureDetector, which is also a scrollable?Uird
Thanks, i struggled for 2 weeks. You are my saviorTd
A
0

The Listener widget was too raw for me and it didn't have drag start / end events to capture from its child ListView. There is a NotificationListener that captures whatever is bubbling from any ancestor:

child: NotificationListener(
  onNotification: (ScrollNotification notification) {
    if (notification is ScrollEndNotification) {
      log('NotificationListener: ${notification.dragDetails?.primaryVelocity}');
    }

    return false; // keep bubbling up
  },
  child: ListView(
    children: [for (int i = 0; i < 10; i++) _tile(i)],
  ),
),

Heads up, if the child is not a scrollable widget or it doesn't need to be scrolled (such as an actual ListView but its content doesn't float the screen) then nothing bubbles up to NotificationListener! If you want to capture ALL scroll drag events regardless to child content then you may need to use both GestureDetector (to detect the ones that is not owned by an ancestor) and NotificationListener for the ones that bubbles up in the hierarchy.

GestureDetector(
  // This will trigger if an ancestor doesn't "win" the event
  onVerticalDragEnd: (DragEndDetails details) {
    log('GestureDetector: ${details.primaryVelocity}');
  },
  child: NotificationListener(
    // This will trigger if an ancestor "wins" the event and bubbles 
    onNotification: (ScrollNotification notification) {
      if (notification is ScrollEndNotification) {
        log('NotificationListener: ${notification.dragDetails?.primaryVelocity}');
      }

      return false; // keep bubbling up
    },
    child: ListView(
      children: [for (int i = 0; i < 10; i++) _tile(i)],
    ),
  ),
),

Have a look at the Flutter issue as to why they're behaving like this (which I find ugly to be honest): https://github.com/flutter/flutter/issues/139713

Alfons answered 7/12, 2023 at 23:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.