The behaviour of hit testing can be confusing at first. These rules define how hit testing usually behaves:
Gesture callbacks in MapOptions
are always invoked, no matter what is within the layers or the result of hitTest
s in those layers, with the exception of custom defined hit test behaviours (not those layers that support interactivity, see Layer Interactivity), such as applying GestureDetector
s around Marker.child
ren
GestureDetector
s absorb hit tests, and so corresponding callbacks in MapOptions
will not be invoked if they are defined/invoked in the GestureDetector
.
Workarounds to resolve this are discussed below.
Hit testing is always* performed on the interactable layers (see Layer Interactivity) even if they have not been set-up for interactivity: hit testing != interactivity
Non-interactable layers (such as Overlay Image Layer) have no defined hitTest
, and behaviour is situation dependent
A successful hit test (true
) from an interactable layer will prevent hit testing on layers below it in the children
stack
To change this behviour, make use of these three widgets, wrapping them around layers when and as necessary:
TranslucentPointer
: a general purpose 'widget' included with flutter_map that allows the child to hit test as normal, but also allows widgets beneath it to hit test as normal, both seperately
For example, a marker with a GestureDetector
child that detects taps beneath a Polyline
will not detect a tap, no matter if the PolylineLayer
has a defined hitNotifier
or the Polyline
has a defined hitValue
. A defined onTap
callback in MapOptions
would be called however. If the Marker
were no longer obscured by the Polyline
, it's onTap
callback would be fired instead of the one defined in MapOptions
.
However, this behaviour could be changed by wrapping the PolylineLayer
with a TranslucentPointer
. This would allow interacitivity to function as normal, but also allow the Marker
beneath to have it's onTap
callback fired. Further wrapping another TransclucentPointer
around the MarkerLayer
would allow all 3 detections to function.
Layer interactivity is different to map interactivity. See Interaction Options to control map interactivity.
For information about how hit testing behaves in flutter_map, see Hit Testing Behaviour.
It is important to note that hit testing != interactivity, and hit testing is always executed on interactable layers by default.
The following layers are interactable - they have specialised hitTest
ers and support external hit detection:
These all follow roughly the same pattern to setup hit detection/interactivity, and there's three or four easy steps to setting it up.
Direct callbacks, such as onTap,
aren't provided on layers or individual elements, to maximize flexibility.
Pass a LayerHitNotifier
to the hitNotifier
parameter of the layer. The LayerHitNotifier
should be created as a ValueNotifier
defaulting to null
, but strongly typed to LayerHitNotifier
.
This notifier will be notified whenever a hit test occurs on the layer, with a LayerHitResult
when an element (such as a Polyline
or Polygon
) within the layer is hit, and with null
when an element is not hit (but the layer is).
It is possible to listen to the notifier directly with addListener
, if you want to handle all hit events (including, for example, hover events).
However, most use cases just need to handle particular gestures (such as taps). This can be done with a wrapper widget to 'filter' the events appropriately: #id-3.-gesture-detection.
hitValue
To ElementsTo identify which particular element was hit (which will be useful when handling the hit events in later steps), supported elements have a hitValue
property.
This can be set to any object, but if one layer contains all the same type, type casting can be avoided (if the type is also specified in the LayerHitNotifier
's type argument).
The equality of the element depends on the equality of the hitValue
.
Therefore, any object passed to the hitValue
should have a valid and useful equality method.
Objects such as records do this behind the scenes, and can be a good choice to store small amounts of uncomplicated data alongside the element.
To only handle certain hits based on the type of gesture the user performed (such as a tap), wrap the layer with a gesture/hit responsive widget, such as GestureDetector
or MouseRegion
.
These widgets are smart enough to delegate whether they detect a hit (and therefore whether they can detect a gesture) to the child - although HitTestBehavior.deferToChild
may be required for some widgets to enable this functionality.
This means the layer can report whether it had any form of hit, and the handler widget can detect whether the gesture performed on it actually triggered a hit on the layer below.
Once a LayerHitResult
object is obtained, through the hit notifier, you can retrieve:
hitValues
: the hitValue
s of all elements that were hit, ordered by their corresponding element, first-to-last, visually top-to-bottom
coordinate
: the geographic coordinate of the hit location (which may not lie on any element)
point
: the screen point of the hit location
If all the hitValue
s in a layer are of the same type, and the created hit notifier specifies that type in the type argument, typing is preserved all the way to retrieval.
Because the HitNotifier
is a special type of ValueNotifier
, it can be both listened to (like a Stream
), and its value instantly retrieved (like a normal variable).
Therefore, there are two ways to retrieve a LayerHitResult
(or null
) from the notifier:
Using .value
to instantly retrieve the value
This is usually done within a gesture handler, such as GestureDetector.onTap
, as demonstrated below.
Adding a listener (.addListener
) to retrieve all hit results
This is useful where you want to apply some custom/advanced filtering to the values, and is not a typical usecase.