Skip to content

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

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


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.