Android处理按钮重复点击事件

前言

在Android APP中,按钮的点击随处可见,比如:页面跳转,请求服务器等等!如果不处理按钮重复点击,就会造成一系列的问题,因此,防止按钮多次点击,是Android开发中一个很重要的技术手段。

处理方案

方案一:每个按钮点击事件中,记录点击时间,判断是否超过点击时间间隔

private long mLastClickTime = 0;
public static final long TIME_INTERVAL = 1000L;
private Button btTest;
private void initView() {
    btTest = findViewById(R.id.bt_test);
    btTest.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            long nowTime = System.currentTimeMillis();
            if (nowTime - mLastClickTime > TIME_INTERVAL) {
                // do something
                mLastClickTime = nowTime;
            } else {
                Toast.makeText(MainActivity.this, "不要重复点击", Toast.LENGTH_SHORT).show();
            }
        }
    });
}

此方法虽然可以解决按钮的重复点击问题,但是会导致重复代码太多。

方案二:在方案一的基础上进行封装,以接口回调的方式处理点击事件

方案三:使用Rxjava处理重复点击

public class RxView {
    public static int intervalTime;

    /**
     * 设置点击间隔时间
     *
     * @param intervalTime
     * @return
     */
    public static void setIntervalTime(int intervalTime) {
        RxView.intervalTime = intervalTime;
    }

    /**
     * 防止重复点击
     *
     * @param target 目标view
     * @param action 监听器
     */
    public static void setOnClickListeners(final OnRxViewClickListener<View> action, @NonNull View... target) {
        for (View view : target) {
            RxView.onClick(view).throttleFirst(intervalTime == 0 ? 1000 : intervalTime, TimeUnit.MILLISECONDS).subscribe(new Consumer<View>() {
                @Override
                public void accept(@io.reactivex.annotations.NonNull View view) throws Exception {
                    action.onRxViewClick(view);
                }
            });
        }
    }

    /**
     * 监听onclick事件防抖动
     *
     * @param view
     * @return
     */
    @SuppressLint("RestrictedApi")
    @CheckResult
    @NonNull
    private static Observable<View> onClick(@NonNull View view) {
        checkNotNull(view, "view == null");
        return Observable.create(new ViewClickOnSubscribe(view));
    }

    /**
     * onclick事件防抖动
     * 返回view
     */
    private static class ViewClickOnSubscribe implements ObservableOnSubscribe<View> {
        private View view;

        public ViewClickOnSubscribe(View view) {
            this.view = view;
        }

        @Override
        public void subscribe(@io.reactivex.annotations.NonNull final ObservableEmitter<View> e) throws Exception {
            checkUiThread();

            View.OnClickListener listener = new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (!e.isDisposed()) {
                        e.onNext(view);
                    }
                }
            };
            view.setOnClickListener(listener);
        }
    }

    /**
     * A one-argument action. 点击事件转发接口
     *
     * @param <T> the first argument type
     */
    public interface OnRxViewClickListener<T> {
        /**
         * 点击事件
         *
         * @param view
         */
        void onRxViewClick(View view);
    }
}
public class Preconditions {
    public static void checkArgument(boolean assertion, String message) {
        if (!assertion) {
            throw new IllegalArgumentException(message);
        }
    }

    public static <T> T checkNotNull(T value, String message) {
        if (value == null) {
            throw new NullPointerException(message);
        }
        return value;
    }

    public static void checkUiThread() {
        if (Looper.getMainLooper() != Looper.myLooper()) {
            throw new IllegalStateException(
                    "Must be called from the main thread. Was: " + Thread.currentThread());
        }
    }

    private Preconditions() {
        throw new AssertionError("No instances.");
    }
}

使用方法:

1、在需要加点击事件的类中实现RxView.OnRxViewClickListener接口

2、设置点击时间和添加点击事件

RxView.setIntervalTime(2000);
RxView.setOnClickListeners(this, rx_view);

3、事件处理在接口的实现方法中进行处理即可

@Override
public void onRxViewClick(View view) {
    switch (view.getId()) {
        case R.id.rx_view:
            LogUtil.e("点击了");
            break;
        default:
            break;
    }
}

此方案响应式地处理按钮点击,利用rxjava的操作符,来防止重复点击,相较于方案一、方案二来说,此方法更为优雅一些。

