Реалізація відображення маркерів на карті в мобільному додатку
Додати один маркер — три строки коду. Відобразити 500 маркерів із кастомними іконками, коректним переиспользованием і тапами без фризів — це вже задача з нюансами.
Кастомні іконки: bitmap vs vector
Google Maps Android SDK приймає BitmapDescriptor, MapKit — ImageProvider, Mapbox — Drawable / UIImage. Рендерити bitmap з Canvas потрібно один раз і кешувати — не в onMapReady для кожного маркера окремо.
// Google Maps Android — кешований BitmapDescriptor
private val markerCache = HashMap<String, BitmapDescriptor>()
fun getMarkerIcon(type: String): BitmapDescriptor {
return markerCache.getOrPut(type) {
val bitmap = Bitmap.createBitmap(48, 48, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = when (type) {
"cafe" -> Color.parseColor("#E74C3C")
"shop" -> Color.parseColor("#3498DB")
else -> Color.GRAY
}
}
canvas.drawCircle(24f, 24f, 20f, paint)
BitmapDescriptorFactory.fromBitmap(bitmap)
}
}
Створювати Bitmap кожен раз при додаванні маркера — прямий шлях до OutOfMemoryError при 200+ об'єктах на екрані.
Callout / InfoWindow при тапі
У Google Maps на Android InfoWindow рендерується як статичний снімок — всередині не працюють кнопки і не оновлюється динамічний контент. Для інтерактивного попапа використовуйте ViewAnnotation (Maps SDK v3+) або власний FrameLayout поверх карти з позиціонуванням через Projection.toScreenLocation.
// iOS MapKit — кастомний callout через UIView
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let view = MKAnnotationView(annotation: annotation, reuseIdentifier: "custom")
view.image = UIImage(named: "pin")
view.canShowCallout = false // вимикаємо стандартний
// Кастомний callout додаємо в didSelect
return view
}
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
let callout = CustomCalloutView(annotation: view.annotation)
callout.center = CGPoint(x: view.bounds.midX, y: -callout.bounds.height / 2)
view.addSubview(callout)
}
Продуктивність: 500+ маркерів
При великій кількості об'єктів нативні маркерні API починають гальмувати — кожен маркер — окремий view. Поріг залежить від пристрою: на бюджетних Android помітні фризи з'являються вже після 150-200 Marker об'єктів при одночасному додаванні.
Рішення — GeoJSON-шар у Mapbox або TileOverlay у Google Maps: точки рендерятся як частина стилю карти, без створення об'єктів на кожну координату.
Для сценаріїв, де все ж таки потрібні нативні маркери з тапами — додавайте їх частинами через Handler.postDelayed або корутини:
lifecycleScope.launch {
locations.chunked(50).forEach { chunk ->
chunk.forEach { loc ->
googleMap.addMarker(
MarkerOptions()
.position(LatLng(loc.lat, loc.lng))
.icon(getMarkerIcon(loc.type))
)
}
delay(16) // один кадр, даємо UI не залежнути
}
}
Строки
4 години — 2 дні. Один тип маркерів з callout — півдня. Кілька типів з кешем іконок і інтерактивними попапами — 1–2 дні. Вартість розраховується індивідуально.







