mirror of
https://github.com/ev-map/EVMap.git
synced 2026-04-23 23:57:08 -04:00
animate marker appearance and disappearance
This commit is contained in:
@@ -38,8 +38,7 @@ import com.johan.evmap.api.ChargeLocationCluster
|
||||
import com.johan.evmap.api.ChargepointListItem
|
||||
import com.johan.evmap.api.ChargerPhoto
|
||||
import com.johan.evmap.databinding.FragmentMapBinding
|
||||
import com.johan.evmap.ui.ClusterIconGenerator
|
||||
import com.johan.evmap.ui.getBitmapDescriptor
|
||||
import com.johan.evmap.ui.*
|
||||
import com.johan.evmap.viewmodel.MapPosition
|
||||
import com.johan.evmap.viewmodel.MapViewModel
|
||||
import com.johan.evmap.viewmodel.viewModelFactory
|
||||
@@ -57,6 +56,9 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
private var markers: Map<Marker, ChargeLocation> = emptyMap()
|
||||
private var clusterMarkers: List<Marker> = emptyList()
|
||||
|
||||
private lateinit var clusterIconGenerator: ClusterIconGenerator
|
||||
private lateinit var chargerIconGenerator: ChargerIconGenerator
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
@@ -67,6 +69,8 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
binding.vm = vm
|
||||
|
||||
fusedLocationClient = LocationServices.getFusedLocationProviderClient(requireContext())
|
||||
clusterIconGenerator = ClusterIconGenerator(requireContext())
|
||||
chargerIconGenerator = ChargerIconGenerator(requireContext())
|
||||
|
||||
setHasOptionsMenu(true)
|
||||
|
||||
@@ -214,6 +218,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
|
||||
}
|
||||
map.setOnMapClickListener {
|
||||
vm.chargerSparse.value = null
|
||||
@@ -270,40 +275,51 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
|
||||
private fun updateMap(chargepoints: List<ChargepointListItem>) {
|
||||
val map = this.map ?: return
|
||||
markers.keys.forEach { it.remove() }
|
||||
clusterMarkers.forEach { it.remove() }
|
||||
|
||||
val context = context ?: return
|
||||
val iconGenerator = ClusterIconGenerator(context)
|
||||
val clusters = chargepoints.filterIsInstance<ChargeLocationCluster>()
|
||||
val chargers = chargepoints.filterIsInstance<ChargeLocation>()
|
||||
|
||||
markers = chargers.map { charger ->
|
||||
map.addMarker(
|
||||
val chargepointIds = chargers.map { it.id }.toSet()
|
||||
markers = markers.filter {
|
||||
if (!chargepointIds.contains(it.value.id)) {
|
||||
val tint = getMarkerTint(it.value)
|
||||
if (it.key.isVisible) {
|
||||
animateMarkerDisappear(it.key, tint, chargerIconGenerator)
|
||||
} else {
|
||||
it.key.remove()
|
||||
}
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
markers = markers + chargers.filter {
|
||||
!markers.containsValue(it)
|
||||
}.map { charger ->
|
||||
val tint = getMarkerTint(charger)
|
||||
val marker = map.addMarker(
|
||||
MarkerOptions()
|
||||
.position(LatLng(charger.coordinates.lat, charger.coordinates.lng))
|
||||
.icon(
|
||||
getBitmapDescriptor(
|
||||
R.drawable.ic_map_marker_charging, when {
|
||||
charger.maxPower >= 100 -> R.color.charger_100kw
|
||||
charger.maxPower >= 43 -> R.color.charger_43kw
|
||||
charger.maxPower >= 20 -> R.color.charger_20kw
|
||||
charger.maxPower >= 11 -> R.color.charger_11kw
|
||||
else -> R.color.charger_low
|
||||
}, context
|
||||
chargerIconGenerator.getBitmapDescriptor(
|
||||
R.drawable.ic_map_marker_charging,
|
||||
tint
|
||||
)
|
||||
)
|
||||
) to charger
|
||||
)
|
||||
animateMarkerAppear(marker, tint, chargerIconGenerator)
|
||||
|
||||
marker to charger
|
||||
}.toMap()
|
||||
clusterMarkers = clusters.map { cluster ->
|
||||
map.addMarker(
|
||||
MarkerOptions()
|
||||
.position(LatLng(cluster.coordinates.lat, cluster.coordinates.lng))
|
||||
.icon(BitmapDescriptorFactory.fromBitmap(iconGenerator.makeIcon(cluster.clusterCount.toString())))
|
||||
.icon(BitmapDescriptorFactory.fromBitmap(clusterIconGenerator.makeIcon(cluster.clusterCount.toString())))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<out String>,
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
package com.johan.evmap.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.view.ViewGroup
|
||||
import com.google.maps.android.ui.IconGenerator
|
||||
import com.google.maps.android.ui.SquareTextView
|
||||
import com.johan.evmap.R
|
||||
|
||||
class ClusterIconGenerator(context: Context) : IconGenerator(context) {
|
||||
init {
|
||||
setBackground(context.getDrawable(R.drawable.marker_cluster_bg))
|
||||
setContentView(makeSquareTextView(context))
|
||||
setTextAppearance(R.style.TextAppearance_AppCompat_Inverse)
|
||||
}
|
||||
|
||||
private fun makeSquareTextView(context: Context): SquareTextView? {
|
||||
val density = context.resources.displayMetrics.density
|
||||
val twelveDpi = (12.0f * density).toInt()
|
||||
|
||||
return SquareTextView(context).apply {
|
||||
layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
id = com.google.maps.android.R.id.amu_text
|
||||
setPadding(twelveDpi, twelveDpi, twelveDpi, twelveDpi)
|
||||
}
|
||||
}
|
||||
}
|
||||
96
app/src/main/java/com/johan/evmap/ui/IconGenerators.kt
Normal file
96
app/src/main/java/com/johan/evmap/ui/IconGenerators.kt
Normal file
@@ -0,0 +1,96 @@
|
||||
package com.johan.evmap.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.DrawableCompat
|
||||
import com.google.android.gms.maps.model.BitmapDescriptor
|
||||
import com.google.android.gms.maps.model.BitmapDescriptorFactory
|
||||
import com.google.maps.android.ui.IconGenerator
|
||||
import com.google.maps.android.ui.SquareTextView
|
||||
import com.johan.evmap.R
|
||||
|
||||
class ClusterIconGenerator(context: Context) : IconGenerator(context) {
|
||||
init {
|
||||
setBackground(context.getDrawable(R.drawable.marker_cluster_bg))
|
||||
setContentView(makeSquareTextView(context))
|
||||
setTextAppearance(R.style.TextAppearance_AppCompat_Inverse)
|
||||
}
|
||||
|
||||
private fun makeSquareTextView(context: Context): SquareTextView? {
|
||||
val density = context.resources.displayMetrics.density
|
||||
val twelveDpi = (12.0f * density).toInt()
|
||||
|
||||
return SquareTextView(context).apply {
|
||||
layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
id = com.google.maps.android.R.id.amu_text
|
||||
setPadding(twelveDpi, twelveDpi, twelveDpi, twelveDpi)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ChargerIconGenerator(val context: Context) {
|
||||
data class BitmapData(val id: Int, val tint: Int, val scale: Int, val alpha: Int)
|
||||
|
||||
val cache = hashMapOf<BitmapData, Bitmap>()
|
||||
val oversize = 1.5f
|
||||
|
||||
fun getBitmapDescriptor(
|
||||
@DrawableRes id: Int,
|
||||
@ColorRes tint: Int,
|
||||
scale: Int = 255,
|
||||
alpha: Int = 255
|
||||
): BitmapDescriptor? {
|
||||
val data = BitmapData(id, tint, scale, alpha)
|
||||
if (cache.containsKey(data)) {
|
||||
return BitmapDescriptorFactory.fromBitmap(cache[data])
|
||||
} else {
|
||||
val bitmap = generateBitmap(data)
|
||||
cache[data] = bitmap
|
||||
return BitmapDescriptorFactory.fromBitmap(bitmap)
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateBitmap(data: BitmapData): Bitmap {
|
||||
val vd: Drawable = context.getDrawable(data.id)!!
|
||||
|
||||
DrawableCompat.setTint(vd, ContextCompat.getColor(context, data.tint));
|
||||
DrawableCompat.setTintMode(vd, PorterDuff.Mode.MULTIPLY);
|
||||
|
||||
val leftPadding = vd.intrinsicWidth * (oversize - 1) / 2
|
||||
val topPadding = vd.intrinsicWidth * (oversize - 1)
|
||||
vd.setBounds(
|
||||
leftPadding.toInt(), topPadding.toInt(),
|
||||
leftPadding.toInt() + vd.intrinsicWidth,
|
||||
topPadding.toInt() + vd.intrinsicHeight
|
||||
)
|
||||
vd.alpha = data.alpha
|
||||
|
||||
val bm = Bitmap.createBitmap(
|
||||
(vd.intrinsicWidth * oversize).toInt(), (vd.intrinsicHeight * oversize).toInt(),
|
||||
Bitmap.Config.ARGB_8888
|
||||
)
|
||||
val canvas = Canvas(bm)
|
||||
|
||||
val scale = data.scale / 255f
|
||||
canvas.scale(
|
||||
scale,
|
||||
scale,
|
||||
leftPadding + vd.intrinsicWidth / 2f,
|
||||
topPadding + vd.intrinsicHeight.toFloat()
|
||||
)
|
||||
|
||||
vd.draw(canvas)
|
||||
return bm
|
||||
}
|
||||
}
|
||||
70
app/src/main/java/com/johan/evmap/ui/MarkerUtils.kt
Normal file
70
app/src/main/java/com/johan/evmap/ui/MarkerUtils.kt
Normal file
@@ -0,0 +1,70 @@
|
||||
package com.johan.evmap.ui
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import androidx.core.animation.addListener
|
||||
import androidx.interpolator.view.animation.FastOutLinearInInterpolator
|
||||
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
|
||||
import com.google.android.gms.maps.model.Marker
|
||||
import com.johan.evmap.R
|
||||
import com.johan.evmap.api.ChargeLocation
|
||||
|
||||
fun getMarkerTint(charger: ChargeLocation): Int = when {
|
||||
charger.maxPower >= 100 -> R.color.charger_100kw
|
||||
charger.maxPower >= 43 -> R.color.charger_43kw
|
||||
charger.maxPower >= 20 -> R.color.charger_20kw
|
||||
charger.maxPower >= 11 -> R.color.charger_11kw
|
||||
else -> R.color.charger_low
|
||||
}
|
||||
|
||||
fun animateMarkerAppear(
|
||||
marker: Marker,
|
||||
tint: Int,
|
||||
gen: ChargerIconGenerator
|
||||
) {
|
||||
ValueAnimator.ofInt(0, 255).apply {
|
||||
duration = 250
|
||||
interpolator = LinearOutSlowInInterpolator()
|
||||
addUpdateListener { animationState ->
|
||||
if (!marker.isVisible) {
|
||||
cancel()
|
||||
return@addUpdateListener
|
||||
}
|
||||
val scale = animationState.animatedValue as Int
|
||||
marker.setIcon(
|
||||
gen.getBitmapDescriptor(
|
||||
R.drawable.ic_map_marker_charging,
|
||||
tint,
|
||||
scale = scale
|
||||
)
|
||||
)
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
fun animateMarkerDisappear(
|
||||
marker: Marker,
|
||||
tint: Int,
|
||||
gen: ChargerIconGenerator
|
||||
) {
|
||||
ValueAnimator.ofInt(255, 0).apply {
|
||||
duration = 200
|
||||
interpolator = FastOutLinearInInterpolator()
|
||||
addUpdateListener { animationState ->
|
||||
if (!marker.isVisible) {
|
||||
cancel()
|
||||
return@addUpdateListener
|
||||
}
|
||||
val scale = animationState.animatedValue as Int
|
||||
marker.setIcon(
|
||||
gen.getBitmapDescriptor(
|
||||
R.drawable.ic_map_marker_charging,
|
||||
tint,
|
||||
scale = scale
|
||||
)
|
||||
)
|
||||
}
|
||||
addListener(onEnd = {
|
||||
marker.remove()
|
||||
})
|
||||
}.start()
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.johan.evmap.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.drawable.Drawable
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.DrawableCompat
|
||||
import com.google.android.gms.maps.model.BitmapDescriptor
|
||||
import com.google.android.gms.maps.model.BitmapDescriptorFactory
|
||||
|
||||
|
||||
fun getBitmapDescriptor(@DrawableRes id: Int, @ColorRes tint: Int, context: Context): BitmapDescriptor? {
|
||||
val vd: Drawable = context.getDrawable(id)!!
|
||||
|
||||
DrawableCompat.setTint(vd, ContextCompat.getColor(context, tint));
|
||||
DrawableCompat.setTintMode(vd, PorterDuff.Mode.MULTIPLY);
|
||||
|
||||
vd.setBounds(0, 0, vd.intrinsicWidth, vd.intrinsicHeight)
|
||||
val bm = Bitmap.createBitmap(vd.intrinsicWidth, vd.intrinsicHeight, Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(bm)
|
||||
vd.draw(canvas)
|
||||
return BitmapDescriptorFactory.fromBitmap(bm)
|
||||
}
|
||||
Reference in New Issue
Block a user