思考:以上三种方案,无论哪一种,都对原有点击事件有很大的侵入性,要么你需要往Click事件中加方法,要么你需要替换整个Click事件,那么,有没有一种方式,可以在不改动原有逻辑的情况下,又能很好地处理按钮的重复点击呢?这就是我们的方案四。

方案四:更为优雅的处理方式-----AOP切面编程

一、如何使用AOP来解决重复点击问题?

1.引入Aspectj

Android 上使用AOP编程,一般使用Aspectj这个库,站在巨人的肩膀上,沪江已经开源了Aspectj的Gradle插件,方便我们使用Aspectj。

  • 在项目根目录下的build.gradle中,添加依赖
dependencies {
   
    classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.0'

  
}
  • 在app或其他module目录下的build.gradle中,添加以下代码:
apply plugin: 'android-aspectjx'
dependencies {
  implementation 'org.aspectj:aspectjrt:1.8.13'
}

注意:如果aspectjx是在module或者library中引入依赖,则在app的build.gradle也需要添加依赖,要不就在使用module或者library中的aop注解就无法生效。(具体原因还未发现)

2、添加一个自定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public  @interface AopOnclick {
    /**
     * 点击间隔时间
     */
    long value() default 1000;
}

3、封装一个重复点击判断工具类

public class AopClickUtil {
    /**
     * 最近一次点击的时间
     */
    private static long mLastClickTime;
    /**
     * 最近一次点击的控件ID
     */
    private static int mLastClickViewId;

    /**
     * 是否是快速点击
     *
     * @param v  点击的控件
     * @param intervalMillis  时间间期(毫秒)
     * @return  true:是,false:不是
     */
    public static boolean isFastDoubleClick(View v, long intervalMillis) {
        int viewId = v.getId();
//        long time = System.currentTimeMillis();
        long time = SystemClock.elapsedRealtime();
        long timeInterval = Math.abs(time - mLastClickTime);
        if (timeInterval < intervalMillis && viewId == mLastClickViewId) {
            return true;
        } else {
            mLastClickTime = time;
            mLastClickViewId = viewId;
            return false;
        }
    }
}

4、编写Aspect AOP处理类

@Aspect
public class AopClickAspect {

    /**
     * 定义切点,标记切点为所有被@AopOnclick注解的方法
     * 注意:这里com.freak.aop.AopOnclick需要替换成
     * 你自己项目中AopOnclick这个类的全路径
     */
    @Pointcut("execution(@com.freak.httpmanage.aop.AopOnclick * *(..))")
    public void methodAnnotated() {}

    /**
     * 定义一个切面方法,包裹切点方法
     */
    @Around("methodAnnotated()")
    public void aroundJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
        // 取出方法的参数
        View view = null;
        for (Object arg : joinPoint.getArgs()) {
            if (arg instanceof View) {
                view = (View) arg;
                break;
            }
        }
        if (view == null) {
            return;
        }
        // 取出方法的注解
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        if (!method.isAnnotationPresent(AopOnclick.class)) {
            return;
        }
       AopOnclick aopOnclick = method.getAnnotation(AopOnclick.class);
        // 判断是否快速点击
        if (!AopClickUtil.isFastDoubleClick(view, aopOnclick.value())) {
            // 不是快速点击,执行原方法
            joinPoint.proceed();
        }
    }
}

二、使用方法

只需要在点击事件的方法上面加入@AopOnclick注解,则就可以处理点击事件重复的问题,代码如下:

@AopOnclick(5000)
public void aop(View view) {
    LogUtil.e("点击了");
}
aop.setOnClickListener(new View.OnClickListener() {
    @AopOnclick
    @Override
    public void onClick(View v) {
        Log.e(TAG, "点击了");
    }
});

@AopOnclick注解后面加入值,代表的是设置点击的间隔时间,默认是1000毫秒。

总结:以上四种方案,对比之后,方案四是最为简单粗暴,一个注解就解决了问题。当然,处理方法的选择也就看个人的需求了。

参考文章:

https://github.com/sososeen09/android-blog-demos/tree/master/aop-tech

https://www.jianshu.com/p/c66f4e3113b3

猜你喜欢

转载自blog.csdn.net/freak_csh/article/details/89477388