Android のアンチリピートクリック (Kotlin コルーチンの実装とハンドラーの実装)

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) }
}


おすすめ

転載: blog.csdn.net/jjwwmlp456/article/details/124897867