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的源码
相关文章推荐
- Android View事件分发机制详解
- Android Touch事件分发深入了解
- Android 事件分发详解及示例代码
- Android事件分发机制
- Android Touch事件的分发机制
- android中touch事件的分发机制
- android 事件分发机制完全解析 从源码开始(上)
- android 事件分发机制完全解析 从源码开始(下)
- android事件分发机制解析(二)
- Android事件分发原理全面解析
- 深入解析Android事件分发机制源码(1)
- Andriod 详解View,ViewGroup的Touch事件的分发机制
- 关于touch事件分发
- Android中的View事件分发
- android事件分发机制
- 菜鸟都能理解的Android中View的事件分发机制及滑动冲突处理
- android事件分发点滴记录
- cocos2dx事件监听器
- 谈谈对dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent的理解
- android view手势冲突的通用解决方法