Kotlin Androidでdebounce
ロジックを実装するための素晴らしい方法はありますか?
プロジェクトでRxを使用していません。
Java には方法がありますが、ここでは私にとっては大きすぎます。
kotlinコルーチンを使用してそれを達成できます。 ここに例があります 。
coroutinesは kotlin 1.1+で実験的 であり、今後のkotlinバージョンでは変更される可能性があることに注意してください。
Kotlin 1. リリースなので、コルーチンは安定しています。
https://medium.com/@pro100svitlo/edittext-debounce-with-kotlin-coroutines-fd134d54f4e9 および https://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
を含めなかったので、設定しないとおそらく動作しません。詳細については、 Android with Kotlin 1.3 )のKotlinコルーチンへの移行を参照してください。
より単純で一般的な解決策は、デバウンスロジックを実行する関数を返す関数を使用し、それを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) }
}
このエレガントなソリューション from Patrick に触発された3つのデバウンス演算子で Gist を作成しました。ここで、さらに2つの類似したケースを追加しました:throttleFirst
およびthrottleLatest
。これらは両方とも、RxJavaの類似物( throttleFirst 、 throttleLatest )と非常によく似ています。
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)
}
}
}
}
throttleFirst
は、最初の呼び出しをすぐに処理し、その後の呼び出しをしばらくスキップして望ましくない動作を避ける必要がある場合に便利です(たとえば、Androidで2つの同一のアクティビティを開始しないでください)。
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)
スタックオーバーフローの古い回答から単一の拡張関数を作成しました。
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
}