在 SingleChildScrollView 中使用 onPan GestureDetector

Use onPan GestureDetector inside a SingleChildScrollView

问题

所以我在 SingleChildScrollView 中有一个 GestureDetector 小部件(滚动视图是为了适应较小的屏幕)。 GestureDetector 正在侦听平移更新。

当 SingleChildScrollView“正在使用”时,GestureDetector 无法接收平移更新,因为来自用户的“拖动”输入被转发到 SingleChildScrollView。

我想要的

当拖动到 GestureDetector 顶部时,使子 GestureDetector 优先于 SingleChildScrollView -- 但仍然具有在 GestureDetector 外部滚动 SingleChildScrollView 的功能。

例子

如果你把这段代码copy/paste改成dart pad你就能明白我的意思了。当渐变容器很大时,SingleChildScrollView 不活动——您可以拖动蓝色框并在控制台中查看更新。但是,一旦您按下切换按钮,容器就会变小并且 SingleChildScrollView 会变为活动状态。您现在无法再在只能滚动容器的控制台中获取平移更新。

旁注:似乎如果您快速拖动蓝色框,您可以获得拖动更新,但缓慢拖动它只会滚动容器。我不确定这是错误还是功能,但我无法在我的生产应用程序中重现相同的结果。

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

As @PatrickMahomes said this answer (by @Chris) 将解决问题。但是,它只会检查拖动是否与 GestureDetector 一致。所以一个完整的解决方案是这样的:

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