記事ディレクトリ
View#setOnClickListener 実装関数を直接バインドするか、View.OnClickListener インスタンスを渡すことを検討してください。
Kotlin コルーチンの実装
移行
mBind.btnJaClickA.debounceClick(lifecycleScope) {
}
mBind.btnJaClickB.debounceClick(this) {
}
mBind.btnJaClickC.debounceClick(lifecycleScope, originBlock = {
})
mBind.btnJaClickD.debounceClick(this, originBlock = {
})
この実装では、会議の後に実際のイベントがトリガーされます。連続してクリックすると、次のようなログが表示されます。
start
start
start
...
end
ハンドラー + 実行可能実装
グローバル ハンドラー オブジェクトの構築に加えて、ビューでハンドラー インスタンスを直接使用することもできます。
例えばthis.handler.postDelayed()
移行
mBind.btnJaClickD.debounceClickWidthHandler {
}
mBind.btnJaClickE.debounceClickWidthHandler(originBlock = {
})
この実装では、会議の後に実際のイベントがトリガーされます。連続してクリックすると、次のようなログが表示されます。
start
start remove
start remove
...
end
システム時間に基づいた実装
移行
mBind.btnJaClickG.clickWithTrigger {
}
mBind.btnJaClickH.clickWithTrigger(originBlock = {
})
この実装では、実際のイベントが最初にトリガーされ、連続してクリックすると、次のようなログが表示されます。
start
end
start
start
...
setTag/getTag は例外をスローする可能性があります
uuid-string を使用し、その hashCode をタグのキー値として取得します。または、固定 int 値をキー値として使用します。例外が報告される場合があります。この例外の説明を参照してください。使用する特定のリソース ID を定義する必要がありますIllegalArgumentException: The key must be an application-specific resource id;
。 setTag/getTagのキー。
ids.xml :
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item type="id" name="job_id"/>
<item type="id" name="runnable_id"/>
<item type="id" name="trigger_last_time_id"/>
</resources>
応用:
setTag(R.id.job_id, obj)
getTag(R.id.job_id)
最終的な完全なコード
import android.view.View
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.coroutineScope
import com.stone.stoneviewskt.R
import com.stone.stoneviewskt.util.loge
import com.stone.stoneviewskt.util.logi
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
/**
* desc:
* author: stone
* email: [email protected]
* time: 2022/5/21 12:14
*/
/**
* 利用 CoroutineScope 防抖
* 若block中是 CoroutineScope.() 这样的,在每次调用 CoroutineScope 实例对象时,就会触发。
* 会在最后一次 松开超时后, 执行 end
*/
private var <T : View> T.mDebounceSuspendJob: Job?
get() = getTag(R.id.job_id) as? Job
set(value) {
setTag(R.id.job_id, value)
}
fun <T : View> T.debounceClick(coroutineScope: CoroutineScope, delayMs: Long = 600L, block: suspend (T) -> Unit) {
// 对于回调函数 block 中,要考虑 界面销毁,view 为null 的问题。 eg. 异步线程回调、网络请求回调
setOnClickListener {
mDebounceSuspendJob?.cancel()
mDebounceSuspendJob = coroutineScope.launch {
logi("start: ${
System.currentTimeMillis()}")
delay(delayMs)
logi("end: ${
System.currentTimeMillis()}")
block(this@debounceClick)
mDebounceSuspendJob = null
}
}
}
fun <T : View> T.debounceClick(owner: LifecycleOwner, delayMs: Long = 600L, block: suspend (T) -> Unit) {
debounceClick(owner.lifecycle.coroutineScope, delayMs, block)
}
fun <T : View> T.debounceClick(coroutineScope: CoroutineScope, originBlock: View.OnClickListener?, delayMs: Long = 600L) {
originBlock ?: return
debounceClick(coroutineScope, delayMs) {
originBlock.onClick(this) }
}
fun <T : View> T.debounceClick(owner: LifecycleOwner, originBlock: View.OnClickListener?, delayMs: Long = 600L) {
originBlock ?: return
debounceClick(owner.lifecycle.coroutineScope, originBlock, delayMs)
}
/**
* 利用 Handler + Runnable 防抖
*/
private var <T : View> T.mDebounceHandleRunnable: Runnable?
get() = getTag(R.id.runnable_id) as Runnable?
set(value) {
setTag(R.id.runnable_id, value)
}
fun <T : View> T.debounceClickWidthHandler(delayMs: Long = 600L, callback: (T) -> Unit) {
// 对于回调函数 callback 中,要考虑 界面销毁,view 为null 的问题。 eg. 异步线程回调、网络请求回调
setOnClickListener {
logi("start: ${
System.currentTimeMillis()} ${
this.handler}")
mDebounceHandleRunnable?.let {
loge("remove")
this.handler.removeCallbacks(it)
}
mDebounceHandleRunnable = Runnable {
logi("end: ${
System.currentTimeMillis()}")
callback(this)
mDebounceHandleRunnable = null
}
this.handler.postDelayed(mDebounceHandleRunnable!!, delayMs)
}
}
fun <T : View> T.debounceClickWidthHandler(delayMs: Long = 600L, originBlock: View.OnClickListener?) {
originBlock ?: return
debounceClickWidthHandler(delayMs) {
originBlock.onClick(this) }
}
/**
* 记录系统时间,以判断 是否能执行真实点击事件。
* 会一开始就触发真实回调,后面的连续快速点击,不会触发。
*/
private var <T : View> T.triggerLastTime: Long
get() = getTag(R.id.trigger_last_time_id)?.toString()?.toLong() ?: 0
set(value) {
setTag(R.id.trigger_last_time_id, value)
}
private fun <T : View> T.clickEnable(delayMs: Long): Boolean {
var flag = false
val currentClickTime = System.currentTimeMillis()
if (currentClickTime - triggerLastTime >= delayMs) {
flag = true
}
triggerLastTime = currentClickTime
return flag
}
@Suppress("UNCHECKED_CAST")
fun <T : View> T.clickWithTrigger(delayMs: Long = 600, block: (T) -> Unit) {
setOnClickListener {
logi("start: ${
System.currentTimeMillis()}")
if (clickEnable(delayMs)) {
logi("end: ${
System.currentTimeMillis()}")
block(it as T)
}
}
}
fun <T : View> T.clickWithTrigger(originBlock: View.OnClickListener?, time: Long = 600) {
originBlock ?: return
clickWithTrigger(time) {
originBlock.onClick(this) }
}