在Android开发中,我们不免碰到点击响应和触摸事件,最常见的栗子就是定义一个按钮,然后设置它的点击/触摸监听器:
...
Button btn = (Button) findViewById(R.id.btn);
// click
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.i("Event Dispatch", "button click");
}
});
// touch
btn.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i("Event Dispatch", "button touch");
return false;
}
});
...
既设置了点击监听,又设置了触摸监听,那么谁会先触发呢,运行程序看log输出:
12-21 14:49:44.350 12994-12994/com.sye.eventdispatchdemo I/Event Dispatch: button touch
12-21 14:49:44.411 12994-12994/com.sye.eventdispatchdemo I/Event Dispatch: button touch
12-21 14:49:44.437 12994-12994/com.sye.eventdispatchdemo I/Event Dispatch: button click
很明显,先执行的是onTouch
方法,然后再执行的是onClick
方法,下面从这里为入口来分析View
的事件分发机制。
相信熟悉Android的童鞋都知道,在我们触摸屏幕时,之前会触发Activity::dispatchTouchEvent
方法,然后通过Activity
对触摸事件的分发到相应父布局的dispatchTouchEvent
方法,当然,是指Activity
所属的顶层View
没有拦截掉触摸事件,这个后面再分析。父布局指的是LinearLayout
,FrameLayout
等一些常用布局,如果看了源码的童鞋就会知道这些类没有dispatchTouchEvent
方法的,但是它们都是继承于ViewGroup
,所以在Activity
将事件分发到ViewGroup
,最后再由ViewGroup
将事件分发到对应的target View
中处理,流程大致是这样,但是我们先不分析ViewGroup
的事件分发,我们先从源码的角度来看一下更加简单的View
的事件分发。
我们还是用上面的例子来介绍View
的事件分发,当我们点击Button
时,会触发Button
的dispatchTouchEvent
方法,而在Button
类中并没有dispatchTouchEvent
,那么就从它的父类TextView
中找,但是在TextView
中也没有,再往上在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) {
...
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;
}
...
}
因为代码比较长,只贴关键的代码了。ListenerInfo
类是View
类中的一个静态类,主要作用是保存View
的一些监听,例如OnLongClickListener
,OnClickListener
等等,这些可以通过阅读源码来了解。然后这里是将mListenerInfo
赋值给li
对象,在第一个if条件中有一个判断li.mOnTouchListener.onTouch(this, event)
,调用的是li
对象的mOnTouchListener::onTouch
方法,那么,我们就要找到li
对象的mOnTouchListener
是在哪里初始化的,其实就是mListenerInfo.mOnTouchListener
是在哪里初始化的。
比较容易会发现在View
类中有这样一个方法定义:
/**
* Register a callback to be invoked when a touch event is sent to this view.
* @param l the touch listener to attach to this view
*/
public void setOnTouchListener(OnTouchListener l) {
getListenerInfo().mOnTouchListener = l;
}
很明显,这里就是初始化mOnTouchListener
的地方,而setOnTouchListener
方法是我们最熟悉不过的方法了,它就是我们的栗子中触摸监听,所以在输出Log时首先会输出button touch
,为什么会输出两次,因为触摸事件分为ACTION_DOWN
和ACTION_UP
。
细心的童鞋也许会发现在栗子中OnTouchListener::onTouch
方法还有一个返回值,这个返回值有什么用处呢,它默认为return false
的,我们把它改成return true
,再看看Log,就变成这样了:
12-21 17:15:44.227 22589-22589/com.sye.eventdispatchdemo I/Event Dispatch: button touch
12-21 17:15:44.281 22589-22589/com.sye.eventdispatchdemo I/Event Dispatch: button touch
是的,Click
事件没有了!
为什么会没有,我们来分析下,在上面View
类的dispatchTouchEvent
方法中第一个if条件中的li.mOnTouchListener.onTouch(this, event)
正好是我们栗子中的返回值,当我们return true
时,相应的result = true
(前面的三个条件会为true
),那么下面的第二个if条件中的onTouchEvent
方法就不会执行,到这里有的童鞋就会想,点击事件的处理难道会在onTouchEvent
方法中?来看看就知道了:
public boolean onTouchEvent(MotionEvent event) {
...
switch (action) {
case MotionEvent.ACTION_UP:
...
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)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
...
break;
...
...
}
根据栗子的Log,Click
事件应该是在ACTION_UP
时触发的,所以我们只需要看MotionEvent.ACTION_UP
条件即可。在这个条件中会执行performClick
方法,一是因为PerformClick
这个Runnable
中会执行performClick
方法:
private final class PerformClick implements Runnable {
@Override
public void run() {
performClick();
}
}
二是如果!post(mPerformClick)
返回为false
,也会执行performClick
方法。
我们来看看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;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}
这段代码与前面dispatchTouchEvent
方法中那段代码有点相似,li
对象的mOnTouchListener
换成了mOnClickListener
,同样的,我们找到mOnClickListener
在哪里赋值的就行了,具体代码如下:
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
是不是很明白了,这就是我们栗子中设置的点击监听事件!然后再在performClick
方法中回调onClick
方法。
经过上面的源码分析,View
的事件分发机制大致应该清楚了。下面来总结一下:
首先点击一个View
,会调用其dispatchTouchEvent
方法,在dispatchTouchEvent
方法中会先调用mOnTouchListener::onTouch
回调方法,如果回调return true
,则不会执行onTouchEvent
方法,而click
事件就是在onTouchEvent
方法中执行的,所以当return false
时才会响应click
事件。
为了更好理解,贴出上面栗子的时序图:
留下一个问题:dispatchTouchEvent
方法的返回值作用是什么?
下一篇文章介绍ViewGroup
的事件分发机制,以及在点击屏幕后,上层事件是怎么传递的。并且上面的问题答案也会出来。
ps:源码为Android N
的源码