InteractiveVectorMap is an Android SDK for both the classic Android View system and Jetpack Compose, so you can use the same map capabilities in either UI stack. It ships with ready-to-use map datasets, region selection, region coloring, hide/show support, zoom and pan controls, anchored markers, labels, custom shapes, curved connection paths, and animated dashed routes. And it is very lightweight!
- Android View API with
MapView - Jetpack Compose API with
InteractiveMapandInteractiveMapState - Built-in vector maps
- Single, multiple, or disabled selection modes
- Programmatic region select, deselect, clear, hide, and show
- Region color overrides
- Zoom and pan camera helpers
- Anchored markers on a region or on the full map viewport
- Marker types: pulse, custom drawable, and label
- Shapes: circle, rectangle, polygon, and polyline
- Paths between arbitrary points, regions, and markers
- Dashed and animated dashed paths
- Per-item transforms for translation and rotation
The library is published on Maven Central.
dependencies {
implementation("com.nezihyilmaz.interactivevectormap:view:1.0.0")
implementation("com.nezihyilmaz.interactivevectormap:data-world:1.0.0")
}dependencies {
implementation("com.nezihyilmaz.interactivevectormap:compose:1.0.0")
implementation("com.nezihyilmaz.interactivevectormap:data-world:1.0.0")
}Add only the datasets you want to ship.
| Map | Use in code | Maven artifact | Projection |
|---|---|---|---|
| World (Minus Antarctica) | Maps.World |
implementation("com.nezihyilmaz.interactivevectormap:data-world:1.0.0") |
Robinson |
| World 2 (Including Antarctica) | Maps.World2 |
implementation("com.nezihyilmaz.interactivevectormap:data-world2:1.0.0") |
Natural Earth |
| Europe | Maps.Europe |
implementation("com.nezihyilmaz.interactivevectormap:data-europe:1.0.0") |
Robinson |
| Africa | Maps.Africa |
implementation("com.nezihyilmaz.interactivevectormap:data-africa:1.0.0") |
Robinson |
| UK | Maps.Uk |
implementation("com.nezihyilmaz.interactivevectormap:data-uk:1.0.0") |
Mercator |
| Turkey | Maps.Turkey |
implementation("com.nezihyilmaz.interactivevectormap:data-turkey:1.0.0") |
Mercator |
| USA | Maps.Usa |
implementation("com.nezihyilmaz.interactivevectormap:data-usa:1.0.0") |
Lambert azimuthal equal-area |
<MapView
android:id="@+id/mapView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />mapView.defaultColor = Color.parseColor("#ECECEC")
mapView.selectedColor = Color.parseColor("#4CAF50")
mapView.borderColor = Color.GRAY
mapView.selectionMode = SelectionMode.Multiple
mapView.setMap(Maps.World) {
mapView.addMarker(
type = Pulse(
color = Color.RED,
text = "HQ"
),
position = Anchor.Region(WorldId.US)
)
}
mapView.setOnSelectionChangedListener { regions ->
println("Selected: ${regions.map { it.id }}")
}val state = rememberInteractiveMapState(
initialSelectionMode = SelectionMode.Multiple
)
InteractiveMap(
mapData = Maps.World,
state = state,
defaultColor = Color(0xFFECECEC),
selectedColor = Color(0xFF4CAF50),
borderColor = Color.Gray,
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(),
onMapLoaded = {
state.addMarker(
type = Pulse(
color = Color.Red,
text = "HQ"
),
position = Anchor.Region(WorldId.US)
)
},
onSelectionChanged = { regions ->
println("Selected: ${regions.map { it.id }}")
}
)Every dataset module also exposes typed region-id helpers through RegionId.
Examples:
val unitedStates = WorldId.USval istanbul = TurkeyId.TR34SelectionMode supports:
SingleMultipleNone
Map content can be positioned relative to:
Anchor.Region(regionId)for placement inside a regionAnchor.Map()for placement inside the full map viewport
Both anchors accept an offset: PositionScope.() -> PointF lambda. Inside that scope you can use:
centercentroidwidthheight
Example:
Anchor.Region(regionId = WorldId.US) { center }
Anchor.Region(regionId = WorldId.US) { PointF(width * 0.75f, height * 0.2f) }
Anchor.Map { PointF(width / 2f, height / 2f) }Transform can be used with markers and shapes:
Transform(
translationX = 12f,
translationY = -8f,
rotation = 20f
)setMap(mapData, onLoaded = null)setOnSelectionChangedListener(listener)selectRegion(regionId, animate = true)selectRegions(regionIds, animate = true)deselectRegion(regionId, animate = true)clearSelection(animate = true)setRegionColor(regionId, color)clearRegionColor(regionId)clearRegionColors()hideRegion(regionId, animate = true): BooleanshowRegion(regionId): BooleanzoomTo(ratio, smooth = true)panTo(regionId, smooth = true)cameraCenter(): PointF?addMarker(type, position, transform = Transform.None): Int?removeMarker(markerId)clearMarkers()addShape(type, position, transform = Transform.None): Int?addCircle(position, radius, style = ShapeStyle(), transform = Transform.None): Int?addRectangle(position, width, height, cornerRadius = 0f, style = ShapeStyle(), transform = Transform.None): Int?addPolygon(position, points, style = ShapeStyle(), transform = Transform.None): Int?addPolyline(position, points, style = ShapeStyle(), transform = Transform.None): Int?removeShape(shapeId)clearShapes()addPath(from, to, style = PathStyle(), curvature = 0f): Int?removePath(pathId)clearPaths()
When interactionEnabled is true, MapView supports:
- tap selection
- pinch to zoom
- pan/drag navigation
InteractiveMap(
mapData = Maps.World,
modifier = Modifier.wrapContentHeight(),
state = rememberInteractiveMapState(),
selectedColor = Color(0xFF4CAF50),
defaultColor = Color(0xFFE7E7E7),
borderColor = Color.Gray,
borderWidth = 1.5f,
onMapLoaded = {},
onSelectionChanged = {}
)InteractiveMapState exposes:
maxZoomanimationDurationMsscaleoffsetselectionModeinteractionEnabledregionsselectedRegions
addMarker(type, position, transform = Transform.None): Int?removeMarker(markerId)clearMarkers()addShape(type, position, transform = Transform.None): Int?addCircle(position, radius, style = ShapeStyle(), transform = Transform.None): Int?addRectangle(position, width, height, cornerRadius = 0f, style = ShapeStyle(), transform = Transform.None): Int?addPolygon(position, points, style = ShapeStyle(), transform = Transform.None): Int?addPolyline(position, points, style = ShapeStyle(), transform = Transform.None): Int?removeShape(shapeId)clearShapes()addPath(from, to, style = PathStyle(), curvature = 0f): Int?removePath(pathId)clearPaths()setRegionColor(regionId, color)clearRegionColor(regionId)clearRegionColors()selectRegion(regionId, animate = true)selectRegions(regionIds, animate = true)deselectRegion(regionId, animate = true)clearSelection(animate = true)hideRegion(regionId, animate = true): BooleanshowRegion(regionId): BooleanzoomTo(ratio, smooth = true)panTo(regionId, smooth = true)cameraCenter(): PointF?
When state.interactionEnabled is true, InteractiveMap supports:
- tap selection
- pinch zoom
- pan
Supported marker types:
MarkerType.PulseMarkerType.CustomMarkerType.Label
mapView.addMarker(
type = Pulse(
color = Color.RED,
text = "12"
),
position = Anchor.Region(WorldId.US)
)mapView.addMarker(
type = Custom(
drawable = yourDrawable,
text = "NYC"
),
position = Anchor.Map {
PointF(width * 0.5f, height * 0.35f)
}
)state.addMarker(
type = Label(
text = "Europe",
color = Color.BLACK,
size = Fixed(30f)
),
position = Anchor.Region(WorldId.FR)
)Supported shapes:
ShapeType.CircleShapeType.RectangleShapeType.PolygonShapeType.Polyline
val style = ShapeStyle(
fillColor = Color.argb(102, 33, 150, 243),
strokeColor = Color.parseColor("#1976D2"),
strokeWidth = 2f
)mapView.addCircle(
position = Anchor.Region(WorldId.US),
radius = 24f,
style = style
)mapView.addRectangle(
position = Anchor.Region(WorldId.US),
width = 64f,
height = 36f,
cornerRadius = 12f,
style = style
)state.addPolygon(
position = Anchor.Map(),
points = listOf(
PointF(-20f, -20f),
PointF(30f, -10f),
PointF(10f, 25f)
),
style = style
)state.addPolyline(
position = Anchor.Map(),
points = listOf(
PointF(-30f, -10f),
PointF(0f, 20f),
PointF(30f, -10f)
),
style = style
)Paths connect two PathEndpoints:
PathEndpoint.Point(x, y)PathEndpoint.Marker(markerId)PathEndpoint.Region(regionId)
curvature = 0f draws a straight line. Non-zero values produce a curved arc.
val style = PathStyle(
color = Color.parseColor("#1976D2"),
width = 2f,
dashPattern = floatArrayOf(8f, 4f),
dashAnimation = DashAnimation(
cycleDurationMs = 1200L,
reverse = false
)
)mapView.addPath(
from = Region(WorldId.US),
to = Region(WorldId.CA),
style = style,
curvature = 0.22f
)val markerId = state.addMarker(
type = Pulse(text = "Hub"),
position = Anchor.Region(WorldId.GB)
) ?: return
state.addPath(
from = Marker(markerId),
to = Region(WorldId.FR),
style = style,
curvature = -0.18f
)state.addPath(
from = Point(100f, 120f),
to = Point(260f, 180f),
style = PathStyle(
color = Color.RED,
width = 3f
),
curvature = 0f
)mapView.setRegionColor(WorldId.US, Color.parseColor("#1E88E5"))
mapView.setRegionColor(WorldId.CA, Color.parseColor("#43A047"))mapView.clearRegionColor(WorldId.US)
mapView.clearRegionColors()mapView.hideRegion(WorldId.GL)
mapView.showRegion(WorldId.GL)Hidden regions are excluded from hit testing and selection.
mapView.selectRegion(WorldId.US)
mapView.selectRegions(listOf(WorldId.US, WorldId.CA), animate = false)
mapView.deselectRegion(WorldId.CA)
mapView.clearSelection()zoomTo(ratio) maps 0f..1f into 1x..maxZoom.
mapView.zoomTo(0f)
mapView.zoomTo(0.5f)
mapView.zoomTo(1f)mapView.panTo(WorldId.TR)This project is licensed under the terms of the LICENSE file.


