Map module: review comments and small fixes

This commit is contained in:
Grigorii 2022-10-20 12:03:14 +04:00
parent c8c44ede4f
commit 3f44ad3ba7
8 changed files with 76 additions and 87 deletions

View File

@ -43,7 +43,7 @@ abstract class AbstractMapManager<TMapView : View, TMap : Any, TLocation : Any>(
abstract fun increaseZoom(target: TLocation, zoomIncreaseValue: Int = getDefaultZoomStep())
abstract fun decreaseZoom(target: TLocation, zoomIncreaseValue: Int = getDefaultZoomStep())
abstract fun decreaseZoom(target: TLocation, zoomDecreaseValue: Int = getDefaultZoomStep())
abstract fun setMapAllGesturesEnabled(enabled: Boolean)

View File

@ -17,28 +17,24 @@ open class GoogleIconGenerator<T : ClusterItem>(
private val context: Context
) : IconGenerator(context), BaseIconGenerator<T, Cluster<T>, BitmapDescriptor> {
init {
setBackground(ContextCompat.getDrawable(context, R.drawable.default_cluster_background))
LayoutInflater
.from(context)
.inflate(R.layout.view_google_map_cluster_item, null)
.apply {
setContentView(this)
}
}
private val clusterIconsCache = SparseArray<BitmapDescriptor>()
private val clusterItemIconsCache = mutableMapOf<T, BitmapDescriptor>()
fun setDefaultViewAndBackground() {
val defaultLayout = LayoutInflater.from(context).inflate(R.layout.view_google_map_cluster_item, null)
setBackground(ContextCompat.getDrawable(context, R.drawable.default_cluster_background))
setContentView(defaultLayout)
}
override fun getClusterIcon(cluster: Cluster<T>): BitmapDescriptor? {
val clusterSize = cluster.size
return BitmapDescriptorFactory.fromBitmap(makeIcon(clusterSize.toString()))
}
override fun getClusterItemIcon(clusterItem: T): BitmapDescriptor? =
context.getDrawable(ru.touchin.basemap.R.drawable.marker_default_icon)?.let {
BitmapDescriptorFactory.fromBitmap(it.toBitmap())
}
override fun getClusterItemIcon(clusterItem: T): BitmapDescriptor? {
val defaultIcon = context.getDrawable(ru.touchin.basemap.R.drawable.marker_default_icon)
return BitmapDescriptorFactory.fromBitmap(defaultIcon?.toBitmap())
}
override fun getClusterItemView(clusterItem: T): BitmapDescriptor? =
clusterItemIconsCache.getOrPutIfNotNull(clusterItem) { getClusterItemIcon(clusterItem) }

View File

@ -1,7 +1,6 @@
package ru.touchin.googlemap
import android.content.Context
import android.view.LayoutInflater
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.model.BitmapDescriptor
import com.google.android.gms.maps.model.MarkerOptions
@ -19,10 +18,7 @@ open class GoogleMapItemRenderer<TClusterItem : ClusterItem>(
) : DefaultClusterRenderer<TClusterItem>(context, googleMap, clusterManager) {
var iconGenerator: BaseIconGenerator<TClusterItem, Cluster<TClusterItem>, BitmapDescriptor> =
GoogleIconGenerator<TClusterItem>(context).apply {
setBackground(context.getDrawable(R.drawable.default_cluster_background))
setContentView(LayoutInflater.from(context).inflate(R.layout.view_google_map_cluster_item, null))
}
GoogleIconGenerator<TClusterItem>(context).apply { setDefaultViewAndBackground() }
override fun shouldRenderAsCluster(cluster: Cluster<TClusterItem>): Boolean =
cluster.size > minClusterItemSize

View File

@ -20,7 +20,7 @@ class GoogleMapManager(mapView: MapView) : AbstractMapManager<MapView, GoogleMap
override fun initialize(mapListener: AbstractMapListener<MapView, GoogleMap, LatLng>?) {
super.initialize(mapListener)
mapView.getMapAsync(::initMap)
mapView.getMapAsync(this::initMap)
}
override fun initMap(map: GoogleMap) {

View File

@ -12,18 +12,20 @@ import com.google.maps.android.clustering.algo.Algorithm
import com.google.maps.android.clustering.algo.NonHierarchicalDistanceBasedAlgorithm
import com.google.maps.android.clustering.algo.PreCachingAlgorithmDecorator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.sample
@OptIn(FlowPreview::class)
class GooglePlacemarkManager<TClusterItem : ClusterItem>(
context: Context,
private val lifecycleOwner: LifecycleOwner,
@ -47,7 +49,8 @@ class GooglePlacemarkManager<TClusterItem : ClusterItem>(
private val markers = mutableListOf<TClusterItem>()
private var lastVisibleItems = emptyList<TClusterItem>()
private var onCameraIdleListener: (() -> Unit)? = null
var onCameraIdleListener: (() -> Unit)? = null
var clusterRenderer: GoogleMapItemRenderer<TClusterItem>? = null
set(value) {
@ -55,8 +58,6 @@ class GooglePlacemarkManager<TClusterItem : ClusterItem>(
setRenderer(value)
}
private val lock = Any()
init {
googleMap.setOnCameraIdleListener(this)
googleMap.setOnMarkerClickListener(this)
@ -65,40 +66,28 @@ class GooglePlacemarkManager<TClusterItem : ClusterItem>(
setOnClusterItemClickListener(clusterItemTapAction)
}
fun setItems(items: Collection<TClusterItem>) {
synchronized(lock) {
markers.clear()
markers.addAll(items)
onDataChanged()
}
}
@Synchronized
override fun addItems(items: Collection<TClusterItem>) {
synchronized(lock) {
markers.addAll(items)
onDataChanged()
}
markers.addAll(items)
onDataChanged()
}
@Synchronized
override fun addItem(clusterItem: TClusterItem) {
synchronized(lock) {
markers.add(clusterItem)
onDataChanged()
}
markers.add(clusterItem)
onDataChanged()
}
@Synchronized
override fun removeItem(atmClusterItem: TClusterItem) {
synchronized(lock) {
markers.remove(atmClusterItem)
onDataChanged()
}
markers.remove(atmClusterItem)
onDataChanged()
}
@Synchronized
override fun clearItems() {
synchronized(lock) {
markers.clear()
onDataChanged()
}
markers.clear()
onDataChanged()
}
override fun onCameraIdle() {
@ -106,22 +95,11 @@ class GooglePlacemarkManager<TClusterItem : ClusterItem>(
onCameraIdleEvent.tryEmit(true)
}
private fun onDataChanged() {
onVisibilityChangedEvent.tryEmit(getData())
}
private fun getData(): Pair<VisibleRegion?, List<TClusterItem>> =
googleMap.projection.visibleRegion to markers
private fun listenToCameraIdleEvents() {
cameraIdleJob = lifecycleOwner.lifecycleScope.launchWhenStarted {
onCameraIdleEvent
.debounce(CAMERA_DEBOUNCE_MILLI)
.flowOn(Dispatchers.Main)
.collect {
onCameraIdleListener?.invoke()
}
}
@Synchronized
fun setItems(items: Collection<TClusterItem>) {
markers.clear()
markers.addAll(items)
onDataChanged()
}
fun startClustering() {
@ -131,10 +109,7 @@ class GooglePlacemarkManager<TClusterItem : ClusterItem>(
.debounce(CLUSTERING_START_DEBOUNCE_MILLI)
.flowOn(Dispatchers.Default)
.onStart { emit(getData()) }
.filter { (visibleRegion, _) -> visibleRegion != null }
.map { (visibleRegion, clusteringItems) ->
clusteringItems.filter { visibleRegion?.latLngBounds?.contains(it.position) == true }
}
.mapNotNull { (region, items) -> findItemsInRegion(region, items) }
.sample(CLUSTERING_NEW_LIST_CONSUMING_THROTTLE_MILLIS)
.catch { emit(lastVisibleItems) }
.flowOn(Dispatchers.Main)
@ -148,16 +123,32 @@ class GooglePlacemarkManager<TClusterItem : ClusterItem>(
listenToCameraIdleEvents()
}
private fun listenToCameraIdleEvents() {
cameraIdleJob = lifecycleOwner.lifecycleScope.launchWhenStarted {
onCameraIdleEvent
.debounce(CAMERA_DEBOUNCE_MILLI)
.flowOn(Dispatchers.Main)
.collect {
onCameraIdleListener?.invoke()
}
}
}
fun stopClustering() {
clusteringJob?.cancel()
cameraIdleJob?.cancel()
}
fun setOnCameraIdleListener(listener: () -> Unit) {
if (onCameraIdleListener != null) return
onCameraIdleListener = listener
private fun onDataChanged() {
onVisibilityChangedEvent.tryEmit(getData())
}
private fun getData(): Pair<VisibleRegion?, List<TClusterItem>> =
googleMap.projection.visibleRegion to markers
private fun findItemsInRegion(region: VisibleRegion?, items: List<TClusterItem>): List<TClusterItem>? =
region?.let { items.filter { item -> item.position in region.latLngBounds } }
private companion object {
const val CAMERA_DEBOUNCE_MILLI = 50L

View File

@ -8,11 +8,11 @@ import com.yandex.runtime.ui_view.ViewProvider
class DefaultIconGenerator<T : PointModel>(private val context: Context) : YandexIconGenerator<T>() {
override fun getClusterIcon(cluster: List<T>): ViewProvider {
val view: TextView = LayoutInflater.from(context).inflate(R.layout.default_cluster_view, null) as TextView
view.setText(cluster.size.toString())
view.setBackgroundResource(ru.touchin.basemap.R.drawable.marker_default_icon)
return ViewProvider(view)
val textView = LayoutInflater.from(context).inflate(R.layout.default_cluster_view, null).apply {
(this as? TextView)?.text = cluster.size.toString()
setBackgroundResource(ru.touchin.basemap.R.drawable.marker_default_icon)
}
return ViewProvider(textView)
}
override fun getClusterItemIcon(clusterItem: T) = ViewProvider(

View File

@ -135,10 +135,12 @@ class YandexMapManager(
}
override fun setMapAllGesturesEnabled(enabled: Boolean) {
map.isRotateGesturesEnabled = enabled
map.isScrollGesturesEnabled = enabled
map.isTiltGesturesEnabled = enabled
map.isZoomGesturesEnabled = enabled
with(map) {
isRotateGesturesEnabled = enabled
isScrollGesturesEnabled = enabled
isTiltGesturesEnabled = enabled
isZoomGesturesEnabled = enabled
}
}
override fun setMyLocationEnabled(enabled: Boolean) {

View File

@ -15,6 +15,11 @@ class YandexPlacemarkManager<TPoint : PointModel>(
private val markerTapAction: (MapObject, Point) -> Boolean,
) : ClusterListener, ClusterTapListener, MapObjectTapListener {
private companion object {
const val DEFAULT_CLUSTER_RADIUS = 42.0
const val DEFAULT_MIN_ZOOM = 35
}
private val markers = mutableListOf<TPoint>()
private var placemarkCollection: ClusterizedPlacemarkCollection? = null
@ -25,8 +30,8 @@ class YandexPlacemarkManager<TPoint : PointModel>(
fun addMarkersOnMap(
mapView: MapView,
clusterRadius: Double = 42.0,
minZoom: Int = 35
clusterRadius: Double = DEFAULT_CLUSTER_RADIUS,
minZoom: Int = DEFAULT_MIN_ZOOM
) {
removeMarkers()
@ -52,9 +57,8 @@ class YandexPlacemarkManager<TPoint : PointModel>(
}
override fun onClusterAdded(cluster: Cluster) {
mapItemRenderer.getClusterIcon(cluster.toPlacemarkList())?.let {
cluster.appearance.setView(it)
}
val clusterIcon = mapItemRenderer.getClusterIcon(cluster.toPlacemarkList())
clusterIcon?.let(cluster.appearance::setView)
cluster.addClusterTapListener(this)
}