前言
在项目开发过程中,有时我们需要限制一些组件在短期内只响应一次点击事件来防止其点击事件的多次响应,从而使应用的交互更加的友好(如:在网络不好的状况下多次点击登录,注册按钮;多次点击列表添加按钮;多次点击跳转按钮等)。
说到这里,大家的第一反应应该是自定义对应的组件(如:Button,TextView,ImageButton等)来限制点击,这不失为一个好办法,但其局限性过大,只适合特定需求的环境下;那么如果我们需要使用的范围较广,又不想那么麻烦的通过一个个自定义来解决该如何去做呢——我们可以通过自定义OnClickListener的方式来进行通用的限制,这样的话,就可以有更广泛的使用范围了。
1.使用自定义方式(如:Button)实现
- onClick何时触发?
通过查看Button源码我们可以得知Button继承自TextView,而TextView又继承自View,那么既然点击事件是通过setOnClickListener来与组件进行绑定的,我们可以到最上层的View.java类中来看下该方法:
/**
* Register a callback to be invoked when this view is clicked. If this view is not
* clickable, it becomes clickable.
*
* @param l The callback that will run
*
* @see #setClickable(boolean)
*/
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
我们可以通过查看mOnClickListener.Onclick在View类的哪些方法里被调用了来判断到底该重写哪个方法:
/**
* Call this view's OnClickListener, if it is defined. Performs all normal
* 通知定义OnClickListener的视图,执行所有正常
* actions associated with clicking: reporting accessibility event, playing
* 与单击相关的操作:报告可访问事件,播放
* a sound, etc.
* 声音等
*
* @return True there was an assigned OnClickListener that was called, false
* otherwise is returned.
*/
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}
/**
* Directly call any attached OnClickListener. Unlike {@link #performClick()},
* 直接调用任何附加的OnClickListener。与{@link #performClick()}不同的是,
* this only calls the listener, and does not do any associated clicking
* 这里只调用监听器,不执行任何关联的单击操作,
* actions like reporting an accessibility event.
* 如报告可访问事件。
*
* @return True there was an assigned OnClickListener that was called, false
* otherwise is returned.
*/
public boolean callOnClick() {
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
li.mOnClickListener.onClick(this);
return true;
}
return false;
}
通过查阅上述代码我们可以得知,当点击行为发生时,会先执行performClick()
然后触发对应组件的onClick(View v)
回调。
- 如何拦截onClick的触发?
通过对View.jave类中OnClickListener
事件监听器使用的了解,我们知道可以通过重写自定义组件的performClick()
方法来限制OnClick(View v)
方法的多次触发,示例代码如下:
public class NoMultiClickButton extends Button {
// 两次点击按钮之间的最小点击间隔时间(单位:ms)
private static final int MIN_CLICK_DELAY_TIME = 3000;
// 最后一次点击的时间
private long lastClickTime;
public NoMultiClickButton(Context context) {
super(context);
}
public NoMultiClickButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NoMultiClickButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean performClick() {
long currentTime = System.currentTimeMillis();
if (currentTime - lastClickTime > MIN_CLICK_DELAY_TIME) {// 两次点击的时间间隔大于最小限制时间,则触发点击事件
lastClickTime = currentTime;
return super.performClick();
} else {// 否则,拦截继续下发的事件
return false;
}
}
}
- 如何使用
自定义的Button实际上与我们平时的使用方式一致,示例代码如下:
NoMultiClickButton noMultiClickButton = (NoMultiClickButton) findViewById(R.id.btn_no_multi);
noMultiClickButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e(TAG, "onclick");
}
});
2.自定义OnClickListener事件监听
1.自定义NoMultiClickListener继承View.OnClickListener
public abstract class NoMultiClickListener implements View.OnClickListener {
@Override
public void onClick(View v) {
}
}
2.在Onclick中限制多次点击
// 两次点击按钮之间的最小点击间隔时间(单位:ms)
private static final int MIN_CLICK_DELAY_TIME = 3000;
// 最后一次点击的时间
private long lastClickTime;
@Override
public void onClick(View v) {// 限制多次点击
long currentTime = System.currentTimeMillis();
if (currentTime - lastClickTime > MIN_CLICK_DELAY_TIME) {// 两次点击的时间间隔大于最小限制时间,则触发点击事件
lastClickTime = currentTime;
// 这里触发点击事件
}
}
- 创建触发点击事件的回调
public abstract class NoMultiClickListener implements View.OnClickListener {
// 两次点击按钮之间的最小点击间隔时间(单位:ms)
private static final int MIN_CLICK_DELAY_TIME = 3000;
// 最后一次点击的时间
private long lastClickTime;
@Override
public void onClick(View v) {// 限制多次点击
long currentTime = System.currentTimeMillis();
if (currentTime - lastClickTime > MIN_CLICK_DELAY_TIME) {// 两次点击的时间间隔大于最小限制时间,则触发点击事件
lastClickTime = currentTime;
onNoMultiClick(v);
}
}
/**
* 点击事件(相当于@link{android.view.View.OnClickListener})
*
* @param v 使用该限制点击的View
*/
public abstract void onNoMultiClick(View v);
}
- 使用示例
btnLogin.setOnClickListener(new NoMultiClickListener() {
@Override
public void onNoMultiClick(View v) {
if (TextUtils.isEmpty(editTextPhoe.getText().toString())) {
showToast("输入的手机号不能为空!");
}
}
});
3.解决多个View共用一个NoMultiClickListener监听器引发的问题
当我们使用多个控件绑定同一个NoMultiClickListener监听器的时候,我们会发现点击一个触发单击事件后,再点击其它绑定该监听的组件就触发不了单击事件了!!!
看到这里。。。相信大家都已经想好解决办法了吧。。其实上面已经实现了对一个控件的限制,那么我们只需要给所有控件单独绑定上最后的点击时间然后根据view.getId()取值按照上述逻辑判断即可,修订后的具体代码如下:
public abstract class NoMultiClickListener implements View.OnClickListener {
// 两次点击按钮之间的最小点击间隔时间(单位:ms)
private static final int MIN_CLICK_DELAY_TIME = 3000;
// 记录所有绑定该监听器View的最后一次点击时间
private SparseArray<Long> lastClickViewArray = new SparseArray<>();
@Override
public void onClick(View v) {// 限制多次点击
long currentTime = System.currentTimeMillis();
long lastClickTime = lastClickViewArray.get(v.getId(), -1L);// 获取该view最后一次的点击时间,默认为-1
if (currentTime - lastClickTime > MIN_CLICK_DELAY_TIME) {// 两次点击的时间间隔大于最小限制时间,则触发点击事件
lastClickViewArray.put(v.getId(), currentTime);
onNoMultiClick(v);
}
}
/**
* 点击事件(相当于@link{android.view.View.OnClickListener})
*
* @param v 使用该限制点击的View
*/
public abstract void onNoMultiClick(View v);
}