Android Developer

防快速点击处理代码

旧版的问题

  • 共享时间

  • 点击完 View A后,在限定时间无法点击View B

最新版的使用方式

针对Kotlin和新代码

		addTestItem("验证DebounceClick", {}).setDebouncedOnClickListener {
            smartLog {
                "验证DebounceClick v=$it"
            }
        }

        addTestItem("验证DebounceClick 2秒延迟", {}).setDebouncedOnClickListener(2000L) {
            smartLog {
                "验证DebounceClick 2秒延迟 v=$it"
            }
        }

针对旧代码或者Java代码

@Override
    public void onItemClickListener(View v, Object data, int position) {
        if (data == null) {
            return;
        }
        if (ViewClickDebouncer.INSTANCE.isFastClick(v)) {
            LogUtils.debugInfo("onItemClickListener isFastClick");
            return;
        }

        //实际的处理逻辑
}

API 源码

fun View?.setDebouncedOnClickListener(debounceTimeOutInMills: Long? = null, onClickListener: ((View?) -> Unit)?) {
    this ?: return
    onClickListener ?: return
    ViewClickDebouncer.setupViewDebounceTimeout(this, debounceTimeOutInMills)
    this.doOnClick {
        if (!ViewClickDebouncer.isFastClick(it)) {
            onClickListener.invoke(it)
        }
    }
}

fun View?.setDebouncedOnClickListener(onClickListener: ((View?) -> Unit)?) {
    this.setDebouncedOnClickListener(null, onClickListener)
}
package com.secoo.commonsdk.utils

import android.view.View
import com.secoo.commonsdk.ktx.alsoWithLog
import com.secoo.commonsdk.ktx.smartLog
import java.util.*

/**
 * View防快速点击处理
 */
object ViewClickDebouncer: View.OnAttachStateChangeListener {
    private const val THRESHOLD_DEBOUNCE_IN_MILLS = 500L
    private val viewClickDebounceMap: WeakHashMap<View, Debounce<View>> = WeakHashMap()

    /**
     * 获取View快速点击控制
     */
    private fun getViewDebounce(view: View?, thresholdInMills: Long? = THRESHOLD_DEBOUNCE_IN_MILLS): Debounce<View>? {
        view ?: return null

        return viewClickDebounceMap[view] ?: onCreateViewDebounce(view, thresholdInMills).also {
            viewClickDebounceMap[view] = it
        }
    }

    fun setupViewDebounceTimeout(view: View?, debounceTimeOutInMills: Long?) {
        view ?: return
        viewClickDebounceMap.remove(view)
        getViewDebounce(view, debounceTimeOutInMills)
    }

    private fun onCreateViewDebounce(view: View, thresholdInMills: Long?): Debounce<View> {
        view.addOnAttachStateChangeListener(this)

        val finalThreshold = thresholdInMills ?: THRESHOLD_DEBOUNCE_IN_MILLS
        return Debounce<View>(finalThreshold).alsoWithLog(this) {
            "createViewDebounce view=$view; finalThreshold=$finalThreshold"
        }
    }

    /**
     * 是否是快速点击
     */
    fun isFastClick(view: View?): Boolean {
        view ?: return false.alsoWithLog(this) {
            "isTooFast view is null;return false"
        }

        val debounce = getViewDebounce(view)
        debounce ?: return false.alsoWithLog(this) {
            "isTooFast debounce is null;return false"
        }

        return debounce.updateValue(view).not().alsoWithLog(this) {
            "isTooFast return $it;view=$view"
        }
    }

    override fun onViewDetachedFromWindow(view: View?) {
        smartLog {
            "onViewDetachedFromWindow view=$view"
        }
        view ?: return
        view.removeOnAttachStateChangeListener(this)
        viewClickDebounceMap.remove(view)
    }

    override fun onViewAttachedToWindow(view: View?) {
        smartLog {
            "onViewAttachedToWindow view=$view"
        }
    }
}