Add view annotations connected to features
Add a ViewAnnotation to a MapView and anchor it to a feature in a symbol layer.
<?xml version="1.0" encoding="utf-8"?><androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"xmlns:app="http://schemas.android.com/apk/res-auto"> <com.mapbox.maps.MapViewandroid:id="@+id/mapView"android:layout_width="match_parent"android:layout_height="match_parent" /> <com.google.android.material.floatingactionbutton.FloatingActionButtonandroid:id="@+id/fab_style_toggle"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_margin="16dp"android:src="@drawable/ic_layers_24dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"tools:backgroundTint="@color/blue"/> <com.google.android.material.floatingactionbutton.FloatingActionButtonandroid:id="@+id/fab_reframe"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_margin="16dp"android:src="@drawable/ic_baseline_refresh_24"app:layout_constraintBottom_toTopOf="@+id/fab_style_toggle"app:layout_constraintEnd_toEndOf="parent"tools:backgroundTint="@color/blue"/> </androidx.constraintlayout.widget.ConstraintLayout>
package com.mapbox.maps.testapp.examples.markersandcallouts import android.annotation.SuppressLintimport android.graphics.Bitmapimport android.graphics.BitmapFactoryimport android.os.Bundleimport android.util.TypedValueimport android.view.Menuimport android.view.MenuItemimport android.view.Viewimport android.view.ViewGroupimport android.widget.Buttonimport android.widget.ImageViewimport android.widget.TextViewimport android.widget.Toastimport androidx.appcompat.app.AppCompatActivityimport androidx.asynclayoutinflater.view.AsyncLayoutInflaterimport com.mapbox.bindgen.Expectedimport com.mapbox.geojson.Featureimport com.mapbox.geojson.FeatureCollectionimport com.mapbox.geojson.Pointimport com.mapbox.maps.*import com.mapbox.maps.extension.style.image.imageimport com.mapbox.maps.extension.style.layers.generated.symbolLayerimport com.mapbox.maps.extension.style.layers.properties.generated.IconAnchorimport com.mapbox.maps.extension.style.sources.generated.GeoJsonSourceimport com.mapbox.maps.extension.style.sources.generated.geoJsonSourceimport com.mapbox.maps.extension.style.sources.generated.rasterDemSourceimport com.mapbox.maps.extension.style.sources.getSourceAsimport com.mapbox.maps.extension.style.styleimport com.mapbox.maps.extension.style.terrain.generated.terrainimport com.mapbox.maps.plugin.gestures.*import com.mapbox.maps.testapp.Rimport com.mapbox.maps.testapp.databinding.ActivityViewAnnotationShowcaseBindingimport com.mapbox.maps.viewannotation.ViewAnnotationManagerimport com.mapbox.maps.viewannotation.ViewAnnotationUpdateModeimport com.mapbox.maps.viewannotation.viewAnnotationOptionsimport java.util.concurrent.CopyOnWriteArrayList /*** Example how to add view annotations to the map.** Specifically view annotations will be associated with marker icons* showcasing how to implement functionality similar to MarkerView from Maps v9.*/class ViewAnnotationShowcaseActivity : AppCompatActivity(), OnMapClickListener, OnMapLongClickListener { private lateinit var mapboxMap: MapboxMapprivate lateinit var viewAnnotationManager: ViewAnnotationManagerprivate val pointList = CopyOnWriteArrayList<Feature>()private var markerId = 0 private var markerWidth = 0private var markerHeight = 0 private val asyncInflater by lazy { AsyncLayoutInflater(this) } override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)val binding = ActivityViewAnnotationShowcaseBinding.inflate(layoutInflater)setContentView(binding.root) viewAnnotationManager = binding.mapView.viewAnnotationManager val bitmap = BitmapFactory.decodeResource(resources, R.drawable.blue_marker_view)markerWidth = bitmap.widthmarkerHeight = bitmap.height mapboxMap = binding.mapView.getMapboxMap().apply {loadStyle(styleExtension = prepareStyle(Style.MAPBOX_STREETS, bitmap)) {addOnMapClickListener(this@ViewAnnotationShowcaseActivity)addOnMapLongClickListener(this@ViewAnnotationShowcaseActivity)binding.fabStyleToggle.setOnClickListener {when (getStyle()?.styleURI) {Style.MAPBOX_STREETS -> loadStyle(prepareStyle(Style.SATELLITE_STREETS, bitmap))Style.SATELLITE_STREETS -> loadStyle(prepareStyle(Style.MAPBOX_STREETS, bitmap))}}Toast.makeText(this@ViewAnnotationShowcaseActivity, STARTUP_TEXT, Toast.LENGTH_LONG).show()}}} override fun onCreateOptionsMenu(menu: Menu): Boolean {menuInflater.inflate(R.menu.menu_view_annotation, menu)return true} override fun onOptionsItemSelected(item: MenuItem): Boolean {return when (item.itemId) {R.id.action_view_annotation_fixed_delay -> {viewAnnotationManager.setViewAnnotationUpdateMode(ViewAnnotationUpdateMode.MAP_FIXED_DELAY)true}R.id.action_view_annotation_map_synchronized -> {viewAnnotationManager.setViewAnnotationUpdateMode(ViewAnnotationUpdateMode.MAP_SYNCHRONIZED)true}else -> super.onOptionsItemSelected(item)}} private fun prepareStyle(styleUri: String, bitmap: Bitmap) = style(styleUri) {+image(BLUE_ICON_ID) {bitmap(bitmap)}+geoJsonSource(SOURCE_ID) {featureCollection(FeatureCollection.fromFeatures(pointList))}if (styleUri == Style.SATELLITE_STREETS) {+rasterDemSource(TERRAIN_SOURCE) {url(TERRAIN_URL_TILE_RESOURCE)}+terrain(TERRAIN_SOURCE)}+symbolLayer(LAYER_ID, SOURCE_ID) {iconImage(BLUE_ICON_ID)iconAnchor(IconAnchor.BOTTOM)iconAllowOverlap(false)}} override fun onMapLongClick(point: Point): Boolean {val markerId = addMarkerAndReturnId(point)addViewAnnotation(point, markerId)return true} override fun onMapClick(point: Point): Boolean {mapboxMap.queryRenderedFeatures(RenderedQueryGeometry(mapboxMap.pixelForCoordinate(point)), RenderedQueryOptions(listOf(LAYER_ID), null)) {onFeatureClicked(it) { feature ->if (feature.id() != null) {viewAnnotationManager.getViewAnnotationByFeatureId(feature.id()!!)?.toggleViewVisibility()}}}return true} private fun onFeatureClicked(expected: Expected<String, List<QueriedFeature>>,onFeatureClicked: (Feature) -> Unit) {if (expected.isValue && expected.value?.size!! > 0) {expected.value?.get(0)?.feature?.let { feature ->onFeatureClicked.invoke(feature)}}} private fun View.toggleViewVisibility() {visibility = if (visibility == View.VISIBLE) View.GONE else View.VISIBLE} private fun addMarkerAndReturnId(point: Point): String {val currentId = "${MARKER_ID_PREFIX}${(markerId++)}"pointList.add(Feature.fromGeometry(point, null, currentId))val featureCollection = FeatureCollection.fromFeatures(pointList)mapboxMap.getStyle { style ->style.getSourceAs<GeoJsonSource>(SOURCE_ID)?.featureCollection(featureCollection)}return currentId} @SuppressLint("SetTextI18n")private fun addViewAnnotation(point: Point, markerId: String) {viewAnnotationManager.addViewAnnotation(resId = R.layout.item_callout_view,options = viewAnnotationOptions {geometry(point)associatedFeatureId(markerId)anchor(ViewAnnotationAnchor.BOTTOM)allowOverlap(false)},asyncInflater = asyncInflater) { viewAnnotation ->viewAnnotation.visibility = View.GONE// calculate offsetY manually taking into account icon height only because of bottom anchoringviewAnnotationManager.updateViewAnnotation(viewAnnotation,viewAnnotationOptions {offsetY(markerHeight)})viewAnnotation.findViewById<TextView>(R.id.textNativeView).text ="lat=%.2f\nlon=%.2f".format(point.latitude(), point.longitude())viewAnnotation.findViewById<ImageView>(R.id.closeNativeView).setOnClickListener { _ ->viewAnnotationManager.removeViewAnnotation(viewAnnotation)}viewAnnotation.findViewById<Button>(R.id.selectButton).setOnClickListener { b ->val button = b as Buttonval isSelected = button.text.toString().equals("SELECT", true)val pxDelta = (if (isSelected) SELECTED_ADD_COEF_DP.dpToPx() else -SELECTED_ADD_COEF_DP.dpToPx()).toInt()button.text = if (isSelected) "DESELECT" else "SELECT"viewAnnotationManager.updateViewAnnotation(viewAnnotation,viewAnnotationOptions {selected(isSelected)})(button.layoutParams as ViewGroup.MarginLayoutParams).apply {bottomMargin += pxDeltarightMargin += pxDeltaleftMargin += pxDelta}button.requestLayout()}}} private fun Float.dpToPx() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,this,this@ViewAnnotationShowcaseActivity.resources.displayMetrics) private companion object {const val BLUE_ICON_ID = "blue"const val SOURCE_ID = "source_id"const val LAYER_ID = "layer_id"const val TERRAIN_SOURCE = "TERRAIN_SOURCE"const val TERRAIN_URL_TILE_RESOURCE = "mapbox://mapbox.mapbox-terrain-dem-v1"const val MARKER_ID_PREFIX = "view_annotation_"const val SELECTED_ADD_COEF_DP: Float = 8fconst val STARTUP_TEXT = "Long click on a map to add a marker and click on a marker to pop-up annotation."}}