登录

去注册

登录

注册

去登录

注册

每日一问 屏蔽连续点击的方案有哪些?

xiaoyang   2019-09-23   收藏

这是一个很常见的需求,那么到底有多少种方案,每种各有什么特点?

11

基本的判断逻辑,能想到的有三种,分别是:

  1. 每次计算最后点击时间与当前时间的间隔,并判断是否超过指定时长。这种方法也是最最常见的;

  2. @少東同学 举例的ButterKnife,它原理也很简单,就是:必须要等上一次事件处理完成之后,才接受新的事件(用flag标记,事件处理期间忽略多余的事件)。

  3. 借助线程池的延迟执行机制:每次处理事件之前,根据一个flag来判断应不应该处理该事件,当接收了事件之后,把这个flag标记为无效。事件处理完成后,向线程池提交一个延时执行的任务,这个任务就是把flag重新标记为可用,延时的时长,就是我们指定的间隔时长。所以,在指定的间隔时长之内到达的事件,也是会被直接忽略掉的,直到延时任务被执行(flag被重新标记可以)后,才继续接收新的事件,周而复始。这也是Rxjava的throttleFirst操作符的原理。


至于 @15398865919同学 提到的Rxjava的debounce操作符,个人觉得用在这里不太合适,虽然它也能避免重复点击。为什么呢? 因为这个操作符的特点是:在指定时间内如果重复接收到事件,那么它等待的时间就会重置。 也就是说,如果给这个操作符设置了2秒,那么事件的间隔必须要超过2秒,才会处理,如果2秒内又有新的事件到达,那刚刚等待的时间就会重置,也就是要重新等待了。
这个操作符适合什么样的场景呢?
比如一些播放器,它下面显示的SeekBar,一般是触摸屏幕的时候,它会显示出来,过了一定时间没有操作屏幕,他就会隐藏,如果期间一直有触摸屏幕,那么它也就一直显示。这种场景就很适合用debounce来做。


上面那三种方案分别有什么特点呢?

在效果角度来看,1和3是一样的,它接受新事件的间隔,取决于你指定的时长。
而第2种,它就没有根据固定时间间隔,只要本次点击事件未处理完成,就会一直等待。

所以,1和3是有可能多个事件被同时处理的,比如点击按钮子线程发起网络请求,这种情况下,如果网络请求时间比较长,当用户第二次按下按钮的时候,就有可能再次发起网络请求了。而第二种方案,就不会发生这个情况。

回复
longforus : @陈小缘 

是的当初也考虑过 debounce 操作符,但是发现确实不合适

2019-09-20 回复
4
  1. 最简单粗暴的方案:在BaseActivity中重写事件分发函数,判断Down事件的间隔事件(简单粗暴,代码量少,全局防重复点击,改动少,可能存在潜在问题,但是目前没遇到)
  2. aop,分为注解的方式和直接定位,注解的方式用于自己定义那些地方需要拦截,直接定位onclick的方式用于全局。
  3. j神的rxbinding(好像是基于rxjava操作符的)
回复
DaveBoy : @DaveBoy 

前段时间尝试了一下2方案,替换1方案,1方案会导致列表滑动卡顿。。。 aspectj遇到的问题首先是如果不记录上次点击的id,直接判断两次点击的间隔会导致“在view的点击事件中调用另一个view的p  ...查看更多

2019-11-07 回复
3

屏蔽连续点击最好的办法就是增加响应速度,连击最快的时间都超过了50毫秒,50毫秒足够做出最快的响应了。而不是判断连击,屏蔽掉第二次点击。

回复
养猫的鱼007 : @Reginer 

确实需要提高响应速度,但是一些情况下没办法,比如一个发起网络请求的按钮

2019-10-08 回复
2

这种需求常见于点击按钮启动Activity的场景,startActivity最终会调用 startActivityForResult,所以比较简单实用的一种方案是:重写startActivityForResult,判断两次点击时间差

    /**
     * 防止快速点击启动多次activity --开始
     */
    @Override
    public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) {
        if (startActivityCheck()) {
            super.startActivityForResult(intent, requestCode, options);
        }
    }

    private long mActivityJumpTime;

    private boolean startActivityCheck() {
        if (mActivityJumpTime >= (System.currentTimeMillis() - 500)) {
            LogHelper.d("屏蔽快速点击");
            return false;
        }

        mActivityJumpTime = System.currentTimeMillis();
        return true;
    }
    /** 防止快速启动多次activity --结束*/
回复
1

在陈小缘同学的基础上再加一种处理方式-----rxBinding,具体代码如下:推荐使用这个,简单方便
addDisposable(RxView.clicks(btnClick)
.throttleFirst(2, TimeUnit.SECONDS)
.subscribe(o -> {
Log.e("rx_binding_test", "clicks:点击了按钮:两秒内防抖");
}));
RxBinding中主要包含RxView、RxTextView、RxAdapterView、RxCompoundButton等等,由于内容太多,详细的源码可以参考Jake Wharton的RxBinding 地址为https://github.com/JakeWharton/RxBinding

回复
1

rxbinding debounce操作符

回复
0

1.aspectj,直接对OnclickListener做处理
2.检查view树,设置了点击的就给那个点击换一个,然后添加视图树改变的监听,改变了就重来次(防止动态添加view)

回复
0

对于打开一个页面的连续点击不用理会。
对于处理业务逻辑的连续点击,加一个 flag,连续点击时,flag肯定是相同的,每次响应点击之前判断集合中是否存在该flag,不存在就把flag存放进事件集合,存在就取消该事件,此时取消的是上一次的点击事件,只处理这一次的事件。

回复
0

时间戳 每次点击都记录一次时间戳用静态变量存起来 两次时间戳只差<50ms 就忽略本次点击

回复
0

butterknife 好像可以解决,哪位大神知道原理,以下是源码:

public abstract class DebouncingOnClickListener implements View.OnClickListener {
  static boolean enabled = true;

  private static final Runnable ENABLE_AGAIN = new Runnable() {
    @Override public void run() {
      enabled = true;
    }
  };

  @Override public final void onClick(View v) {
    if (enabled) {
      enabled = false;
      v.post(ENABLE_AGAIN);
      doClick(v);
    }
  }

  public abstract void doClick(View v);
}
回复
少東 : @slz17 

我觉得这个得看具体的业务场景,一般我们点击一个按钮用工作线程执行,是发送一个异步请求,发送请求一般会弹出一个等待框,或者类似让按钮不能再次点的举措。

2019-09-20 回复
slz17 : @少東 

static boolean enabled = true;enabled用来做标志位,true的时候才能做点击事件 v.post(ENABLE_AGAIN);把翻转的操作放到viewroot的han  ...查看更多

2019-09-20 回复

删除留言

确认删除留言,会导致相关评论丢失?

取消 确定