Как я могу отклонить 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
        }
    }
}
Другие вопросы по тегам