美文网首页高级UI技术分享Android开发学习
安卓按钮防止重复点击事件

安卓按钮防止重复点击事件

作者: 因为我的心 | 来源:发表于2019-09-17 09:51 被阅读0次

    一、前言:

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

    这种方式,每个点击事件都需要写一个时间判断,重复代码很多。

    三、 封装一个点击事件,处理点击间隔判断

    public abstract class CustomClickListener implements View.OnClickListener {
        private long mLastClickTime;
        private long timeInterval = 1000L;
    
        public CustomClickListener() {
    
        }
    
        public CustomClickListener(long interval) {
            this.timeInterval = interval;
        }
    
        @Override
        public void onClick(View v) {
            long nowTime = System.currentTimeMillis();
            if (nowTime - mLastClickTime > timeInterval) {
                // 单次点击事件
                onSingleClick();
                mLastClickTime = nowTime;
            } else {
                // 快速点击事件
                onFastClick();
            }
        }
    
        protected abstract void onSingleClick();
        protected abstract void onFastClick();
    }
    

    使用:

    btTest.setOnClickListener(new CustomClickListener() {
        @Override
        protected void onSingleClick() {
            Log.d("xxx", "onSingleClick");
        }
    
        @Override
        protected void onFastClick() {
            Log.d("xxx", "onFastClick");
        }
    });
    

    相比于第一种方式,这种方法将重复点击的判断封装在CustomClickListener内部,外部无需处理时间判断,只需要实现点击方法即可。

    四、利用RxAndroid处理重复点击

    1.依赖

    dependencies {
    
     //按钮防止重复点击
    implementation 'com.jakewharton.rxbinding:rxbinding:1.0.0'
    
    }
    

    2. 基本使用:

     //2秒内点击无效
     RxView.clicks(tvRefreshCode)
        .throttleFirst(2, TimeUnit.SECONDS)
        .subscribeOn(rx.android.schedulers.AndroidSchedulers.mainThread())
        .subscribe(new Action1<Void>() {
             @Override
             public void call(Void aVoid) {
                  //  ...业务逻辑
            }
    });
    

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

    思考一下:

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

    五、 如何使用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中,添加:
    // 注意:主App中请确保添加aspectjx
    apply plugin: 'android-aspectjx'
    dependencies {
        ......
        implementation 'org.aspectj:aspectjrt:1.8.9'
    }
    

    2. 添加一个自定义注解

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

    添加自定义注解的原因是,方便管理哪些方法使用了重复点击的AOP,同时可以在注解中传入点击时间间隔,更加灵活。

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

    public final class XClickUtil {
    
        /**
         * 最近一次点击的时间
         */
        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 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 SingleClickAspect {
        private static final long DEFAULT_TIME_INTERVAL = 5000;
    
        /** 
         * 定义切点,标记切点为所有被@SingleClick注解的方法
         * 注意:这里me.baron.test.annotation.SingleClick需要替换成
         * 你自己项目中SingleClick这个类的全路径哦
         */
        @Pointcut("execution(@me.baron.test.annotation.SingleClick * *(..))")
        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(SingleClick.class)) {
                return;
            }
            SingleClick singleClick = method.getAnnotation(SingleClick.class);
            // 判断是否快速点击
            if (!XClickUtil.isFastDoubleClick(view, singleClick.value())) {
                // 不是快速点击,执行原方法
                joinPoint.proceed();
            }
        }
    }
    

    5. 使用方法

    private void initView() {
        btTest = findViewById(R.id.bt_test);
        btTest.setOnClickListener(new View.OnClickListener() {
            // 如果需要自定义点击时间间隔,自行传入毫秒值即可
            // @SingleClick(2000)
            @SingleClick
            @Override
            public void onClick(View v) {
                // do something
            }
        });
    }
    

    只需要一个注解,即完成了按钮的防止重复点击,其他所有工作交给编译器,代码清爽了很多有木有。


    参考作者:XBaron
    链接:https://www.jianshu.com/p/7b354eb8d0d3

    相关文章

      网友评论

        本文标题:安卓按钮防止重复点击事件

        本文链接:https://www.haomeiwen.com/subject/hgyquctx.html