您的位置:首页 > 移动开发 > Android开发

Android事件分发机制详解(一)

2016-12-24 15:15 337 查看

点击/触摸事件

在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
的事件分发机制。

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
是在哪里初始化的。

比较容易会发现
4000
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
的源码
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  事件分发