ViewGroup事件分发的源码解析
虽然ViewGroup是继承自View,但ViewGroup和View的事件分发的处理还是不一样的,所以这里分开来讲。
当点击事件到达ViewGroup时,会调用ViewGroup的dispatchTouchEvent方法。而dispatchTouchEvent方法中又会调用onInterceptTouchEvent方法,这时会出现下面两种情况:
如果该ViewGroup拦截该事件,则onInterceptTouchEvent方法返回true,该事件将由该ViewGroup处理,如果ViewGroup的mOnTouchListener被设置,则mOnTouchListener回调中的onTouch方法会被调用,否则onTouchEvent会被调用。也就是说,如果都提供的话,mOnTouchListener会屏蔽掉onTouchEvent。在onTouchEvent中,如果设置了mOnClickListener,则onClick会被调用。
如果该ViewGroup不拦截事件,则事件会传递给它所在的点击事件链上的子View,这时子View的dispatchTouchEvent会被调用。到此为止,事件已经从顶级View传递给了下一层View, 接下来的传递过程和顶级View是一致的,如此循环,完成整个事件的分发。
可以看出ViewGroup的事件分发最先从dispatchTouchEvent()方法开始,同时这个方法也是ViewGroup事件分发过程中最主要的方法,方法比较长,我们分段进行解读。dispatchTouchEvent()方法开头便调用了onFilterTouchEventForSecurity()方法,这个方法主要是对触摸事件进行过滤,用于组件安全方面的考虑,比如View在一些特定的条件下需要屏蔽器触摸事件。接下来会监测点击事件如果是ACTION_DOWN时,则会清除所有以前的状态。
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// 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();
}
...
}
resetTouchState()方法中继续调用clearTouchTargets方法。其中我们主要关注对mGroupFlags中FLAG_DISALLOW_INTERCEPT标志的重置,以及设置mFirstTouchTarget为null,在后面分析时会用到这两个对象。
private void resetTouchState() {
clearTouchTargets();
resetCancelNextUpFlag(this);
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
mNestedScrollAxes = SCROLL_AXIS_NONE;
}
private void clearTouchTargets() {
TouchTarget target = mFirstTouchTarget;
if (target != null) {
do {
TouchTarget next = target.next;
target.recycle();
target = next;
} while (target != null);
mFirstTouchTarget = null;
}
}
继续向下,如果当前的点击事件是ACTION_DOWN或者mFirstTouchTarget不为null时执行if代码块,点击事件是ACTION_DOWN这个条件好理解,那么mFirstTouchTarget什么时候不为null呢?这个从后面的代码逻辑可以看出来,当事件由ViewGroup的子元素成功处理时, mFirstTouchTarget会被赋值并指向子元素 ,也就是说当ViewGroup不拦截事件并将事件交由子元素处理时mFirstTouchTarget != null。 反 过 来 ,一旦事件由当前ViewGroup拦截时 ,mFirstTouchTarget != null就不成立。那么当 ACTION_MOVE 和 ACTION_UP 事件到来时,由于(actionMasked == MotionEvent. ACTIQN_DOWN || mFirstTouchTarget != null)这个条件为 false,执行else代码块:intercepted = true,这将导致ViewGroup的onlnterceptTouchEvent不会再被调用,并且同一序列中的其他事件都会默认交给ViewGroup处理。
代码中还有一个比较关键的标志位FLAG_DISALLOW_INTERCEPT,可以看出当事件由子View处理时,该标志位才有意义。这个标记位是通过requestDisallowInterceptTouchEvent方法来设置的,一般用于子View中。FLAG_DISALLOW_INTERCEPT—旦设置后,ViewGroup将不再拦截除了ACTION_DOWN以外的其他点击事件。为什么ACTION_DOWN点击事件无法被拦截呢?这是因为在dispatchTouchEvent()方法开头便针对ACTION_DOWN点击事件会清除所有以前的状态,其中就含有对FLAG_DISALLOW_INTERCEPT标志的重置。所以导致子View中设置的FLAG_DISALLOW_INTERCEPT标志位失效。总结一下:FLAG_DISALLOW_INTERCEPT这个标志的作用是让ViewGroup不再拦截事件,当然前提是ViewGroup不拦截ACTION_DOWN事件。这点很重要,尤其在处理滑动冲突时特别有用。
// 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