每一个Android开发者,都绕不开View的事件分发,理解好View的事件分发机制,有利于我们解决各种与设备触摸交互的问题,同时也利于我们实现更复杂、炫酷的自定义View效果。
关于Android View的事件分发机制,笔者打算分为三篇文章来描述,分别为:
(1)Android View事件分发机制之概念理论篇
(2)Android View事件分发机制之源码解读篇
(3)Android View事件分发机制之滑动冲突实战解决篇
本文是第二篇:Android View事件分发机制之源码解读篇
在开始解读源码前,我们先确定好我们的目标,即我们想通过解读源码而理解、明白什么问题:
(1)如果一个View同时监听onClickListener和onTouchListener ,哪个会先被执行呢?
(2)如果设置了onClickListener,onClick一定会被执行吗?
(3)如果一个View是disable状态,是否还会响应点击事件?clickable为false呢?
(4)onTouch()和onTouchEvent()有什么区别?如何去区分呢?
(5)为什么说事件是由外到内的分发,而由内到外的响应呢?
(6)子View能否阻止父对事件的拦截?如果能,该如何处理?
(7)为什么会出现滑动冲突?什么情况下会出现滑动冲突?
(8)在什么情况下会出现ACTION_CANCEL事件?
(9)requestDisallowInterceptTouchEvent能完全阻止父元素拦截事件吗?
我们接下来就带着这9个问题,去仔细解读一下源码,如果从源码层次理解了这几个问题,那么对View的事件分发机制就掌握得比较通透了。
既然事件分发是从dispatchTouchEvent开始,我们就直接从dispatchTouchEvent为切入点吧。
在上篇:View事件分发机制之概念理论篇 我们讲到,事件分发的一个顺序是:
Activity -> Window -> ViewGroup -> ViewGroup -> … (n个ViewGroup ) -> View
那么我们就直接从Activity的dispatchTouchEvent开始,下面一张图是笔者触发一个ImageView的onTouch方法时的调用栈,从这个调用栈也可以看到,事件分先从Activity开始分发,然后再一层一层往下传:
Activity#dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
// 在这里调用了window的dispatchTouchEvent
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
getWindow()是哪个呢?直接看源码:
public Window getWindow() {
return mWindow;
}
// mWindow在Activity的attach方法实例化了
final void attach() {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window, activityConfigCallback);
// 省略其他代码
}
所以,我们直接看PhoneWindow的superDispatchTouchEvent方法
PhoneWindow#superDispatchTouchEvent
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
代码继续告诉我们应该找mDecor的回调,这个mDecor就我们我们前文提到的DecorView了:
PhoneWindow
private DecorView mDecor;
一步一步的调用,最终来到了ViewGroup的dispatchTouchEvent。
由于ViewGroup的dispatchTouchEvent的代码比较长,我们截取关键的部分来看:
ViewGroup#dispatchTouchEvent#代码片段1
检查事件是否拦截
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
我们来简单分析一下ViewGroup处理事件拦截的逻辑:
先明确一件事,通过源码能知道,在整个ViewGroup类中,就只有这里调用到了onInterceptTouchEvent,然后我们来看,onInterceptTouchEvent的触发条件有两层:
(1)actionMasked == MotionEvent.ACTION_DOW || mFirstTouchTarget != null
(2)(mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0
结果为false时,即mGroupFlags 不包含FLAG_DISALLOW_INTERCEPT这个标志位时(这里为何说包含而不说等于,可看笔者的另外一篇关于Android中的位运算的文章)
对于第一层,当事件是ACTION_DOWN时,会进入第二层判断;或者当mFirstTouchTarget不为null时,也进入第二层判断。这里可以先简单说明一下,mFirstTouchTarget是后续的处理中,如果ViewGroup有子元素对事件进行消耗处理了,mFirstTouchTarget就会被赋值为那个处理的子元素,这时,mFirstTouchTarget就不会为null。
进入第二层之后,会通过一个位运算(mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0
判断一下当前View