I'm working on an app where I display markers over a map like so:
The way it works is markers are rendered "over" the Map widget as Stack
. My problem is that currently, the markers 'absorbs' the gestures used to control the map underneath (if the gesture starts on the marker).
I was therefore wondering, is there a way to pass through all gestures events between two widgets in a stack? Ideally, the marker would ignore (and pass through) all events except onTap
(as I still want to be able to click on the markers).
Here my specific tree:
Cheers!
When a widget that is (visually) on top of another widget in the same stack is hit, the stack will stop any further hit testing. So, in your case, the second child of the stack containing the GoogleMap
widget must be made to report that it is not hit, so the stack will give GoogleMap
a chance to react to pointer events. IgnorePointer
can do that, however that widget will also not hit test its child, so its child gesture detectors will never be involved in any gesture. In simple cases, that can be worked-around by swapping the order of IgnorePointer
and GestureDetector
while setting the latter's behavior
property to HitTestBehaviour.translucent
. For example:
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) => MaterialApp(
home: Stack(
fit: StackFit.expand,
children: [
GestureDetector(
onDoubleTap: () => print("double red"),
child: Container(color: Colors.red),
),
Positioned(
top: 100,
left: 100,
right: 100,
bottom: 100,
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () => print("green"),
child: IgnorePointer(
child: Container(color: Colors.green),
),
),
),
],
),
);
}
Your case is more complicated though. A more generic approach would be to create a new widget like IgnorePointer
(let's call it TransparentPointer
) that can act to it parent as if it is never hit, while still doing hit testing on its child. Here I've copied IgnorePointer
and changed the behavior in that way (the only change with respect to RenderIgnorePointer
is in RenderTransparentPointer.hitTest
):
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) => MaterialApp(
home: Stack(
fit: StackFit.expand,
children: [
GestureDetector(
onDoubleTap: () => print("double red"),
child: Container(color: Colors.red),
),
TransparentPointer(
transparent: true,
child: Stack(
children: [
Positioned(
top: 100,
left: 100,
right: 100,
bottom: 100,
child: GestureDetector(
onTap: () => print("green"),
child: Container(color: Colors.green),
),
)
],
),
),
],
),
);
}
class TransparentPointer extends SingleChildRenderObjectWidget {
const TransparentPointer({
Key key,
this.transparent = true,
Widget child,
}) : assert(transparent != null),
super(key: key, child: child);
final bool transparent;
@override
RenderTransparentPointer createRenderObject(BuildContext context) {
return RenderTransparentPointer(
transparent: transparent,
);
}
@override
void updateRenderObject(BuildContext context, RenderTransparentPointer renderObject) {
renderObject
..transparent = transparent;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<bool>('transparent', transparent));
}
}
class RenderTransparentPointer extends RenderProxyBox {
RenderTransparentPointer({
RenderBox child,
bool transparent = true,
}) : _transparent = transparent,
super(child) {
assert(_transparent != null);
}
bool get transparent => _transparent;
bool _transparent;
set transparent(bool value) {
assert(value != null);
if (value == _transparent) return;
_transparent = value;
}
@override
bool hitTest(BoxHitTestResult result, {@required Offset position}) {
// forward hits to our child:
final hit = super.hitTest(result, position: position);
// but report to our parent that we are not hit when `transparent` is true:
return !transparent && hit;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<bool>('transparent', transparent));
}
}
I have published this code as a small package: https://pub.dev/packages/transparent_pointer
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With