在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();
}
}
});
}
此方法虽然可以解决按钮的重复点击问题,但是会导致重复代码太多。
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 action, @NonNull View... target) {
for (View view : target) {
RxView.onClick(view).throttleFirst(intervalTime == 0 ? 1000 : intervalTime, TimeUnit.MILLISECONDS).subscribe(new Consumer() {
@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 onClick(@NonNull View view) {
checkNotNull(view, "view == null");
return Observable.create(new ViewClickOnSubscribe(view));
}
/**
* onclick事件防抖动
* 返回view
*/
private static class ViewClickOnSubscribe implements ObservableOnSubscribe {
private View view;
public ViewClickOnSubscribe(View view) {
this.view = view;
}
@Override
public void subscribe(@io.reactivex.annotations.NonNull final ObservableEmitter 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 the first argument type
*/
public interface OnRxViewClickListener {
/**
* 点击事件
*
* @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 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事件,那么,有没有一种方式,可以在不改动原有逻辑的情况下,又能很好地处理按钮的重复点击呢?这就是我们的方案四。
1.引入Aspectj
Android 上使用AOP编程,一般使用Aspectj这个库,站在巨人的肩膀上,沪江已经开源了Aspectj的Gradle插件,方便我们使用Aspectj。
dependencies {
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.0'
}
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