I have a listener for zooming in and out mapview:
class ZoomMapListener(
mapView: MapView,
private val zoom: Zoom,
) : View.OnClickListener {
private val localMapView = WeakReference(mapView)
private var clickCount = 0
override fun onClick(view: View?) {
clickCount
}
fun moveCamera() {
val mapView = localMapView.get()
mapView?.let {
var cameraPosition = it.map.cameraPosition
val zoom = if (zoom == IN) {
cameraPosition.zoom (1.0f * clickCount)
} else {
cameraPosition.zoom - (1.0f * clickCount)
}
cameraPosition = CameraPosition(
cameraPosition.target,
zoom,
cameraPosition.azimuth,
cameraPosition.tilt,
)
clickCount = 0
it.map.move(cameraPosition, Animation(Animation.Type.SMOOTH, 0.5f), null)
}
}
}
enum class Zoom {
IN,
OUT
}
In order if user clicks on button several times I've decided to use debounce operator from another answer(https://stackoverflow.com/a/60234167/13236614), so if there are five clicks, for example, camera makes fivefold increase in one operation.
The extension function:
@FlowPreview
@ExperimentalCoroutinesApi
fun View.setDebouncedListener(
listener: ZoomMapListener,
lifecycleCoroutineScope: LifecycleCoroutineScope,
) {
callbackFlow {
setOnClickListener {
listener.onClick(this@setDebouncedListener)
offer(Unit)
}
awaitClose {
setOnClickListener(null)
}
}
.debounce(500L)
.onEach { listener.moveCamera() }
.launchIn(lifecycleCoroutineScope)
}
And how I use it in my fragment:
zoomInMapButton.setDebouncedListener(ZoomMapListener(mapView, Zoom.IN), lifecycleScope)
I think it all looks kinda bad and I'm doubting because of @FlowPreview
annotation, so is there a way to make it right in the custom listener class at least?
CodePudding user response:
Using something with @FlowPreview
or @ExperimentalCoroutinesApi
is sort of like using a deprecated function, because it's possible it will stop working as expected or be removed in a future version of the library. They are relatively stable, but you'll need to check them each time you update your core Kotlin libraries.
My coroutine-free answer on that other question is more like throttleFirst
than debounce
, because it doesn't delay the first click.
I think you can directly handle debounce in your ZoomListener class by changing only one line of code! Replace clickCount
with if ( clickCount == 1) v.postDelayed(::moveCamera, interval)
.
Disclaimer: I didn't test this.
The strategy here is on the first click to immediately post a delayed call to moveCamera()
. If any clicks come in during that delay time, they do not post new delayed calls, because their contribution is accounted for in the clickCount
that moveCamera()
will use when the delay is over.
I also did some cleanup in moveCamera()
, but it's functionally the same. In my opinion, ?.let
should not be used for local variables because you can take advantage of smart casting (or early returns) for local variables, so you can keep your code more readable and less nested.
class ZoomMapListener(
mapView: MapView,
private val zoom: Zoom,
private val interval: Long
) : View.OnClickListener {
private val localMapView = WeakReference(mapView)
private var clickCount = 0
override fun onClick(v: View) {
if ( clickCount == 1) v.postDelayed(::moveCamera, interval)
}
fun moveCamera() {
val map = localMapView.get()?.map ?: return
val multiplier = if (zoom == IN) 1f else -1f
val newCameraPosition = CameraPosition.builder(map.cameraPosition)
.zoom(map.cameraPosition.zoom multiplier * clickCount)
.build()
clickCount = 0
map.move(newCameraPosition, Animation(Animation.Type.SMOOTH, 0.5f), null)
}
}
CodePudding user response:
...so is there a way to make it right in the custom listener class at least?
If I'm not mistaken, you want to make the debounce in the method of onClick
, right?
override fun onClick(view: View?) {
// debounce
clickCount
}
If that, why not use this reference link [it's in the link you provided] https://stackoverflow.com/a/60193549/11835023