View annotations
View annotations are Android View
s that the SDK draws on top of a Mapbox MapView
. They can be bound to a Geometry
or to a feature rendered on a Layer. This can be helpful for achieving common patterns used in maps in mobile applications, such as showing an information window when tapping a point of interest (POI), or providing estimated time of arrival (ETA) for navigation purposes along a route.
The following layer types are supported for binding a view annotation to a feature:
If you need basic pin-style markers in Jetpack Compose, consider using Markers instead. Markers provide a quick way to add default-styled markers with minimal code. Use View Annotations when you need completely custom UI elements or complex layouts.
Benefits:
- Straight-forward API that allows adding an Android
View
as a view annotation, integrating with your existing Android UI code. - High rendering performance when using a reasonable number of views (generally below 100, but may vary based on the view's content and the user's device model).
- Wide number of options including allowing overlap between view annotations, selected state, connecting to a map feature, and more.
Limitations:
- Inefficient and poor performance when adding many features (> 250) to the map if allow overlap is turned on.
- Low-level API that requires adding code on the user's end for advanced cases.
- No built-in support for clustering
Create a view annotation
Start by creating a new layout that contains the contents of the view annotation. This can include anything from text to images to interactive elements.
For example, to create a minimal layout containing text:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="100dp"
android:layout_height="60dp"
android:background="@android:color/white"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:id="@+id/annotation"
android:text="hello world"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="20sp"
android:padding="3dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
To add the view annotation on top of the MapView
, start by getting a ViewAnnotationManager
instance:
val viewAnnotationManager = binding.mapView.viewAnnotationManager
Then use the manager's addViewAnnotation
method to create the view annotation. Three overloaded methods are available:
- Taking a
View
that is already inflated. - Taking an XML identifier
resId
and returning a synchronously inflatedView
. - Taking an XML identifier
resId
and returning an inflatedView
inasyncInflateCallback
. If using this method, you must addasync inflater dependency
to your project explicitly (any1.x.x
version should work).
All methods above also require ViewAnnotationOptions
with at least annotatedFeature
field specified.
AnnotatedFeature
can be either a direct Geometry
instance or a LayerFeature
.
The latter allows to bind the view annotation to the specific feature on the map, and to update the view annotation with the feature automatically.
ViewAnnotationOptions.geometry
and ViewAnnotationOptions.annotatedLayerFeature
extension functions are available in Kotlin to simplify the annotatedFeature
setting.
You can also specify other options for the view annotation within viewAnnotationOptions
.
private fun addViewAnnotationToPoint(point: Point) {
// Define the view annotation
val viewAnnotation = viewAnnotationManager.addViewAnnotation(
// Specify the layout resource id
resId = R.layout.annotation_view,
// Set any view annotation options
options = viewAnnotationOptions {
// View annotation is placed at the specific geo coordinate
geometry(point)
}
)
}
To create and add a view annotation to the map in Jetpack Compose, you will need to insert the ViewAnnotation
composable function to the content of MapboxMap
composable function, and define your view annotation content as a Composable
and place it as the content of the ViewAnnotation
composable function.
First, define the ViewAnnotation
content using composable function, for example, to create a minimal Text composable function:
// Define the content of the ViewAnnotation, for example create a minimal Text composable function:
@Composable
public fun ViewAnnotationContent() {
Text(
text = "Hello world",
modifier = Modifier
.padding(3.dp)
.width(100.dp)
.height(60.dp)
.background(
Color.White
),
textAlign = TextAlign.Center,
fontSize = 20.sp
)
}
You can add ViewAnnotation
composable function with ViewAnnotationOptions
to the content of MapboxMap
composable function to insert the view annotation to the map. Note that ViewAnnotationOptions
need to be provided with at least annotatedFeature
field specified. AnnotatedFeature
can be either a direct Geometry
instance or a LayerFeature
.
The latter allows to bind the view annotation to the specific feature on the map, and to update the view annotation with the feature automatically.
ViewAnnotationOptions.geometry
and ViewAnnotationOptions.annotatedLayerFeature
extension functions are available to simplify setting annotatedFeature
.
You can also specify other options for the view annotation within viewAnnotationOptions
.
MapboxMap(
Modifier.fillMaxSize()
) {
ViewAnnotation(
options = viewAnnotationOptions {
// View annotation is placed at the specific geo coordinate
geometry(Point.fromLngLat(18.06, 59.31))
}
) {
// Insert the content of the ViewAnnotation
ViewAnnotationContent()
}
}
There are many more options available when adding or updating the view
annotation. See the complete list of options in
theViewAnnotationOptions
documentation.
Customize the appearance
Specify order
The z-index of view annotations is based on the order in which they are added. This allows you to control which view is visible when multiple views intersect on the screen.
To bring a view annotation on top of others regardless of the order in which it was added, use the priority
property:
// specify the priority when inserting a new view annotation
val newViewAnnotation = viewAnnotationManager.addViewAnnotation(
resId = R.layout.some_layout,
options = viewAnnotationOptions {
geometry(point)
priority(10)
}
)
// update an existing view annotation's priority
viewAnnotationManager.updateViewAnnotation(
existingViewAnnotation,
viewAnnotationOptions {
priority(10)
}
)
Control anchor
To update the anchor position and the offset of the view annotation you can specify single or multiple anchors. When you set multiple anchors, the system uses the first one that fits the view annotation within the viewport:
// set single anchor
viewAnnotationManager.updateViewAnnotation(
existingViewAnnotation,
viewAnnotationOptions {
annotationAnchor {
// bottom of the view annotation is placed on the geometry
anchor(ViewAnnotationAnchor.BOTTOM)
offsetX(-10.0)
offsetY(20.0)
}
}
)
// set multiple anchors
viewAnnotationManager.updateViewAnnotation(
existingViewAnnotation,
viewAnnotationOptions {
annotationAnchors(
{
anchor(ViewAnnotationAnchor.TOP_RIGHT)
},
{
anchor(ViewAnnotationAnchor.TOP_LEFT)
},
{
anchor(ViewAnnotationAnchor.BOTTOM_RIGHT)
},
)
}
)
Handle visibility
When a view annotation is added or updated, visibility can be specified explicitly:
viewAnnotationManager.addViewAnnotation(
R.layout.some_layout,
viewAnnotationOptions {
geometry(point)
// hide view annotation initially, even if the android view is visible
visible(false)
}
)
For most use cases there is no need to specify visibility
for viewAnnotationOptions
explicitly.
Instead, you can update the visibility of the actual view, or guard the ViewAnnotation
composable function with a condition:
val view = viewAnnotationManager.addViewAnnotation(
// Inflated view is visible by default
R.layout.some_layout,
viewAnnotationOptions {
// Do not set visibility explicitly
geometry(point)
}
)
// Simply hide the view when needed
view.visibility = View.GONE
Common uses
The flexibility of view annotations means that there are countless ways to use this feature, but there are a few common uses outlined below to help you get started.
Display on map click
You can add view annotations when a user interacts with the map.
When the user clicks on the map, you can get information from the map camera, position (like in the example linked below),
or data in the map to determine the contents of the view annotation using one of MapboxMap
's map feature query methods.
Add view annotation to the map
Show view annotations on a map
Attach to a geometry
You can tie a view annotation to a geometry, for example, a line string, this allows the annotation to stay visible on screen along with the visible part of the line string.
Dynamic view annotations
When a view annotation is added to the complex geometry (for example, line or polygon), it becomes dynamic and is displayed at some coordinate belonging to the geometry. It updates its position automatically if the camera viewport changes and the view annotation is going out of the visible scope.
Add several dynamic view annotations to line and polygon features
Add several dynamic view annotations to line and polygon features
Attach to an existing map feature
You can tie a view annotation's visibility to another feature on the map using the layer/feature id. For example, you can position a ViewAnnotation
relative to a point feature in a SymbolLayer
to make a marker pop-up. You could also use a line or polygon feature to show additional information about that feature when it is visible.
To attach the view annotation to the layer feature, use ViewAnnotationOptions.annotatedFeature
and specify the feature to use by its layer id and
feature id.
The visibility of the feature will then determine the visibility of the view annotation. For example, if the feature collides with another feature, the associated view annotation will also disappear.
// add point annotation
val annotationPlugin = mapView.annotations
val pointAnnotationOptions: PointAnnotationOptions = PointAnnotationOptions()
.withPoint(POINT)
.withIconImage(iconBitmap)
.withIconAnchor(IconAnchor.BOTTOM)
val pointAnnotationManager = annotationPlugin.createPointAnnotationManager(AnnotationConfig(layerId = LAYER_ID))
val pointAnnotation = pointAnnotationManager.create(pointAnnotationOptions)
val viewAnnotationManager = mapView.viewAnnotationManager
// add view annotation attached to the point
val viewAnnotation = viewAnnotationManager.addViewAnnotation(
resId = R.layout.item_callout_view,
options = viewAnnotationOptions {
annotatedLayerFeature(LAYER_ID) {
featureId(annotation.id)
}
}
)
Add view annotation anchored to a point annotation and update its position when point annotation is dragging
While the View Annotation API does not come with built-in logic for handling opening and closing a callout attached to a symbol layer feature when the feature is click, you can see one possible way to implement that functionality in the View annotations: advanced example.
Add view annotation anchored to a symbol layer feature