Android Kaki

Build beautiful, usable products using required Components for Android.

Android MapView with clustered and dynamic photos in marker | by Roman Chugunov | April 2023


Animation in Google Maps marker
android:title="com.google.android.geo.API_KEY"
android:worth="YOUR_API_KEY" />
[
{
"lat": 59.92140394439577,
"lon": 30.445576954709395,
"icon": "1619152.jpg"
},
{
"lat": 59.93547541514004,
"lon": 30.21481515274267,
"icon": "1712315710.jpg"
},
class MapApplication : Application() {
private val context = CoroutineScope(Dispatchers.Default)


private val _dataFlow = MutableSharedFlow>(
replay = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)


val dataFlow: Flow> = _dataFlow


override fun onCreate() {
super.onCreate()


val retrofit = Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()


val service = retrofit.create(MarkerLocationsService::class.java)


context.launch {
_dataFlow.tryEmit(service.getLocations())
}
}
}

override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
scope.launch {
(application as MapApplication).dataFlow.collect { data ->
mMap.clear()
data.forEach { marker ->
mMap.addMarker(MarkerOptions().position(LatLng(marker.lat, marker.lon)))
}
}
}
}

internal class BoundariesListener(
private val map: GoogleMap,
) : GoogleMap.OnCameraIdleListener {
private val _boundariesFlow = MutableSharedFlow(
replay = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST,
)
val boundariesFlow: Flow = _boundariesFlow


override fun onCameraIdle() {
val boundaries = map.projection.visibleRegion.latLngBounds
_boundariesFlow.tryEmit(boundaries)
}
}

override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
val boundariesListener = BoundariesListener(googleMap)


mMap.setOnCameraMoveStartedListener(boundariesListener)
mMap.setOnCameraIdleListener(boundariesListener)


scope.launch {
(application as MapApplication).dataFlow.combine(boundariesListener.boundariesFlow) { data, boundaries ->
data to boundaries
}.collect { (data, boundaries) ->
mMap.clear()


data.filter { boundaries.bounds.includesLocation(it.lat, it.lon) }
.forEach { marker ->
mMap.addMarker(MarkerOptions().position(LatLng(marker.lat, marker.lon)))
}
}
}
}


fun LatLngBounds.includesLocation(lat: Double, lon: Double): Boolean {
return this.northeast.latitude > lat && this.southwest.latitude < lat &&
this.northeast.longitude > lon && this.southwest.longitude < lon


}

dependencies {
...


implementation 'com.google.maps.android:android-maps-utils:'
}

data class MapMarker(
val titleText: String,
val location: LatLng,
) : ClusterItem {
override fun getPosition(): LatLng = location


override fun getTitle(): String? = null


override fun getSnippet(): String? = null
}

override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
val boundariesListener = BoundariesListener(googleMap)


val clusterManager = ClusterManager(this, mMap)
val mapRenderer = MapMarkersRenderer(
context = this,
callback = this,
map = mMap,
clusterManager = clusterManager
)
clusterManager.renderer = mapRenderer

class MapMarkersRenderer(
context: Context,
map: GoogleMap,
clusterManager: ClusterManager,
) : DefaultClusterRenderer(context, map, clusterManager) {
private val mapMarkerView: MapMarkerView = MapMarkerView(context)
private val markerIconGenerator = IconGenerator(context)


init {
markerIconGenerator.setBackground(null)
markerIconGenerator.setContentView(mapMarkerView)
}


override fun onBeforeClusterItemRendered(clusterItem: MapMarker, markerOptions: MarkerOptions) {
val data = getItemIcon(clusterItem)
markerOptions
.icon(data.bitmapDescriptor)
.anchor(data.anchorU, data.anchorV)
}


override fun onClusterItemUpdated(clusterItem: MapMarker, marker: Marker) {
val data = getItemIcon(clusterItem)
marker.setIcon(data.bitmapDescriptor)
marker.setAnchor(data.anchorU, data.anchorV)
}


override fun onBeforeClusterRendered(
cluster: Cluster,
markerOptions: MarkerOptions
) {
val data = getClusterIcon(cluster)
markerOptions
.icon(data.bitmapDescriptor)
.anchor(data.anchorU, data.anchorV)
}


override fun onClusterUpdated(cluster: Cluster, marker: Marker) {
val data = getClusterIcon(cluster)
marker.setIcon(data.bitmapDescriptor)
marker.setAnchor(data.anchorU, data.anchorV)
}


override fun shouldRenderAsCluster(cluster: Cluster): Boolean = cluster.size > 1
}

