Android 探讨防二次点击

关于 View 的二次点击,估计是测试对程序员频率较高的 Bug 了,为此我们来探讨一下如何防止二次点击。

一、普遍解法

网上几乎所有的解法都是在 View 调用点击事件后,将其的 clickable 属性设置成 false,然后设置一个定时器
什么的,在多少秒后再将其的 clickable 设置成 true

示例代码:

/**
 *  <让 View 在 @param time 内不可点击>
 */
fun View.sleep(time: Long = 200) {
    
    
    this.isClickable = false
    launch({
    
    
        delay(time)
        this.isClickable = true
    })
}

上面是利用到了协程,还有诸多法门,本文只是探讨不作具体实现,所以就不一一演示。(Java 代码可以利用简单的 装饰者模式 进行封装)

那么上面的代码在实际的运用当中是这样的:

view.setOnclickListener{
    
    
    doSmThing()
    view.sleep()
}

这样的做法简单粗暴,也是最实际的,几乎能解决 99% 的问题。

二、RxView解法

把一个点击事件,以响应式的模型实现。RxBinding 实际上是作为 RxJava 的一种扩展,里面包含了多种关于 ViewObservable 扩展,例如一个点击事件我们可以这么写:

导入的是RxBinding 4.0 的库,该库对kotlin完美的支持。

view.clicks().subscribe {
    
    
	doSmThing()
}

实际上 view.clicks() 就是生成了一个持有该 View 的一个被观察者,里面帮助我们实现了点击事件,使得点击后会在下游——subscribe() 参数中的 订阅者 中实现。

扫描二维码关注公众号,回复: 11978109 查看本文章

那么既然他把点击事件包装成一个 Observable ,那么我们能做的事情一件都不会落下,例如加个 throttleFirst 操作符,那么就可以轻易的实现多少时间内只取第一次的事件:

btn_blueTooth.clicks().throttleFirst(1000,TimeUnit.SECONDS).subscribe {
    
    
	doSmThing()
}

三、花里胡哨解法

我们可以通过动态代理,拿到点击事件的 Listener 对象,将其强制代理,然后做一些操作:

/**
 *  <View 防二次点击 (花里胡哨版本)>
 */
inline fun View.avoidClick(){
    
    
    val listenerInfo = View::class.java.getDeclaredMethod("getListenerInfo").apply {
    
     isAccessible = true }.invoke(this)
    val clickListener = listenerInfo::class.java.getField("mOnClickListener").get(listenerInfo)
    if (clickListener == null){
    
    
        throw IllegalStateException("该方法必须在设置 onClickListener 之后")
    }else{
    
    
        val clickListenerProxy = View.OnClickListener {
    
    
            clickListener::class.java.getMethod("onClick",View::class.java).invoke(clickListener,this@avoidClick)
            this.sleep()    //这里直接调用简单粗暴的方法
        }
        listenerInfo::class.java.getField("mOnClickListener").set(listenerInfo,clickListenerProxy)
    }
}

具体使用:

view.setOnclickListener{
    
    
    doSmThing()
}
view.avoidClick()

对比第一个解法,两个不同的实现,区别就是方法调用位置的不同。

其实对于 kotlin 来说,扩展方法确实是个好东西,所以我们还可以这样实现(再说一遍,在 Java 中可以利用装饰者模式做出同样的操作):

fun View.setAvoidClickListener(time:Long = 200,click:(View)->Unit){
    
    
    this.setOnClickListener {
    
    
        click(it)
        this.sleep(time)
    }
}

具体使用:

view.setAvoidClickListener(300) {
    
    
    doSmTing()
}

这个做法似乎更加简单,规则没有那么多,而且性能比花里胡哨版本好多了……

但是有些时候会变得有点麻烦,比如说使用 DataBinding 的时候,Android SDK 给你提供的 android:onClick 就不能绑定了,到时候得需要自己实现自定义绑定。

四、探讨根源性的问题

说了这么久,其实我们可以确定两个规则:

  1. 如果是依据 clickable 来实现,在 View 的点击事件后,马上调用 view.isClickable = false,在某个条件之后,再调用 view.isClickable = true
  2. 单独开一个线程,来进行对时间的计算,在满足条件之后,再回到原线程中执行点击事件。

某个条件 可以是很多种情况,例如说需要等到一个网络请求结束之后,你才可以再次点击;又比如发送验证码必须得等 60s 才能再次点击等等。

这些条件我们都是必须要贴切具体的需求,才能书写具体的代码,想到这里基本上可以确定:

防二次点击没有完美的解决方案。

猜你喜欢

转载自blog.csdn.net/catzifeng/article/details/107266786