如何使 GestureDetector 捕获堆栈内的点击?

Var*_*iak 12 flutter

我正在尝试使 GestureDetector 在堆栈中工作,堆栈顶部有一个容器,但从未调用 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),
          ],
        ),
      ),
    );
  }
Run Code Online (Sandbox Code Playgroud)

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

Var*_*iak 19

好的,我想我自己找到了解决方案。我希望有一个更简单的解决方案,但它对我有用。我遇到的问题是 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;
  }
}

Run Code Online (Sandbox Code Playgroud)


Hos*_*adi 14

这可以通过IgnorePointer.

您可以ContainerIgnorePointer.

Stack(
  children:[
    (...your down widget which should handle the tap...)

    IgnorePointer(
      child: (... your top widget which must be transparent to any tap ...)
    )
  ]
)
Run Code Online (Sandbox Code Playgroud)

然后您的顶部小部件对手势事件变得透明。事件将由 上最顶层的检测器捕获(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)),
          ],
        ),
      ),
    );
  }
Run Code Online (Sandbox Code Playgroud)

  • 但ignorepointer内的小部件现在不处理ontap方法:( (2认同)

Hug*_*o H 7

2021 年 10 月更新flutter > 2.5

受到@Vardiak回答的启发,具有空安全性和框架更改

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

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;
  }
}
Run Code Online (Sandbox Code Playgroud)


fzy*_*cjy 6

受到 @Vardiak 和 @HugoH 的启发,修复了错误(即updateRenderObject添加 - 否则代码有错误)

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

class MultiHitStack extends Stack {
  MultiHitStack({
    super.key,
    super.alignment = AlignmentDirectional.topStart,
    super.textDirection,
    super.fit = StackFit.loose,
    super.clipBehavior = Clip.hardEdge,
    super.children = const <Widget>[],
  });

  @override
  RenderMultiHitStack createRenderObject(BuildContext context) {
    return RenderMultiHitStack(
      alignment: alignment,
      textDirection: textDirection ?? Directionality.maybeOf(context),
      fit: fit,
      clipBehavior: clipBehavior,
    );
  }

  @override
  void updateRenderObject(BuildContext context, RenderMultiHitStack renderObject) {
    renderObject
      ..alignment = alignment
      ..textDirection = textDirection ?? Directionality.maybeOf(context)
      ..fit = fit
      ..clipBehavior = clipBehavior;
  }
}

class RenderMultiHitStack extends RenderStack {
  RenderMultiHitStack({
    super.children,
    super.alignment = AlignmentDirectional.topStart,
    super.textDirection,
    super.fit = StackFit.loose,
    super.clipBehavior = Clip.hardEdge,
  });

  // NOTE MODIFIED FROM [RenderStack.hitTestChildren], i.e. [defaultHitTestChildren]
  @override
  bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
    // NOTE MODIFIED
    var childHit = false;

    RenderBox? child = lastChild;
    while (child != null) {
      // The x, y parameters have the top left of the node's box as the origin.
      final StackParentData childParentData = child.parentData! as StackParentData;
      final bool isHit = result.addWithPaintOffset(
        offset: childParentData.offset,
        position: position,
        hitTest: (BoxHitTestResult result, Offset transformed) {
          assert(transformed == position - childParentData.offset);
          return child!.hitTest(result, position: transformed);
        },
      );

      // NOTE MODIFIED
      // if (isHit) return true;
      childHit |= isHit;

      child = childParentData.previousSibling;
    }

    // NOTE MODIFIED
    return childHit;
    // return false;
  }
}
Run Code Online (Sandbox Code Playgroud)