【问题标题】:How to make a GestureDetector capture taps inside a Stack?如何让 GestureDetector 捕获堆栈内的点击?
【发布时间】:2019-08-12 18:56:48
【问题描述】:

我正在尝试让 GestureDetector 在 Stack 内工作,其顶部有一个 Container,但从未调用过 onTap 回调。

如您所见,即使使用 HitTestBehavior.translucent 也无法使用

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Stack(
          children: <Widget>[
            GestureDetector(
              behavior: HitTestBehavior.translucent,
              onTap: () {
                print('tap');
              },
              child: Container(color: Colors.blue),
            ),
            Container(color: Colors.white),
          ],
        ),
      ),
    );
  }

我知道我想在另一个小部件下方捕获点击事件可能很奇怪,但在我的实际情况中,顶部的小部件是透明的,有时有渐变。

【问题讨论】:

    标签: flutter


    【解决方案1】:

    好的,伙计们,我想我自己找到了解决方案。我希望存在一个更简单的解决方案,但它适用于我的使用。我遇到的问题是 Stack 小部件没有将命中测试传递给所有孩子,而只有第一个被命中的孩子。我所做的是重写了 Stack 的 RenderBox 使用的命中检测算法。我真的不打算走这么远,我仍在等待更好的答案。这是我的代码,使用风险自负:

    class CustomStack extends Stack {
      CustomStack({children}) : super(children: children);
    
      @override
      CustomRenderStack createRenderObject(BuildContext context) {
        return CustomRenderStack(
          alignment: alignment,
          textDirection: textDirection ?? Directionality.of(context),
          fit: fit,
          overflow: overflow,
        );
      }
    }
    
    class CustomRenderStack extends RenderStack {
      CustomRenderStack({alignment, textDirection, fit, overflow})
          : super(
                alignment: alignment,
                textDirection: textDirection,
                fit: fit,
                overflow: overflow);
    
      @override
      bool hitTestChildren(BoxHitTestResult result, {Offset position}) {
        var stackHit = false;
    
        final children = getChildrenAsList();
    
        for (var child in children) {
          final StackParentData childParentData = child.parentData;
    
          final childHit = result.addWithPaintOffset(
            offset: childParentData.offset,
            position: position,
            hitTest: (BoxHitTestResult result, Offset transformed) {
              assert(transformed == position - childParentData.offset);
              return child.hitTest(result, position: transformed);
            },
          );
    
          if (childHit) stackHit = true;
        }
    
        return stackHit;
      }
    }
    
    

    【讨论】:

    • 你是个天才!谢啦。我想在带有 Stack 的全屏容器上方创建一个透明的 AppBar,但由于 AppBar 也是全屏呈现的,因此它捕获了点击并且底层元素没有响应。您的 CustomStack 成功了!
    • 欢迎您!真的很遗憾,我们必须通过这个才能让它工作,我真的认为Flutter使用的命中检测方法应该重新设计,在CSS中做这些事情要简单得多......
    • 这里的用例相同,如果没有这个解决方案,我什至无法猜测我会投入多少小时。如果可以的话,我会投票十次。谢谢你。
    • 效果很好!谢谢!在列表视图下有一个按钮仍然可以点击,这是一项多么艰巨的任务。
    • 谢谢!你认为让 GestureDetector 不吸收命中测试也有意义吗? IE。 onTap() { print('tap'); return true; } // continue hitTests
    【解决方案2】:

    这可以通过IgnorePointer解决。

    您可以用IgnorePointer 包裹您的顶部小部件(在本例中为白色的Container)。

    Stack(
      children:[
        (...your down widget which should handle the tap...)
    
        IgnorePointer(
          child: (... your top widget which must be transparent to any tap ...)
        )
      ]
    )
    

    然后您的顶部小部件对手势事件变得透明。事件将由Stack(如果有)上最高的检测器捕获。

    例如,您可以对代码进行少量更改以达到您想要的结果,如下所示:

    @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: SafeArea(
            child: Stack(
              children: <Widget>[
                GestureDetector(
                  behavior: HitTestBehavior.translucent,
                  onTap: () {
                    print('tap');
                  },
                  child: Container(color: Colors.blue),
                ),
                IgnorePointer(ignoring:true,child:Container(color: Colors.white)),
              ],
            ),
          ),
        );
      }
    

    【讨论】:

    • 真的帮了我。
    • 工作保护!!!!!
    【解决方案3】:

    2021 年 10 月更新flutter &gt; 2.5

    灵感来自 @Vardiakanswer,具有空安全性和框架更改

    用这个CustomStack 小部件替换您的Stack

    class CustomStack extends Stack {
      CustomStack({children}) : super(children: children);
    
      @override
      CustomRenderStack createRenderObject(BuildContext context) {
        return CustomRenderStack(
          alignment: alignment,
          textDirection: textDirection ?? Directionality.of(context),
          fit: fit,
        );
      }
    }
    
    class CustomRenderStack extends RenderStack {
      CustomRenderStack({alignment, textDirection, fit, overflow})
          : super(alignment: alignment, textDirection: textDirection, fit: fit);
    
      @override
      bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
        var stackHit = false;
    
        final children = getChildrenAsList();
    
        for (var child in children) {
          final StackParentData childParentData =
              child.parentData as StackParentData;
    
          final childHit = result.addWithPaintOffset(
            offset: childParentData.offset,
            position: position,
            hitTest: (BoxHitTestResult result, Offset transformed) {
              assert(transformed == position - childParentData.offset);
              return child.hitTest(result, position: transformed);
            },
          );
    
          if (childHit) stackHit = true;
        }
    
        return stackHit;
      }
    }
    

    【讨论】:

    • 我的 CustomStack 仍然有效,但我真的觉得你可以用 IgnorePointer 做同样的事情。我亲自重构了我的代码以使用它。
    【解决方案4】:

    您通过完成蓝色容器制作了一个容器(白色),因此当您触摸它时,如果您想管理它(白色容器)上的触摸,您将放弃它(白色容器)在上面也放一个手势检测器。

    一种简单的方法来测试您将白色容器与蓝色容器的位置颠倒的问题,您将看到“点击”发生

    这很好用:

    Scaffold(
      body: Container(
        child: Stack(
          children: <Widget>[
             GestureDetector(
              behavior: HitTestBehavior.translucent,
              onTap: () {
                print('tap on white ');
              },
              child:
            Container(color: Colors.white)),
    
            GestureDetector(
              behavior: HitTestBehavior.translucent,
              onTap: () {
                print('tap on blue');
              },
              child: 
              SizedBox(
                height: 500,
                child: Container(color: Colors.blue),
              )
            ),
          ],
        ),
      ),
    );
    

    【讨论】:

    • 正如我所说,这只是一个复杂问题的简化。我不能把手势检测器放在上层,因为那里有很多复杂的手势。我想要的是检测第二层的点击。
    猜你喜欢
    • 2012-03-02
    • 2020-12-13
    • 2023-01-10
    • 2016-01-23
    • 2019-01-18
    • 2021-10-24
    • 2022-01-23
    • 1970-01-01
    • 2014-04-23
    相关资源
    最近更新 更多