Реалізація відображення маркерів на карті в мобільному додатку
Додати один маркер — три рядки коду. А ось відобразити 500 кастомних іконок із коректним перевикористанням та обробкою тапів без фризів — задача з нюансами. Ми бачимо такі запити регулярно: замовники хочуть на карті магазини, кафе, зупинки — кожен зі своєю іконкою та спливаючою інформацією. За нашими вимірами, правильно оптимізована карта з 300 маркерами видає 55 FPS на пристроях середнього сегмента, тоді як без оптимізації — близько 20 FPS. Економія часу на малювання сягає 40%, а при 500+ об'єктах різниця стає критичною. У цій статті розберемо, як реалізувати кастомні маркери на карті в мобільному додатку з максимальною продуктивністю. Зв'яжіться з нами для консультації з оптимізації карти — це допоможе заощадити бюджет на ранніх етапах.
Кроки для реалізації кастомного маркера з кешуванням
- Створіть Bitmap через Canvas: намалюйте іконку потрібного кольору та форми.
- Перетворіть Bitmap у BitmapDescriptor (Android) або UIImage (iOS).
- Кешуйте результат за типом маркера, щоб уникнути повторного малювання.
- При додаванні маркера використовуйте кешований дескриптор.
- Для інтерактивного попапу використовуйте ViewAnnotation або кастомний UIView.
Як створити кастомні іконки без витоків пам'яті?
Google Maps Android SDK приймає BitmapDescriptor, MapKit — ImageProvider, Mapbox — Drawable/UIImage. Рендерити bitmap з Canvas потрібно один раз і кешувати — не в onMapReady для кожного маркера окремо. Це дозволяє заощадити до 40% часу на малювання та уникнути OutOfMemoryError при 200+ об'єктах.
// 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 щоразу — прямий шлях до помилок пам'яті. Кешування скорочує витрати на виділення об'єктів і прискорює повторне відображення.
Чому InfoWindow не підходить для інтерактивних попапів?
У Google Maps на Android InfoWindow рендериться як статичний знімок — всередині не працюють кнопки і не оновлюється динамічний контент. Для інтерактивного попапу використовуйте ViewAnnotation (Maps SDK v3+) або власний FrameLayout поверх карти з позиціонуванням через Projection.toScreenLocation. На iOS аналогічно: вимикаєте canShowCallout у MKAnnotationView і додаєте кастомний UIView у mapView:didSelect:.
// 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 // вимикаємо стандартний 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: точки рендеряться як частина стилю карти, без створення об'єктів на кожну координату. За нашими вимірами, такий підхід дає приріст FPS у 3–5 разів на завантаженій карті.
Якщо все ж потрібні нативні маркери з тапами — додавайте їх частинами через 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 не зависнути } } Кожна пачка маркерів обробляється в одному фреймі — UI залишається чуйним. Це скорочує витрати на налагодження та прискорює фінальне тестування.
Порівняння InfoWindow і кастомного callout
| Параметр | InfoWindow (Google) | Кастомний callout (свій UIView) |
|---|---|---|
| Інтерактивність | Ні (статичний знімок) | Так (кнопки, скрол) |
| Оновлення контенту | Тільки при повторному показі | У реальному часі |
| Продуктивність | Висока (рендериться один раз) | Середня (додається в ієрархію) |
| Рекомендація | Для простих підписів | Для детальних карток з діями |
Чому GeoJSON-шар вигідніший за кластеризацію для великих обсягів?
GeoJSON-шар (Mapbox Style API) рендерить точки як векторні тайли — це в 2–3 рази швидше при 1000+ об'єктах. Кластеризація групує маркери, але при зумі перемальовує їх, створюючи навантаження. GeoJSON підтримує оновлення без перезавантаження всієї карти. Для статичних даних (магазини, парковки) GeoJSON оптимальний, а для динамічного трекінгу краще кластеризація.
Порівняння підходів до відображення великої кількості маркерів
| Підхід | Максимум маркерів | Завантаження CPU | Використання пам'яті |
|---|---|---|---|
| Нативні маркери | ~200 на бюджетному пристрої | Середнє | Високе |
| Кластеризація | ~1000 | Високе при зумі | Середнє |
| ГеоJSON-шар | 5000+ | Низьке | Низьке (тайли) |
Поширені проблеми та рішення
- Маркер не з'являється через невірні координати. Перевірте широту/довготу: в Google Maps широта від -90 до 90, довгота від -180 до 180. Для MapKit — аналогічно.
- Витік пам'яті при великій кількості маркерів. Використовуйте кешування BitmapDescriptor (як у прикладі вище) і не зберігайте посилання на Marker після їх додавання. Економія пам'яті безпосередньо впливає на стабільність додатка.
Що входить в роботу
- Аналіз — вивчаємо макети та вимоги до картографічного функціоналу.
- Проектування — обираємо стек (Google Maps, MapKit, Mapbox) та архітектуру маркерів.
- Реалізація — пишемо кастомні іконки, callout, обробку тапів.
- Оптимізація — тестуємо на пристроях з 2 ГБ RAM, застосовуємо кластеризацію або GeoJSON.
- Документація — передаємо вихідні коди, інструкцію з додавання нових типів маркерів.
Досвід та гарантії
Ми займаємося мобільною розробкою більше 5 років і реалізували понад 50 проектів з картами — від навігатора до гео-сервісу доставки. Гарантуємо стабільну роботу маркерів на пристроях з Android 8+ та iOS 14+. Всі рішення проходять тестування на реальних девайсах. MapKit та Google Maps Android SDK — наші настільні книги.
Терміни та вартість
Оцінюємо проект безкоштовно. Терміни: від 4 годин для простого варіанту до 2 днів для комплексного рішення з кількома типами маркерів. Вартість розраховується індивідуально — напишіть нам, щоб отримати консультацію та обговорити ваш проект. Зв'яжіться з нами для оцінки вашого проекту.
Ми використовуємо: Swift 5.9, Kotlin 1.9, Jetpack Compose, SwiftUI, Combine, Coroutines + Flow, GraphQL, Firebase.







