本文已授权公众号「玉刚说」独家发布
也许有些人会纳闷,老哥,你这么简单的功能还需要写博客吗?是游戏不好玩呢?还是妹子不好撩啊?
这位兄台,你回答的真是太对了,这年头游戏太烧钱,撩妹子比改bug还难。。。。
App中,有很大一部分场景是点击按钮,向服务端提交数据,由于网络请求需要时间,用户很可能会多次点击,造成数据重复提交,造成各种莫名其妙的问题。
同时如果你是hmi开发,在很多时候也需要此项操作。
因此,防止按钮多次点击,是Android开发中一个很重要的技术手段。
以前的处理方式
网上查找到的,或者你可能会想到的方法大概有这些:
1.每个按钮点击事件中,记录点击时间,判断是否超过点击时间间隔
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();
}
}
});
}
这种方式,每个点击事件都需要写一个时间判断,重复代码很多。
2.封装一个点击事件,处理点击间隔判断
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内部,外部无需处理时间判断,只需要实现点击方法即可。
3.利用RxAndroid处理重复点击
RxView.clicks(view)
.throttleFirst(1, TimeUnit.SECONDS)
.subscribe(new Consumer<Object>() {
@Override
public void accept(Object o) throws Exception {
// do something
}
});
响应式地处理按钮点击,利用rxjava的操作符,来防止重复点击,相较于第1,2方案来说,此方法更为优雅一些。
偷懒的方式
作为程序员,我们要学会偷懒,能少写的绝不多写,因此我封装成了一个工具类,同时考虑到后面有更好的实现方式,在尽可能小的情况下改动代码;或者每个按钮的点击间隔不一样呢?
1.首先定义一个接口
public interface ViewClick extends View.OnClickListener {
void throttle(long skipDuration, TimeUnit timeUnit);
}
2.Android原生的实现方式
public class ViewClickImp implements ViewClick {
private View mView;
private View.OnClickListener mOnClickListener;
private long mOldTime;
private long mDelayMilliseconds = 1000;
public ViewClickImp(View view, View.OnClickListener onClickListener) {
RxUtil.requestNoNull(view, "view in null");
RxUtil.requestNoNull(onClickListener, "onClickListener in null");
mView = view;
mOnClickListener = onClickListener;
mView.setOnClickListener(this);
LogUtil.logD("ViewClick ViewClickImp: ");
}
@Override
public void onClick(final View v) {
// 考虑到用户手动修改系统时间,所以要使用以下方式
long nowTime = SystemClock.elapsedRealtime();
long intervalTime = nowTime - mOldTime;
if (mOldTime == 0 || intervalTime >= mDelayMilliseconds) {
mOldTime = nowTime;
mOnClickListener.onClick(v);
}
}
@Override
public void throttle(long skipDuration, TimeUnit timeUnit) {
if (skipDuration < 0) {
skipDuration = 0;
}
RxUtil.requestNoNull(timeUnit, "timeUnit in null");
mDelayMilliseconds = timeUnit.toMillis(skipDuration);
}
}
需要注意: System.currentTimeMillis()受系统时间的影响,因此在这里要用SystemClock.elapsedRealtime()
3.RxJava的实现方式
public class RxViewClickImp implements ViewClick {
private View mView;
private View.OnClickListener mOnClickListener;
private long mSkipDuration;
private TimeUnit mTimeUnit = TimeUnit.MILLISECONDS;
private ObservableEmitter mObservableEmitter;
private Observable mObservable;
public RxViewClickImp(View view, View.OnClickListener onClickListener) {
RxUtil.requestNoNull(view, "view in null");
RxUtil.requestNoNull(onClickListener, "onClickListener in null");
mView = view;
mOnClickListener = onClickListener;
mView.setOnClickListener(this);
LogUtil.logD("ViewClick RxViewClickImp: ");
}
@Override
public void onClick(final View v) {
if (mObservable == null) {
mObservable = Observable.create(new ObservableOnSubscribe<View>() {
@Override
public void subscribe(final ObservableEmitter<View> emitter) throws Exception {
mObservableEmitter = emitter;
}
});
throttleFirst(mObservable);
}
mObservableEmitter.onNext(mView);
}
@Override
public void throttle(long skipDuration, TimeUnit timeUnit) {
mSkipDuration = skipDuration;
mTimeUnit = timeUnit;
}
private void throttleFirst(Observable observable) {
observable.throttleFirst(mSkipDuration, mTimeUnit)
.subscribe(new Consumer<View>() {
@Override
public void accept(View view) throws Exception {
mOnClickListener.onClick(view);
}
});
}
}
需要注意:在RxViewClickImp对象内只能有一个Observable和ObservableEmitter,不然你会发现设置是无效的
4.ViewClickUtil的实现
1.点击事件
public void clicks(View.OnClickListener onClickListener, View view) {
switch (mType) {
case RX_VIEW:
mViewClick = new RxViewClickImp(view, onClickListener);
break;
case VIEW:
mViewClick = new ViewClickImp(view, onClickListener);
break;
}
mViewClick.throttle(mSkipDuration, mTimeUnit);
}
public void clicks(View.OnClickListener onClickListener, View... views) {
for (View view : views) {
clicks(onClickListener, view);
}
}
2.build
private ViewClickUtil(Builder builder) {
mSkipDuration = builder.mSkipDuration;
mTimeUnit = builder.mTimeUnit;
mType = builder.mType;
}
public static class Builder {
private long mSkipDuration;
private TimeUnit mTimeUnit;
private Type mType = Type.RX_VIEW;
public Builder setSkipDuration(long mSkipDuration) {
this.mSkipDuration = mSkipDuration;
return this;
}
public Builder setTimeUnit(TimeUnit mTimeUnit) {
this.mTimeUnit = mTimeUnit;
return this;
}
public Builder setType(Type type) {
this.mType = type;
return this;
}
public ViewClickUtil build() {
return new ViewClickUtil(this);
}
}
3.定义一个枚举
public enum Type {
RX_VIEW, VIEW
}
4.如何使用
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
new ViewClickUtil.Builder()
.setSkipDuration(500)
.setTimeUnit(TimeUnit.MILLISECONDS)
.setType(ViewClickUtil.Type.RX_VIEW)
.build().clicks(this, click1, click2, skip);
}
long oldTime = SystemClock.elapsedRealtime();
@Override
public void onClick(View v) {
long nowTime = SystemClock.elapsedRealtime();
switch (v.getId()) {
case R.id.click1:
LogUtil.logD("click1: " + (nowTime - oldTime));
break;
case R.id.click2:
LogUtil.logD("click2: " + (nowTime - oldTime));
break;
case R.id.skip:
startActivity(new Intent(this, SkipActivity.class));
break;
}
oldTime= nowTime;
}
需要注意的是:view的setOnClickListener对象是ViewClick,同时在ViewClick的oclick()方法里面调用传入的View.OnClickListener的onClick()方法
这只是楼主的实现方式,大家如果有其他实现方式的可以在评论区留言,如果喜欢的请点击关注,谢谢!!!