How do I prevent onTapDown from being triggered on a parent widgets GestureDetector?

╄→尐↘猪︶ㄣ 提交于 2020-02-28 05:57:08

问题


I have a Stack in which several widget can be dragged around. In addition, the container that the Stack is in has a GestureDetector to trigger on onTapDown and onTapUp. I want those onTap events only to be triggered when the user taps outside of the widget in the Stack. I've tried the following code:

class Gestures extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _GesturesState();
}

class _GesturesState extends State<Gestures> {
  Color background;
  Offset pos;

  @override
  void initState() {
    super.initState();
    pos = Offset(10.0, 10.0);
  }

  @override
  Widget build(BuildContext context) => GestureDetector(
    onTapDown: (_) => setState(() => background = Colors.green),
    onTapUp: (_) => setState(() => background = Colors.grey),
    onTapCancel: () => setState(() => background = Colors.grey),
        child: Container(
          color: background,
          child: Stack(
            children: <Widget>[
              Positioned(
                top: pos.dy,
                left: pos.dx,
                child: GestureDetector(
                  behavior: HitTestBehavior.opaque,
                  onPanUpdate: _onPanUpdate,
//                  onTapDown: (_) {},  Doesn't affect the problem
                  child: Container(
                    width: 30.0,
                    height: 30.0,
                    color: Colors.red,
                  ),
                ),
              )
            ],
          ),
        ),
      );

  void _onPanUpdate(DragUpdateDetails details) {
    RenderBox renderBox = context.findRenderObject();
    setState(() {
      pos = renderBox.globalToLocal(details.globalPosition);
    });
  }
}

However, when starting to drag the widget, the onTap of the outermost container is triggered as well, making the background momentarily go green in this case. Settings HitTestBehavior.opaque doesn't seem to work like I'd expect. Neither does adding a handler for onTapDown to the widget in the Stack.

So, how do I prevent onTapDown from being triggered on the outermost GestureDetector when the user interacts with the widget inside of the Stack?

Update:

An even simpler example of the problem I'm encountering:

GestureDetector(
  onTapDown: (_) {
    print("Green");
  },
  child: Container(
    color: Colors.green,
    width: 300.0,
    height: 300.0,
    child: Center(
      child: GestureDetector(
        behavior: HitTestBehavior.opaque,
        onTapDown: (_) {
          print("Red");
        },
        child: Container(
          color: Colors.red,
          width: 50.0,
          height: 50.0,
        ),
      ),
    ),
  ),
);

When I tap and hold the red container, both "Red" and "Green" are printed even though the inner GestureDetector has HitTestBehavior.opaque.


回答1:


In this answer, I'll solve the simpler example you have given. You are creating the following Widget hierarchy:

 - GestureDetector       // green
   - Container
     - Center
       - GestureDetector // red
          - Container

Therefore the red GestureDetector is a child Widget of the green GestureDetector. The green GestureDetector has the default HitTestBehavior: HitTestBehavior.deferToChild. That is why onTapDown is fired for both containers.

Targets that defer to their children receive events within their bounds only if one of their children is hit by the hit test.

Instead, you can use a Stack to build your UI:

 - Stack
   - GestureDetector // green
     - Container
   - GestureDetector // red
     - Container

This structure would result in the follwing sourcecode. It looks the same, but the behavior is the one you desired:

Stack(
  alignment: Alignment.center,
  children: <Widget>[
    GestureDetector(
      onTapDown: (_) {
        print("Green");
      },
      child: Container(
        color: Colors.green,
        width: 300.0,
        height: 300.0,
      ),
    ),
    GestureDetector(
      onTapDown: (_) {
        print("Red");
      },
      child: Container(
        color: Colors.red,
        width: 50.0,
        height: 50.0,
      ),
    )
  ],
)



回答2:


As explained by one of the Flutter devs: https://github.com/flutter/flutter/issues/18450#issuecomment-397372078

opaque just means the entire size of the gesture detector is the hit region, it doesn't stop the child from being hit. To block hits on children use an IgnorePointer or AbsorbPointer.

You'll have to add some kind of condition to wrap your child Widget with IgnorePointer, that way your top GestureDetector will absorb the taps.

In your example, add IgnorePointer between these Widgets:

GestureDetector(
  onTapDown: (_) {
    print("Green");
  },
  child: IgnorePointer(
    child: Container(
      color: Colors.green,
      width: 300.0,
      height: 300.0,
      child: Center(
        child: GestureDetector(
          behavior: HitTestBehavior.opaque,
          onTapDown: (_) {
            print("Red");
          },
          child: Container(
            color: Colors.red,
            width: 50.0,
            height: 50.0,
          ),
        ),
      ),
    ),
  ),
);


来源:https://stackoverflow.com/questions/53231796/how-do-i-prevent-ontapdown-from-being-triggered-on-a-parent-widgets-gesturedetec

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!