private fun getItemIcon(marker: MapMarker): IconData {
mapMarkerView.setContent(
circle = MapMarkerView.CircleContent.Marker,
title = marker.titleText
)
val icon: Bitmap = markerIconGenerator.makeIcon()
val middleBalloon = dpToPx(mapMarkerView.context, 24)
return IconData(
bitmapDescriptor = BitmapDescriptorFactory.fromBitmap(icon),
anchorU = middleBalloon / 2 / icon.width,
anchorV = 1f
)
}
private fun getClusterIcon(cluster: Cluster): IconData {
mapMarkerView.setContent(
circle = MapMarkerView.CircleContent.Cluster(
count = cluster.size
),
title = null
)


val icon: Bitmap = markerIconGenerator.makeIcon()
val middleBalloon = dpToPx(context, 40)
return IconData(
bitmapDescriptor = BitmapDescriptorFactory.fromBitmap(icon),
anchorU = middleBalloon / 2 / icon.width,
anchorV = 1f
)
}

override fun onMapReady(googleMap: GoogleMap) {
...
scope.launch {
(application as MapApplication).dataFlow.combine(boundariesListener.boundariesFlow) { data, boundaries ->
data to boundaries
}.collect { (data, boundaries) ->


val markers = data.filter { boundaries.bounds.includesLocation(it.lat, it.lon) }
.map { marker ->
MapMarker(
titleText = "Item ${marker.hashCode()}",
location = LatLng(marker.lat, marker.lon)
)
}
clusterManager.clearItems()
clusterManager.addItems(markers)
clusterManager.cluster()
}
}
}

Picasso.get()
.load(imageUrl)
.resize(size, size)
.centerCrop()
.into(object : Target {
})

dependencies {
implementation 'com.squareup.picasso:picasso:'

private fun getItemIcon(marker: MapMarker): IconData {
val iconToShow: MapMarker.Icon = when (marker.icon) {
is MapMarker.Icon.UrlIcon -> {
val cachedIcon = loadedImages.get(marker.icon.url)
if (cachedIcon == null) {
loadBitmapImage(marker.icon.url)
}
cachedIcon?.let { MapMarker.Icon.BitmapIcon(marker.icon.url, it) } ?: marker.icon
}


else -> marker.icon
}

private val loadedImages = LruCache(30)
data class MapMarker(
val icon: Icon,
val titleText: String,
@ColorInt val pinColor: Int,
val location: LatLng,
) : ClusterItem {
...


sealed interface Icon {
val url: String
data class Placeholder(override val url: String) : Icon
data class BitmapIcon(override val url: String, val image: Bitmap) : Icon
}
}

fun setContent(
circle: CircleContent,
title: String?,
@ColorInt pinColor: Int,
) {
when (circle) {
is CircleContent.Cluster -> {
...
}


is CircleContent.Marker -> {
binding.mapMarkerViewClusterText.isVisible = false
val icon = circle.mapMarkerIcon
val drawable = getIconDrawable(markerIcon = icon)
binding.mapMarkerViewIcon.setImageDrawable(drawable)


...

private fun getIconDrawable(
markerIcon: MapMarker.Icon,
): Drawable? {
val drawable = when (markerIcon) {
is MapMarker.Icon.BitmapIcon -> {
RoundedBitmapDrawableFactory.create(resources, markerIcon.image).apply {
isCircular = true
cornerRadius = max(markerIcon.image.width, markerIcon.image.height) / 2.0f
}
}


is MapMarker.Icon.Placeholder -> {
// Here we are just waiting for image to be loaded
null
}
}
return drawable
}

private fun loadBitmapImage(imageUrl: String) {
val size = dpToPx(context, 40).toInt()
val target = IconTarget(imageUrl)
Picasso.get()
.load(BuildConfig.IMAGES_URL + imageUrl)
.resize(size, size)
.centerCrop()
.into(target)
}


inner class IconTarget(private val imageUrl: String) : Target {
override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom) {
loadedImages.put(imageUrl, bitmap)
callback.onImageLoaded(icon = MapMarker.Icon.BitmapIcon(imageUrl, bitmap))
}


override fun onBitmapFailed(e: Exception?, errorDrawable: Drawable?) {}


override fun onPrepareLoad(placeHolderDrawable: Drawable?) {}
}


interface Callback {
fun onImageLoaded(icon: MapMarker.Icon.BitmapIcon)
}

John Wick: Chapter 4 (FREE) FULLMOVIE The Super Mario Bros Movie avatar 2 Where To Watch Creed 3 Free At Home Knock at the Cabin (2023) FullMovie Where To Watch Ant-Man 3 and the Wasp: Quantumania Cocaine Bear 2023 (FullMovie) Scream 6 Full Movie
Updated: April 25, 2023 — 10:16 pm

Leave a Reply

Your email address will not be published. Required fields are marked *

androidkaki.com © 2023 Android kaki