Как я могу отклонить setOnClickListener на 1 секунду с помощью Kotlin Coroutines?
Когда пользователь быстро нажимает на кнопку, метод showDialog() отображается несколько раз друг над другом, поэтому, когда вы закрываете его, за ним появляется еще один. Я ищу способ игнорировать второе нажатие в течение 1 секунды без использования обработчика или проверки времени предыдущего нажатия.
//Button that opens a dialog
button.setOnClickListener {
showDialog()
}
Я ищу решение, использующее сопрограммы Kotlin или потоки Kotlin для будущих реализаций.
4 ответа
Лучше использовать для этого простой флаг вместо задержки, поскольку это не очень удобно для пользователя.
Но если вы хотите использовать сопрограммы, вы можете просто использовать поток Kotlin Coroutine, чтобы применить это:
Сначала я создал функцию расширения для события щелчка, которое возвращает поток сопрограммы. как это:
fun View.clicks(): Flow<Unit> = callbackFlow {
setOnClickListener {
offer(Unit)
}
awaitClose { setOnClickListener(null) }
}
Теперь все, что вам нужно, это вызвать функцию в onCreate следующим образом:
button.clicks().debounce(1000).onEach { println("clicked") }.launchIn(GlobalScope)
Не забудьте добавить эти строки в файл build.gradle:
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3'
Редактировать:
Аналог Flow оператора throttleFirst еще не реализован в сопрограммах kotlin. однако можно реализовать с помощью функций расширения:
@FlowPreview
@ExperimentalCoroutinesApi
fun <T> Flow<T>.throttleFirst(windowDuration: Long): Flow<T> = flow {
var lastEmissionTime = 0L
collect { upstream ->
val currentTime = System.currentTimeMillis()
val mayEmit = currentTime - lastEmissionTime > windowDuration
if (mayEmit)
{
lastEmissionTime = currentTime
emit(upstream)
}
}
}
Изменения заключаются в следующем:
binding.button.clicks().throttleFirst(1250)
.onEach {
//delay(100)
showDialog()
}.launchIn(GlobalScope)
Кроме того, вы можете использовать delay(), чтобы справиться с этим. Вы можете легко изменить значение этих параметров в соответствии с вашими потребностями.
Курпрограммы излишни для такой тривиальной вещи, как дебаунс:
class DebounceOnClickListener(
private val interval: Long,
private val listenerBlock: (View) -> Unit
): View.OnClickListener {
private var lastClickTime = 0L
override fun onClick(v: View) {
val time = System.currentTimeMillis()
if (time - lastClickTime >= interval) {
lastClickTime = time
listenerBlock(v)
}
}
}
fun View.setOnClickListener(debounceInterval: Long, listenerBlock: (View) -> Unit) =
setOnClickListener(DebounceOnClickListener(debounceInterval, listenerBlock))
Применение:
myButton.setOnClickListener(1000L) { doSomething() }
Я честно рекомендую Corbind
С этой замечательной библиотекой вы можете забыть о setOnClickListener
и просто обрабатывать потоки вроде
binding.myButton.clicks()
.debounce(500)
.onEach { doSomethingImportant() }
.launchIn(viewLifecycleOwner.lifecycleScope)
Его действительно легко использовать, а с привязкой к представлению приложение становится очень простым. Надеюсь, это поможет, удачного кодирования!
Чтобы улучшить решение Morteza Nedaei:
Если вы попробуете этот код в течение периода времени 2000 мс и продолжительности окна 500 мс, он будет запущен только 4 раза; а должно быть 5 раз.
Мортеза:
237: #1-(0-500)
839: #2-(501-1000)
1440: #3-(1001-1500)
????: #?
2041: № 5-(2001-2500)
Улучшенный:
236: #1-(0-500)
637: #2-(501-1000)
1038: #3-(1001-1500)
1639: #4-(1501-2000)
2039: #5-(2001-2500)
fun <T> Flow<T>.throttleFirst(windowDuration: Long): Flow<T> = flow {
var windowStartTime = System.currentTimeMillis()
var isEmitted = false
collect { value ->
val currentTime = System.currentTimeMillis()
val delta = currentTime - windowStartTime
if (delta >= windowDuration) {
windowStartTime += delta / windowDuration * windowDuration
isEmitted = false
}
if (isEmitted.not()) {
emit(value)
isEmitted = true
}
}
}