Custom Map Styling
This tutorial shows how to customize the appearance of your MapMetrics Android map — switching styles, modifying layers at runtime, and applying light/dark themes.
Prerequisites
- Completed the Getting Started Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
Switch Between Map Styles
Let users choose a map style at runtime:
kotlin
import android.os.Bundle
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
import org.maplibre.android.camera.CameraPosition
import org.maplibre.android.geometry.LatLng
import org.maplibre.android.maps.MapView
import org.maplibre.android.maps.MapMetricsMap
import org.maplibre.android.maps.Style
class CustomStylingActivity : AppCompatActivity() {
private lateinit var mapView: MapView
private lateinit var map: MapMetricsMap
private val styles = mapOf(
"Streets" to "https://gateway.mapmetrics.org/styles/STREETS_STYLE_ID?token=YOUR_API_KEY",
"Dark" to "https://gateway.mapmetrics.org/styles/DARK_STYLE_ID?token=YOUR_API_KEY",
"Satellite" to "https://gateway.mapmetrics.org/styles/SATELLITE_STYLE_ID?token=YOUR_API_KEY",
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_custom_styling)
mapView = findViewById(R.id.mapView)
mapView.onCreate(savedInstanceState)
mapView.getMapAsync { mapMetricsMap ->
map = mapMetricsMap
// Set initial style
setMapStyle("Streets")
map.cameraPosition = CameraPosition.Builder()
.target(LatLng(48.8566, 2.3522))
.zoom(12.0)
.build()
// Style switch buttons
findViewById<Button>(R.id.btnStreets).setOnClickListener { setMapStyle("Streets") }
findViewById<Button>(R.id.btnDark).setOnClickListener { setMapStyle("Dark") }
findViewById<Button>(R.id.btnSatellite).setOnClickListener { setMapStyle("Satellite") }
}
}
private fun setMapStyle(name: String) {
val url = styles[name] ?: return
// Save current camera position
val currentCamera = map.cameraPosition
map.setStyle(Style.Builder().fromUri(url)) { style ->
// Restore camera after style loads
map.cameraPosition = currentCamera
}
}
override fun onStart() { super.onStart(); mapView.onStart() }
override fun onResume() { super.onResume(); mapView.onResume() }
override fun onPause() { super.onPause(); mapView.onPause() }
override fun onStop() { super.onStop(); mapView.onStop() }
override fun onDestroy() { super.onDestroy(); mapView.onDestroy() }
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
mapView.onSaveInstanceState(outState)
}
}Modify Existing Style Layers
Change layer properties of the current style at runtime:
kotlin
private fun modifyStyleLayers(style: Style) {
// Change water color
val waterLayer = style.getLayerAs<org.maplibre.android.style.layers.FillLayer>("water")
waterLayer?.setProperties(
org.maplibre.android.style.layers.PropertyFactory.fillColor(
android.graphics.Color.parseColor("#1A73E8")
)
)
// Change building color
val buildingLayer = style.getLayerAs<org.maplibre.android.style.layers.FillLayer>("building")
buildingLayer?.setProperties(
org.maplibre.android.style.layers.PropertyFactory.fillColor(
android.graphics.Color.parseColor("#E8E0D8")
)
)
// Hide POI labels
val poiLabels = style.getLayer("poi-label")
poiLabels?.setProperties(
org.maplibre.android.style.layers.PropertyFactory.visibility(
org.maplibre.android.style.layers.Property.NONE
)
)
}Show/Hide Layer Categories
Toggle entire categories of map features:
kotlin
import android.widget.ToggleButton
import org.maplibre.android.style.layers.Property
import org.maplibre.android.style.layers.PropertyFactory.visibility
private fun setupLayerToggles(style: Style) {
// Toggle roads
findViewById<ToggleButton>(R.id.btnRoads).setOnCheckedChangeListener { _, show ->
toggleLayersByPrefix(style, "road", show)
}
// Toggle buildings
findViewById<ToggleButton>(R.id.btnBuildings).setOnCheckedChangeListener { _, show ->
toggleLayersByPrefix(style, "building", show)
}
// Toggle labels
findViewById<ToggleButton>(R.id.btnLabels).setOnCheckedChangeListener { _, show ->
toggleLayersByPrefix(style, "label", show)
toggleLayersByPrefix(style, "place", show)
}
}
private fun toggleLayersByPrefix(style: Style, prefix: String, visible: Boolean) {
for (layer in style.layers) {
if (layer.id.contains(prefix, ignoreCase = true)) {
layer.setProperties(
visibility(if (visible) Property.VISIBLE else Property.NONE)
)
}
}
}Load Style from JSON String
Load a custom style from a local JSON string or asset:
kotlin
// From assets file
private fun loadLocalStyle() {
val json = assets.open("styles/custom-style.json")
.bufferedReader()
.readText()
map.setStyle(Style.Builder().fromJson(json)) { style ->
// Style loaded from local JSON
}
}List All Layers in Current Style
Useful for debugging and discovering layer IDs:
kotlin
private fun listLayers(style: Style) {
for (layer in style.layers) {
android.util.Log.d("MapStyle", "Layer: ${layer.id} (${layer.javaClass.simpleName})")
}
}Next Steps
- Data-Driven Styling — Style by data properties
- Building Layer — 3D building extrusions
- Custom Sprite — Custom icon images
Tip: When switching styles, the camera position resets to the new style's default. Always save map.cameraPosition before calling setStyle() and restore it in the callback, as shown above.