Kotlin Android辩护

Kir*_*tov 11 android kotlin kotlin-extension kotlin-android-extensions kotlinx.coroutines

有没有任何奇特的方法来实现debounceKotlin Android的逻辑?

我没有在项目中使用Rx.

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

Chr*_*ute 19

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

private var searchJob: Job? = null

fun searchDebounced(searchText: String) {
    searchJob?.cancel()
    searchJob = viewModelScope.launch {
        delay(500)
        search(searchText)
    }
}
Run Code Online (Sandbox Code Playgroud)


mas*_*wok 18

我使用的是callbackFlow防抖动科特林协同程序来实现去抖动。例如,要实现按钮单击事件的去抖动,请执行以下操作:

创建一个扩展方法Button来产生一个callbackFlow

fun Button.onClicked() = callbackFlow<Unit> {
    setOnClickListener { offer(Unit) }
    awaitClose { setOnClickListener(null) }
}
Run Code Online (Sandbox Code Playgroud)

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

buttonFoo
    .onClicked()
    .debounce(250)
    .onEach { doSomethingRadical() }
    .launchIn(lifecycleScope)
Run Code Online (Sandbox Code Playgroud)


SAN*_*NAT 10

我从堆栈溢出的旧答案中创建了一个扩展函数:

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()
        }
    })
}
Run Code Online (Sandbox Code Playgroud)

使用以下代码查看 onClick:

buttonShare.clickWithDebounce { 
   // Do anything you want
}
Run Code Online (Sandbox Code Playgroud)

  • 这是一种节流方法,而不是去抖方法。节流保留第一个输入。去抖仅保留最后的输入。这个答案对于按钮很有用,但对于切换开关或任何其他具有状态的东西没有那么有用。 (3认同)
  • 是的!我不知道为什么这里有这么多答案使协程变得复杂。 (2认同)

Die*_*one 8

您可以使用kotlin协同程序来实现这一目标. 这是一个例子.

请注意,协程在kotlin 1.1+上实验性的,并且可能会在即将推出的kotlin版本中进行更改.

UPDATE

Kotlin 1.3发布以来,协同程序现已稳定.

  • 不幸的是,由于1.3.x已经发布,这似乎已经过时了. (2认同)

Coo*_*ind 8

感谢https://medium.com/@pro100svitlo/edittext-debounce-with-kotlin-coroutines-fd134d54f4e9/sf/answers/3500521741/,我编写了以下代码:

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()
}
Run Code Online (Sandbox Code Playgroud)

我没有包括coroutineContext在这里,所以如果没有设置的话,它可能不会起作用。有关信息,请参阅使用Kotlin 1.3在Android中迁移到Kotlin协程


小智 7

我创建了一个要点与启发3个防抖动运营商这个优雅的解决方案,从帕特里克,我增加了两个类似的情况:throttleFirstthrottleLatest。这两者都是非常相似,他们RxJava类似物(throttleFirstthrottleLatest)。

throttleLatestdebounce它的工作原理与之类似,但是它按时间间隔运行,并返回每个数据的最新数据,从而使您可以根据需要获取和处理中间数据。

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)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

throttleFirst 当您需要立即处理第一个调用,然后跳过一些后续调用一段时间以免发生不良行为(例如,避免在Android上启动两个相同的活动)时,此功能非常有用。

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)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • 请注意,“onTextChanged()”不在 Android SDK 中,尽管[这篇文章](https://theengineerscafe.com/useful-kotlin-extension-functions-android/)包含一个兼容的实现。 (2认同)

Pat*_*ick 6

一个更简单通用的解决方案是使用一个函数,该函数返回执行反跳逻辑的函数,并将其存储在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)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在可以将其与:

val handleClickEventsDebounced = debounce<Unit>(500, coroutineContext) {
    doStuff()
}

fun initViews() {
   myButton.setOnClickListener { handleClickEventsDebounced(Unit) }
}
Run Code Online (Sandbox Code Playgroud)

  • 此实现不会去抖,但会限制。 (2认同)

Rod*_*roz 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()
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

debounceClick {
    // code block...
}
Run Code Online (Sandbox Code Playgroud)


Rvb*_*b84 5

@大师作品,

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

    fun AppCompatEditText.textInputAsFlow() = callbackFlow {
        val watcher: TextWatcher = doOnTextChanged { textInput: CharSequence?, _, _, _ ->
            offer(textInput)
        }
        awaitClose { this@textInputAsFlow.removeTextChangedListener(watcher) }
    }
Run Code Online (Sandbox Code Playgroud)
        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)
    }
Run Code Online (Sandbox Code Playgroud)