在一个Activity中,给一个控件(比如:Button)添加一个点击事件,有两种方式:
// 1. 设置 onClick 事件
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e("chris", "button.onClick");
}
});
// 2. 设置 onTouch 事件
button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.e("chris", "button.onTouch = " + MotionEvent.actionToString(event.getAction()) + "[" + event.getX() + "," + event.getY() + "]");
return false;
}
});
如果同时添加了这两种事件监听,结果会如何呢?
2017-12-23 16:43:09.539 E/chris: button.onTouch = ACTION_DOWN[108.468994,61.447388]
2017-12-23 16:43:09.571 E/chris: button.onTouch = ACTION_UP[108.468994,61.447388]
2017-12-23 16:43:09.573 E/chris: button.onClick
我们可以看到:
- onTouch比onClick优先执行;
- 默认情况下,onTouch会执行DOWN、0或多个MOVE、以及UP事件;
- UP事件之后才会执行onClick事件;
在上面第2点中提到,默认情况,我们看onTouch是有一个返回值(默认返回false),如果改为返回true呢?
2017-12-23 16:45:29.669 E/chris: button.onTouch = ACTION_DOWN[108.468994,61.447388]
2017-12-23 16:45:29.671 E/chris: button.onTouch = ACTION_UP[108.468994,61.447388]
这时,就只有onTouch事件,没有onClick事件。
因为大家的理解是:返回true,表明onTouch就消费了这些事件,这些事件就不会再后续传递下去。
从源码层面来理解
任何触摸产生的事件,都会从硬件(屏幕)传至Android系统,再传递给Window,然后从根视图开始传递事件,直到该控件,然后调用该控件的事件分发(dispatchTouchEvent),该方法在父类View中可查到:
/**
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTouchEvent(MotionEvent event) {
......
boolean result = false;
......
if (onFilterTouchEventForSecurity(event)) {
......
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
......
return result;
}
以上代码为事件处理的核心代码:
- 如果mOnTouchListener不为null,且该view支持touch,就会进入onTouch方法中(也就是我们注册的listener);
- 如果onTouch返回false,才会进入onTouchEvent(onTouch返回true,即 result = true,就不会进入onTouchEvent了);
- 后面没有什么重要代码,因此 onClick 一定是在 onTouchEvent 方法中被调用;
onTouchEvent源码分析:
public boolean onTouchEvent(MotionEvent event) {
......
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
......
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
......
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
......
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
......
}
......
break;
}
return true;
}
return false;
}
前面的打印日志也表明,在ACTION_UP之后,才会响应 onClick ,因此,我们只需要关注 onTouchEvent 中的 UP 事件。
上面的代码最终就会走到 PerformClick / performClickInternal:
private final class PerformClick implements Runnable {
@Override
public void run() {
performClickInternal();
}
}
private boolean performClickInternal() {
......
return performClick();
}
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;
}
......
return result;
}
最终我们看到了我们想要的 onClick !
网友评论