Map module: review comments and small fixes
This commit is contained in:
parent
c8c44ede4f
commit
3f44ad3ba7
|
|
@ -43,7 +43,7 @@ abstract class AbstractMapManager<TMapView : View, TMap : Any, TLocation : Any>(
|
||||||
|
|
||||||
abstract fun increaseZoom(target: TLocation, zoomIncreaseValue: Int = getDefaultZoomStep())
|
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)
|
abstract fun setMapAllGesturesEnabled(enabled: Boolean)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,28 +17,24 @@ open class GoogleIconGenerator<T : ClusterItem>(
|
||||||
private val context: Context
|
private val context: Context
|
||||||
) : IconGenerator(context), BaseIconGenerator<T, Cluster<T>, BitmapDescriptor> {
|
) : 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 clusterIconsCache = SparseArray<BitmapDescriptor>()
|
||||||
private val clusterItemIconsCache = mutableMapOf<T, 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? {
|
override fun getClusterIcon(cluster: Cluster<T>): BitmapDescriptor? {
|
||||||
val clusterSize = cluster.size
|
val clusterSize = cluster.size
|
||||||
return BitmapDescriptorFactory.fromBitmap(makeIcon(clusterSize.toString()))
|
return BitmapDescriptorFactory.fromBitmap(makeIcon(clusterSize.toString()))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getClusterItemIcon(clusterItem: T): BitmapDescriptor? =
|
override fun getClusterItemIcon(clusterItem: T): BitmapDescriptor? {
|
||||||
context.getDrawable(ru.touchin.basemap.R.drawable.marker_default_icon)?.let {
|
val defaultIcon = context.getDrawable(ru.touchin.basemap.R.drawable.marker_default_icon)
|
||||||
BitmapDescriptorFactory.fromBitmap(it.toBitmap())
|
return BitmapDescriptorFactory.fromBitmap(defaultIcon?.toBitmap())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getClusterItemView(clusterItem: T): BitmapDescriptor? =
|
override fun getClusterItemView(clusterItem: T): BitmapDescriptor? =
|
||||||
clusterItemIconsCache.getOrPutIfNotNull(clusterItem) { getClusterItemIcon(clusterItem) }
|
clusterItemIconsCache.getOrPutIfNotNull(clusterItem) { getClusterItemIcon(clusterItem) }
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package ru.touchin.googlemap
|
package ru.touchin.googlemap
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.view.LayoutInflater
|
|
||||||
import com.google.android.gms.maps.GoogleMap
|
import com.google.android.gms.maps.GoogleMap
|
||||||
import com.google.android.gms.maps.model.BitmapDescriptor
|
import com.google.android.gms.maps.model.BitmapDescriptor
|
||||||
import com.google.android.gms.maps.model.MarkerOptions
|
import com.google.android.gms.maps.model.MarkerOptions
|
||||||
|
|
@ -19,10 +18,7 @@ open class GoogleMapItemRenderer<TClusterItem : ClusterItem>(
|
||||||
) : DefaultClusterRenderer<TClusterItem>(context, googleMap, clusterManager) {
|
) : DefaultClusterRenderer<TClusterItem>(context, googleMap, clusterManager) {
|
||||||
|
|
||||||
var iconGenerator: BaseIconGenerator<TClusterItem, Cluster<TClusterItem>, BitmapDescriptor> =
|
var iconGenerator: BaseIconGenerator<TClusterItem, Cluster<TClusterItem>, BitmapDescriptor> =
|
||||||
GoogleIconGenerator<TClusterItem>(context).apply {
|
GoogleIconGenerator<TClusterItem>(context).apply { setDefaultViewAndBackground() }
|
||||||
setBackground(context.getDrawable(R.drawable.default_cluster_background))
|
|
||||||
setContentView(LayoutInflater.from(context).inflate(R.layout.view_google_map_cluster_item, null))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun shouldRenderAsCluster(cluster: Cluster<TClusterItem>): Boolean =
|
override fun shouldRenderAsCluster(cluster: Cluster<TClusterItem>): Boolean =
|
||||||
cluster.size > minClusterItemSize
|
cluster.size > minClusterItemSize
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ class GoogleMapManager(mapView: MapView) : AbstractMapManager<MapView, GoogleMap
|
||||||
|
|
||||||
override fun initialize(mapListener: AbstractMapListener<MapView, GoogleMap, LatLng>?) {
|
override fun initialize(mapListener: AbstractMapListener<MapView, GoogleMap, LatLng>?) {
|
||||||
super.initialize(mapListener)
|
super.initialize(mapListener)
|
||||||
mapView.getMapAsync(::initMap)
|
mapView.getMapAsync(this::initMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun initMap(map: GoogleMap) {
|
override fun initMap(map: GoogleMap) {
|
||||||
|
|
|
||||||
|
|
@ -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.NonHierarchicalDistanceBasedAlgorithm
|
||||||
import com.google.maps.android.clustering.algo.PreCachingAlgorithmDecorator
|
import com.google.maps.android.clustering.algo.PreCachingAlgorithmDecorator
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.FlowPreview
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.channels.BufferOverflow
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.debounce
|
import kotlinx.coroutines.flow.debounce
|
||||||
import kotlinx.coroutines.flow.filter
|
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.mapNotNull
|
||||||
import kotlinx.coroutines.flow.onStart
|
import kotlinx.coroutines.flow.onStart
|
||||||
import kotlinx.coroutines.flow.sample
|
import kotlinx.coroutines.flow.sample
|
||||||
|
|
||||||
|
@OptIn(FlowPreview::class)
|
||||||
|
|
||||||
class GooglePlacemarkManager<TClusterItem : ClusterItem>(
|
class GooglePlacemarkManager<TClusterItem : ClusterItem>(
|
||||||
context: Context,
|
context: Context,
|
||||||
private val lifecycleOwner: LifecycleOwner,
|
private val lifecycleOwner: LifecycleOwner,
|
||||||
|
|
@ -47,7 +49,8 @@ class GooglePlacemarkManager<TClusterItem : ClusterItem>(
|
||||||
|
|
||||||
private val markers = mutableListOf<TClusterItem>()
|
private val markers = mutableListOf<TClusterItem>()
|
||||||
private var lastVisibleItems = emptyList<TClusterItem>()
|
private var lastVisibleItems = emptyList<TClusterItem>()
|
||||||
private var onCameraIdleListener: (() -> Unit)? = null
|
|
||||||
|
var onCameraIdleListener: (() -> Unit)? = null
|
||||||
|
|
||||||
var clusterRenderer: GoogleMapItemRenderer<TClusterItem>? = null
|
var clusterRenderer: GoogleMapItemRenderer<TClusterItem>? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
|
|
@ -55,8 +58,6 @@ class GooglePlacemarkManager<TClusterItem : ClusterItem>(
|
||||||
setRenderer(value)
|
setRenderer(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val lock = Any()
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
googleMap.setOnCameraIdleListener(this)
|
googleMap.setOnCameraIdleListener(this)
|
||||||
googleMap.setOnMarkerClickListener(this)
|
googleMap.setOnMarkerClickListener(this)
|
||||||
|
|
@ -65,40 +66,28 @@ class GooglePlacemarkManager<TClusterItem : ClusterItem>(
|
||||||
setOnClusterItemClickListener(clusterItemTapAction)
|
setOnClusterItemClickListener(clusterItemTapAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setItems(items: Collection<TClusterItem>) {
|
@Synchronized
|
||||||
synchronized(lock) {
|
|
||||||
markers.clear()
|
|
||||||
markers.addAll(items)
|
|
||||||
onDataChanged()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun addItems(items: Collection<TClusterItem>) {
|
override fun addItems(items: Collection<TClusterItem>) {
|
||||||
synchronized(lock) {
|
markers.addAll(items)
|
||||||
markers.addAll(items)
|
onDataChanged()
|
||||||
onDataChanged()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
override fun addItem(clusterItem: TClusterItem) {
|
override fun addItem(clusterItem: TClusterItem) {
|
||||||
synchronized(lock) {
|
markers.add(clusterItem)
|
||||||
markers.add(clusterItem)
|
onDataChanged()
|
||||||
onDataChanged()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
override fun removeItem(atmClusterItem: TClusterItem) {
|
override fun removeItem(atmClusterItem: TClusterItem) {
|
||||||
synchronized(lock) {
|
markers.remove(atmClusterItem)
|
||||||
markers.remove(atmClusterItem)
|
onDataChanged()
|
||||||
onDataChanged()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
override fun clearItems() {
|
override fun clearItems() {
|
||||||
synchronized(lock) {
|
markers.clear()
|
||||||
markers.clear()
|
onDataChanged()
|
||||||
onDataChanged()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCameraIdle() {
|
override fun onCameraIdle() {
|
||||||
|
|
@ -106,22 +95,11 @@ class GooglePlacemarkManager<TClusterItem : ClusterItem>(
|
||||||
onCameraIdleEvent.tryEmit(true)
|
onCameraIdleEvent.tryEmit(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onDataChanged() {
|
@Synchronized
|
||||||
onVisibilityChangedEvent.tryEmit(getData())
|
fun setItems(items: Collection<TClusterItem>) {
|
||||||
}
|
markers.clear()
|
||||||
|
markers.addAll(items)
|
||||||
private fun getData(): Pair<VisibleRegion?, List<TClusterItem>> =
|
onDataChanged()
|
||||||
googleMap.projection.visibleRegion to markers
|
|
||||||
|
|
||||||
private fun listenToCameraIdleEvents() {
|
|
||||||
cameraIdleJob = lifecycleOwner.lifecycleScope.launchWhenStarted {
|
|
||||||
onCameraIdleEvent
|
|
||||||
.debounce(CAMERA_DEBOUNCE_MILLI)
|
|
||||||
.flowOn(Dispatchers.Main)
|
|
||||||
.collect {
|
|
||||||
onCameraIdleListener?.invoke()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startClustering() {
|
fun startClustering() {
|
||||||
|
|
@ -131,10 +109,7 @@ class GooglePlacemarkManager<TClusterItem : ClusterItem>(
|
||||||
.debounce(CLUSTERING_START_DEBOUNCE_MILLI)
|
.debounce(CLUSTERING_START_DEBOUNCE_MILLI)
|
||||||
.flowOn(Dispatchers.Default)
|
.flowOn(Dispatchers.Default)
|
||||||
.onStart { emit(getData()) }
|
.onStart { emit(getData()) }
|
||||||
.filter { (visibleRegion, _) -> visibleRegion != null }
|
.mapNotNull { (region, items) -> findItemsInRegion(region, items) }
|
||||||
.map { (visibleRegion, clusteringItems) ->
|
|
||||||
clusteringItems.filter { visibleRegion?.latLngBounds?.contains(it.position) == true }
|
|
||||||
}
|
|
||||||
.sample(CLUSTERING_NEW_LIST_CONSUMING_THROTTLE_MILLIS)
|
.sample(CLUSTERING_NEW_LIST_CONSUMING_THROTTLE_MILLIS)
|
||||||
.catch { emit(lastVisibleItems) }
|
.catch { emit(lastVisibleItems) }
|
||||||
.flowOn(Dispatchers.Main)
|
.flowOn(Dispatchers.Main)
|
||||||
|
|
@ -148,16 +123,32 @@ class GooglePlacemarkManager<TClusterItem : ClusterItem>(
|
||||||
listenToCameraIdleEvents()
|
listenToCameraIdleEvents()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun listenToCameraIdleEvents() {
|
||||||
|
cameraIdleJob = lifecycleOwner.lifecycleScope.launchWhenStarted {
|
||||||
|
onCameraIdleEvent
|
||||||
|
.debounce(CAMERA_DEBOUNCE_MILLI)
|
||||||
|
.flowOn(Dispatchers.Main)
|
||||||
|
.collect {
|
||||||
|
onCameraIdleListener?.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun stopClustering() {
|
fun stopClustering() {
|
||||||
clusteringJob?.cancel()
|
clusteringJob?.cancel()
|
||||||
cameraIdleJob?.cancel()
|
cameraIdleJob?.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setOnCameraIdleListener(listener: () -> Unit) {
|
private fun onDataChanged() {
|
||||||
if (onCameraIdleListener != null) return
|
onVisibilityChangedEvent.tryEmit(getData())
|
||||||
onCameraIdleListener = listener
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
private companion object {
|
||||||
const val CAMERA_DEBOUNCE_MILLI = 50L
|
const val CAMERA_DEBOUNCE_MILLI = 50L
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,11 @@ import com.yandex.runtime.ui_view.ViewProvider
|
||||||
class DefaultIconGenerator<T : PointModel>(private val context: Context) : YandexIconGenerator<T>() {
|
class DefaultIconGenerator<T : PointModel>(private val context: Context) : YandexIconGenerator<T>() {
|
||||||
|
|
||||||
override fun getClusterIcon(cluster: List<T>): ViewProvider {
|
override fun getClusterIcon(cluster: List<T>): ViewProvider {
|
||||||
val view: TextView = LayoutInflater.from(context).inflate(R.layout.default_cluster_view, null) as TextView
|
val textView = LayoutInflater.from(context).inflate(R.layout.default_cluster_view, null).apply {
|
||||||
view.setText(cluster.size.toString())
|
(this as? TextView)?.text = cluster.size.toString()
|
||||||
view.setBackgroundResource(ru.touchin.basemap.R.drawable.marker_default_icon)
|
setBackgroundResource(ru.touchin.basemap.R.drawable.marker_default_icon)
|
||||||
|
}
|
||||||
return ViewProvider(view)
|
return ViewProvider(textView)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getClusterItemIcon(clusterItem: T) = ViewProvider(
|
override fun getClusterItemIcon(clusterItem: T) = ViewProvider(
|
||||||
|
|
|
||||||
|
|
@ -135,10 +135,12 @@ class YandexMapManager(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setMapAllGesturesEnabled(enabled: Boolean) {
|
override fun setMapAllGesturesEnabled(enabled: Boolean) {
|
||||||
map.isRotateGesturesEnabled = enabled
|
with(map) {
|
||||||
map.isScrollGesturesEnabled = enabled
|
isRotateGesturesEnabled = enabled
|
||||||
map.isTiltGesturesEnabled = enabled
|
isScrollGesturesEnabled = enabled
|
||||||
map.isZoomGesturesEnabled = enabled
|
isTiltGesturesEnabled = enabled
|
||||||
|
isZoomGesturesEnabled = enabled
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setMyLocationEnabled(enabled: Boolean) {
|
override fun setMyLocationEnabled(enabled: Boolean) {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,11 @@ class YandexPlacemarkManager<TPoint : PointModel>(
|
||||||
private val markerTapAction: (MapObject, Point) -> Boolean,
|
private val markerTapAction: (MapObject, Point) -> Boolean,
|
||||||
) : ClusterListener, ClusterTapListener, MapObjectTapListener {
|
) : 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 val markers = mutableListOf<TPoint>()
|
||||||
private var placemarkCollection: ClusterizedPlacemarkCollection? = null
|
private var placemarkCollection: ClusterizedPlacemarkCollection? = null
|
||||||
|
|
||||||
|
|
@ -25,8 +30,8 @@ class YandexPlacemarkManager<TPoint : PointModel>(
|
||||||
|
|
||||||
fun addMarkersOnMap(
|
fun addMarkersOnMap(
|
||||||
mapView: MapView,
|
mapView: MapView,
|
||||||
clusterRadius: Double = 42.0,
|
clusterRadius: Double = DEFAULT_CLUSTER_RADIUS,
|
||||||
minZoom: Int = 35
|
minZoom: Int = DEFAULT_MIN_ZOOM
|
||||||
) {
|
) {
|
||||||
removeMarkers()
|
removeMarkers()
|
||||||
|
|
||||||
|
|
@ -52,9 +57,8 @@ class YandexPlacemarkManager<TPoint : PointModel>(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClusterAdded(cluster: Cluster) {
|
override fun onClusterAdded(cluster: Cluster) {
|
||||||
mapItemRenderer.getClusterIcon(cluster.toPlacemarkList())?.let {
|
val clusterIcon = mapItemRenderer.getClusterIcon(cluster.toPlacemarkList())
|
||||||
cluster.appearance.setView(it)
|
clusterIcon?.let(cluster.appearance::setView)
|
||||||
}
|
|
||||||
cluster.addClusterTapListener(this)
|
cluster.addClusterTapListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue