Android handles repeated button clicks gracefully
Previous treatment
The methods found online or you might think of are probably these:
1. In each button click event, record the click time and judge whether it exceeds the click time interval
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();
}
}
});
}
In this way, each click event needs to write a time judgment, and a lot of repeated code.
2. Encapsulate a click event to handle the click interval judgment
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();
}
use:
btTest.setOnClickListener(new CustomClickListener() {
@Override
protected void onSingleClick() {
Log.d("xxx", "onSingleClick");
}
@Override
protected void onFastClick() {
Log.d("xxx", "onFastClick");
}
});
Compared with the first method, this method encapsulates the judgment of repeated clicks inside the CustomClickListener, and there is no need to process time judgment externally, only the click method needs to be implemented.
3. Use RxAndroid to handle repeated clicks
RxView.clicks(view)
.throttleFirst(1, TimeUnit.SECONDS)
.subscribe(new Consumer<Object>() {
@Override
public void accept(Object o) throws Exception {
// do something
}
});
Responsively handle button clicks and use rxjava operators to prevent repeated clicks. Compared with the first and second solutions, this method is more elegant.
think for a while:
These three methods, no matter which one, are very intrusive to the original click event. Either you need to add a method to the Click event, or you need to replace the entire Click event. Then, is there a way? Without changing the original logic, can it handle repeated clicks of the button well?
A more elegant approach
Add a unified processing logic to all methods of the same type, and we can quickly think of a word: AOP , yes, aspect-oriented programming.
How to use AOP to solve the problem of repeated clicks?
1. Introduce Aspectj
Use AOP programming on Android, generally use the Aspectj library
Standing on the shoulders of giants, Hujiang has open sourced Aspectj's Gradle plugin, which is convenient for us to use Aspectj
- In the build.gradle under the project root directory, add dependencies:
dependencies {
......
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.0'
}
- In the build.gradle under the app or other module directory, add:
// 注意:主App中请确保添加aspectjx
apply plugin: 'android-aspectjx'
dependencies {
......
implementation 'org.aspectj:aspectjrt:1.8.9'
}
2. Add a custom annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SingleClick {
/* 点击间隔时间 */
long value() default 1000;
}
The reason for adding custom annotations is to facilitate the management of which methods use the repeated click AOP, and at the same time, you can pass in the click time interval in the annotation, which is more flexible.
3. Encapsulate a repeated click judgment tool class
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. Write Aspect AOP processing class
@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();
}
}
}
Instructions
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
}
});
}
Only one annotation is needed to prevent repeated clicks of the button. All other tasks are handed over to the compiler. The code is refreshed a lot.
--------------------------------------------
Introduction of AspectJ
The introduction of eclipse and Android Studio is different. This article only introduces how to introduce AspectJ into Android Studio. Please Baidu for eclipse. Android Studio needs to be introduced in the build.gradle file of the app module, which is divided into 3 steps:
1) Add core dependencies
dependencies {
...
compile 'org.aspectj:aspectjrt:1.8.9'
}
2) Write gradle compilation script
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'org.aspectj:aspectjtools:1.8.9'
classpath 'org.aspectj:aspectjweaver:1.8.9'
}
}