Skip to content

Instantly share code, notes, and snippets.

@RafhaanShah
Last active June 29, 2022 15:36
Show Gist options
  • Save RafhaanShah/14136eeedea87b64178925a9f45de049 to your computer and use it in GitHub Desktop.
Save RafhaanShah/14136eeedea87b64178925a9f45de049 to your computer and use it in GitHub Desktop.
Android onHoldListener that calls a callback periodically while a view is being held, works for clicks as well.
fun View.setOnHoldListener(
firstClickDelayMillis: Long = 250L,
tickDelayMillis: Long = 100L,
onRelease: () -> Unit = {},
onTick: (Int) -> Unit
) {
val lifeCycleOwner = findViewTreeLifecycleOwner() ?: return
var job: Job? = null
// cancel this job if the lifeCycleOwner is ever paused
// just in case we reach this state without MotionEvent.ACTION_UP on the touchListener
lifeCycleOwner.lifecycle.addObserver(object : LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
when (event) {
Lifecycle.Event.ON_PAUSE -> {
job?.cancel()
job = null
}
else -> {}
}
}
})
// added for accessibility, resolves lint issue ClickableViewAccessibility
setOnClickListener {
job?.cancel() // just in case the job is already running
job = lifeCycleOwner.lifecycleScope.launch {
var counter = 0
onTick(counter++)
delay(firstClickDelayMillis)
// call onTick() every tick, while the job is active
while (true) {
ensureActive()
onTick(counter++)
delay(tickDelayMillis)
}
}
}
setOnTouchListener { _, event ->
return@setOnTouchListener when (event.action) {
// triggers onClick() when this view is touched
MotionEvent.ACTION_DOWN -> {
isPressed = true
performClick()
true
}
// cancels the job and ticking the view is no longer being touched
MotionEvent.ACTION_UP -> {
isPressed = false
job?.cancel()
job = null
onRelease()
true
}
else -> false
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment