View annotations
Add a regular Android View
bound to some Geometry
on top of the Mapbox MapView
using the View Annotations API.
This can be helpful to display an info window when a point is tapped or add a label on top of a linestring or a polygon.
Benefits:
- Adding Android
View
as a view annotation allows to add clickable buttons or any other Android UI elements. - View annotations bound to complex geometries (line, polygon) are positioned dynamically and stay within the camera viewport if possible.
- High rendering performance when using 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:
- Performance may be an issue when adding many view annotations (> 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.
Create a view annotation
To create a view annotation a few things are required:
- An Android view represented as either an XML layout that will be inflated by the view annotation manager or a
View
prepared beforehand. - A
ViewAnnotationManager
instance.
Create a layout
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>
Add a view annotation to 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)
}
)
}
private fun addViewAnnotationToAnnotatedLayerFeature(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 feature with `FEATURE_ID` from layer with `LAYER_ID`
annotatedLayerFeature(LAYER_ID) {
featureId(FEATURE_ID)
}
}
)
}
ViewAnnotationOptions
documentation.Create a view annotation with Jetpack Compose
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.
Define the view annotation content
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
)
}
Add a view annotation to the MapboxMap
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(),
) {
// Insert a ViewAnnotation at specific geo coordinate.
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()
}
// Insert a ViewAnnotation that's associated with a feature from specified layer.
ViewAnnotation(
options = viewAnnotationOptions {
// View annotation is placed at the feature with `FEATURE_ID` from layer with `LAYER_ID`
annotatedLayerFeature(LAYER_ID) {
featureId(FEATURE_ID)
}
}
) {
// Insert the content of the ViewAnnotation
ViewAnnotationContent()
}
}
ViewAnnotationOptions
documentation.Customize 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 selected
property:
viewAnnotationManager.updateViewAnnotation(
existingViewAnnotation,
viewAnnotationOptions {
selected(true)
}
)
Any number of view annotations could be in selected state. The z-index among selected view annotations also respects the order in which they are added.
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 the 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 the layer feature
To attach the view annotation to the layer feature, ViewAnnotationOptions.annotatedFeature
of type AnnotatedLayerFeature
should be used.
Specify the Feature
to attach view annotation with the
feature id and the layer 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.
Below you can find examples of a view annotation attached to a point layer and a line layer.
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 a point annotation by the layer/feature id
This shows marker pop-up window attached to a point with minimal amount of code :
// 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
Attach to a line annotation by the layer/feature id
This shows marker pop-up window attached to a polyline with minimal amount of code:
// add polyline annotation
val annotationPlugin = mapView.annotations
val annotationOptions: PolylineAnnotationOptions = PolylineAnnotationOptions()
.withPoints(listOf(POINT_SRC, POINT_DST))
.withLineWidth(10.0)
val annotationManager = annotationPlugin.createPolylineAnnotationManager(AnnotationConfig(layerId = LAYER_ID))
val annotation = annotationManager.create(annotationOptions)
val viewAnnotationManager = mapView.viewAnnotationManager
// add view annotation attached to the polyline
val viewAnnotation = viewAnnotationManager.addViewAnnotation(
resId = R.layout.item_callout_view,
options = viewAnnotationOptions {
annotatedLayerFeature(LAYER_ID) {
featureId(annotation.id)
}
}
)
Add view annotation anchored to a symbol layer feature