Kotlin Android 反跳

问题描述 投票:0回答:13

有什么奇特的方法可以用 Kotlin Android 实现

debounce
逻辑吗?

我没有在项目中使用 Rx。

Java中有一种方法,但对我来说太大了。

android kotlin kotlin-android-extensions kotlin-extension kotlinx.coroutines
13个回答
66
投票
我创建了一个包含三个去抖动运算符的

gist,其灵感来自于Patrick这个优雅的解决方案,其中我添加了两个类似的案例:throttleFirst

throttleLatest
。这两个都与 RxJava 类似物(
throttleFirstthrottleLatest)非常相似。

throttleLatest

 的工作方式与 
debounce
 类似,但它按时间间隔运行并返回每个时间间隔的最新数据,这允许您在需要时获取和处理中间数据。

fun <T> throttleLatest( intervalMs: Long = 300L, coroutineScope: CoroutineScope, destinationFunction: (T) -> Unit ): (T) -> Unit { var throttleJob: Job? = null var latestParam: T return { param: T -> latestParam = param if (throttleJob?.isCompleted != false) { throttleJob = coroutineScope.launch { delay(intervalMs) latestParam.let(destinationFunction) } } } }

当您需要立即处理第一个调用,然后跳过后续调用一段时间以避免出现不良行为(例如,避免在 Android 上启动两个相同的活动)时,

throttleFirst

 非常有用。 

fun <T> throttleFirst( skipMs: Long = 300L, coroutineScope: CoroutineScope, destinationFunction: (T) -> Unit ): (T) -> Unit { var throttleJob: Job? = null return { param: T -> if (throttleJob?.isCompleted != false) { throttleJob = coroutineScope.launch { destinationFunction(param) delay(skipMs) } } } }

debounce

 有助于检测一段时间内没有新数据提交时的状态,有效地允许您在输入完成后处理数据。

fun <T> debounce( waitMs: Long = 300L, coroutineScope: CoroutineScope, destinationFunction: (T) -> Unit ): (T) -> Unit { var debounceJob: Job? = null return { param: T -> debounceJob?.cancel() debounceJob = coroutineScope.launch { delay(waitMs) destinationFunction(param) } } }

所有这些运算符都可以按如下方式使用:

val onEmailChange: (String) -> Unit = throttleLatest( 300L, viewLifecycleOwner.lifecycleScope, viewModel::onEmailChanged ) emailView.onTextChanged(onEmailChange)
    

60
投票
对于从

ViewModel

 内部进行的简单方法,您可以在 
viewModelScope
 内启动一项作业,跟踪该作业,如果在作业完成之前出现新值则取消它:

private var searchJob: Job? = null fun searchDebounced(searchText: String) { searchJob?.cancel() searchJob = viewModelScope.launch { delay(500) search(searchText) } }
    

25
投票
我使用

Kotlin Coroutines 中的 callbackFlowdebounce 来实现去抖动。例如,要实现按钮单击事件的去抖动,请执行以下操作:

Button

 上创建扩展方法以生成 
callbackFlow
:

fun Button.onClicked() = callbackFlow<Unit> { setOnClickListener { offer(Unit) } awaitClose { setOnClickListener(null) } }

订阅生命周期感知活动或片段中的事件。以下代码片段每 250 毫秒消除一次点击事件:

buttonFoo .onClicked() .debounce(250) .onEach { doSomethingRadical() } .launchIn(lifecycleScope)
    

24
投票
一个更简单和通用的解决方案是使用一个返回执行去抖逻辑的函数的函数,并将其存储在 val 中。

fun <T> debounce(delayMs: Long = 500L, coroutineContext: CoroutineContext, f: (T) -> Unit): (T) -> Unit { var debounceJob: Job? = null return { param: T -> if (debounceJob?.isCompleted != false) { debounceJob = CoroutineScope(coroutineContext).launch { delay(delayMs) f(param) } } } }
现在可以与:

一起使用

val handleClickEventsDebounced = debounce<Unit>(500, coroutineContext) { doStuff() } fun initViews() { myButton.setOnClickListener { handleClickEventsDebounced(Unit) } }
    

14
投票
我根据堆栈溢出的旧答案创建了一个扩展函数:

fun View.clickWithDebounce(debounceTime: Long = 600L, action: () -> Unit) { this.setOnClickListener(object : View.OnClickListener { private var lastClickTime: Long = 0 override fun onClick(v: View) { if (SystemClock.elapsedRealtime() - lastClickTime < debounceTime) return else action() lastClickTime = SystemClock.elapsedRealtime() } }) }

