Use onPan GestureDetector inside a SingleChildScrollView

538

As @PatrickMahomes said this answer (by @Chris) will solve the problem. However, it will only check if the drag is in line with the GestureDetector. So a full solution would be this:

 bool _dragOverMap = false;
  GlobalKey _pointerKey = new GlobalKey();

  _checkDrag(Offset position, bool up) {
    if (!up) {
      // find your widget
      RenderBox box = _pointerKey.currentContext.findRenderObject();

      //get offset
      Offset boxOffset = box.localToGlobal(Offset.zero);

      // check if your pointerdown event is inside the widget (you could do the same for the width, in this case I just used the height)
      if (position.dy > boxOffset.dy &&
          position.dy < boxOffset.dy + box.size.height) {
        // check x dimension aswell
        if (position.dx > boxOffset.dx &&
            position.dx < boxOffset.dx + box.size.width) {
          setState(() {
            _dragOverMap = true;
          });
        }
      }
    } else {
      setState(() {
        _dragOverMap = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(

        title: Text("Scroll Test"),
      ),
      body: new Listener(
        onPointerUp: (ev) {
          _checkDrag(ev.position, true);
        },
        onPointerDown: (ev) {
          _checkDrag(ev.position, false);
        },
        child: ListView(
          // if dragging over your widget, disable scroll, otherwise allow scrolling
          physics:
              _dragOverMap ? NeverScrollableScrollPhysics() : ScrollPhysics(),
          children: [

            ListTile(title: Text("Tile to scroll")),
            Divider(),
              ListTile(title: Text("Tile to scroll")),
            Divider(),
              ListTile(title: Text("Tile to scroll")),
            Divider(),

            // Your widget that you want to prevent to scroll the Listview 
            Container(
              key: _pointerKey, // key for finding the widget
              height: 300,
              width: double.infinity,
              child: FlutterMap(
               // ... just as example, could be anything, in your case use the color picker widget
              ),
            ),
          ],
        ),
      ),
    );
  }
}
Share:
538
Joshlucpoll
Author by

Joshlucpoll

Software Developer 👨🏻‍💻

Updated on December 31, 2022

Comments

  • Joshlucpoll
    Joshlucpoll over 1 year

    Problem

    So I have a GestureDetector widget inside SingleChildScrollView (The scroll view is to accommodate smaller screens). The GestureDetector is listening for pan updates.

    When the SingleChildScrollView is "in use" the GestureDetector cannot receive pan updates as the "dragging" input from the user is forwarded to the SingleChildScrollView.

    What I want

    Make the child GestureDetector have priority over the SingleChildScrollView when dragging on top of the GestureDetector -- but still have functionality of scrolling SingleChildScrollView outside GestureDetector.

    Example

    If you copy/paste this code into dart pad you can see what I mean. When the gradient container is large the SingleChildScrollView is not active -- you are able to drag the blue box and see the updates in the console. However, once you press the switch button the container becomes smaller and the SingleChildScrollView becomes active. You are now no longer able to get pan updates in the console only able to scroll the container.

    Sidenote: It seems that if you drag on the blue box quickly you are able to get drag updates but slowly dragging it just scrolls the container. I'm not sure if that's a bug or a feature but I'm not able to reproduce the same result in my production app.

    import 'package:flutter/material.dart';
    
    final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
          debugShowCheckedModeBanner: false,
          home: Scaffold(
            body: Center(
              child: MyWidget(),
            ),
          ),
        );
      }
    }
    
    class MyWidget extends StatefulWidget {
      const MyWidget({Key? key}) : super(key: key);
    
      @override
      _MyWidgetState createState() => _MyWidgetState();
    }
    
    class _MyWidgetState extends State<MyWidget> {
      bool enabled = false;
    
      @override
      Widget build(BuildContext context) {
        return Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            SizedBox(
              height: enabled ? 200 : 400,
              child: SingleChildScrollView(
                child: Container(
                  decoration: const BoxDecoration(
                    gradient: LinearGradient(
                      begin: Alignment.topLeft,
                      end: Alignment(0.8, 0.0),
                      colors: <Color>[Color(0xffee0000), Color(0xffeeee00)],
                      tileMode: TileMode.repeated,
                    ),
                  ),
                  height: 400,
                  width: 200,
                  child: Center(
                    child: GestureDetector(
                      onPanUpdate: (details) => print(details),
                      child: Container(
                        height: 100,
                        width: 100,
                        color: Colors.blue,
                        child: Center(
                          child: Text(
                            "Drag\nme",
                            textAlign: TextAlign.center,
                          ),
                        ),
                      ),
                    ),
                  ),
                ),
              ),
            ),
            ElevatedButton(
                onPressed: () => setState(() {
                      enabled = !enabled;
                    }),
                child: Text("Switch"))
          ],
        );
      }
    }
    
    • PatrickMahomes
      PatrickMahomes almost 3 years
      does this answer help?
    • Joshlucpoll
      Joshlucpoll almost 3 years
      @PatrickMahomes Thank you -- I bit hacky but seems to do the trick!