使用以下代码查看 onClick:

buttonShare.clickWithDebounce { // Do anything you want }
    

9
投票
感谢

https://medium.com/@pro100svitlo/edittext-debounce-with-kotlin-coroutines-fd134d54f4e9https://stackoverflow.com/a/50007453/2914140我写了这段代码:

private var textChangedJob: Job? = null private lateinit var textListener: TextWatcher override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { textListener = object : TextWatcher { private var searchFor = "" // Or view.editText.text.toString() override fun afterTextChanged(s: Editable?) {} override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { val searchText = s.toString().trim() if (searchText != searchFor) { searchFor = searchText textChangedJob?.cancel() textChangedJob = launch(Dispatchers.Main) { delay(500L) if (searchText == searchFor) { loadList(searchText) } } } } } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) editText.setText("") loadList("") } override fun onResume() { super.onResume() editText.addTextChangedListener(textListener) } override fun onPause() { editText.removeTextChangedListener(textListener) super.onPause() } override fun onDestroy() { textChangedJob?.cancel() super.onDestroy() }

我没有在这里包含

coroutineContext

,所以如果没有设置,它可能无法工作。有关信息,请参阅
使用 Kotlin 1.3 迁移到 Android 中的 Kotlin 协程


8
投票
您可以使用

kotlin 协程 来实现这一点。 这是一个例子

请注意,

协程在 kotlin 1.1+ 中处于实验阶段,并且可能会在即将发布的 kotlin 版本中进行更改。 更新

Kotlin 1.3

发布以来,协程现已稳定。


5
投票
RecyclerView.ViewHolder

视图时。


例如

fun View.debounceClick(debounceTime: Long = 1000L, action: () -> Unit) { setOnClickListener { when { tag != null && (tag as Long) > System.currentTimeMillis() -> return@setOnClickListener else -> { tag = System.currentTimeMillis() + debounceTime action() } } } }

用途:

debounceClick { // code block... }



4
投票

很好的答案。这是我使用 EditText 实现的动态搜索栏。这提供了巨大的性能改进,因此搜索查询不会立即在用户文本输入上执行。

fun AppCompatEditText.textInputAsFlow() = callbackFlow { val watcher: TextWatcher = doOnTextChanged { textInput: CharSequence?, _, _, _ -> offer(textInput) } awaitClose { [email protected](watcher) } }

        searchEditText
                .textInputAsFlow()
                .map {
                    val searchBarIsEmpty: Boolean = it.isNullOrBlank()
                    searchIcon.isVisible = searchBarIsEmpty
                    clearTextIcon.isVisible = !searchBarIsEmpty
                    viewModel.isLoading.value = true
                    return@map it
                }
                .debounce(750) // delay to prevent searching immediately on every character input
                .onEach {
                    viewModel.filterPodcastsAndEpisodes(it.toString())
                    viewModel.latestSearch.value = it.toString()
                    viewModel.activeSearch.value = !it.isNullOrBlank()
                    viewModel.isLoading.value = false
                }
                .launchIn(lifecycleScope)
    }


2
投票

@ExperimentalCoroutinesApi // This is still experimental API fun ImageButton.onClicked() = callbackFlow<Unit> { setOnClickListener { offer(Unit) } awaitClose { setOnClickListener(null) } } // Listener for button val someButton = someView.findViewById<ImageButton>(R.id.some_button) someButton .onClicked() .debounce(500) // 500ms debounce time .onEach { clickAction() } .launchIn(lifecycleScope)



1
投票
用途:

ActionDelayer.delay { println("重复...") }


1
投票

定义单例对象

object DebounceHelper { private var job: Job? = null fun <T> debounce( delayMs: Long = 500L, scope: CoroutineScope, func: (T) -> Unit ): (T) -> Unit { job?.cancel() return { param: T -> job = scope.launch { delay(delayMs) func(param) } } } }

如何使用

DebounceHelper.debounce<Unit>(2000, scope) { anotherFunc() }.invoke(Unit)



0
投票

fun EditText.debounce(delay: Long, action: (CharSequence?) -> Unit) { doAfterTextChanged { text -> val counter = getTag(id) as? Int ?: 0 handler.removeCallbacksAndMessages(counter) handler.postDelayed(delay, ++counter) { action(text) } setTag(id, counter) } }

用途

val editText = findViewById<EditText>(R.id.editText) editText.debounce(500) { if (it.isNotEmpty()) { // Submit the form } }

© www.soinside.com 2019 - 2024. All rights reserved.