说到事件分发,我们都应该有一些了解,再开发的多多少少都会碰到一些事件冲突(比如滑动冲突),利用事件分发机制就可以解决,对于事件分发机制我虽然看过很多文章,但自己不做总结,总有点一知半解所以有了这片文章。好了说正文。
当一个点击操作发生时,事件分发是从Activity 的dispatchTouchEvent方法开始的,看源码可知实现在Window的 superDispatchTouchEvent方法中,是一个抽象方法,我们需要找到Window的实现类。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
Window的实现类是什么呢?就是PhoneWindow,它是如何处理点击事件呢
public boolean superDispatchTouchEvent(MotionEvent event){
return mDecor.superDispatchTouchEvent(event);
}
可以清楚的看出将处理传给了DecorView,这个View也就是我们常说的根View,它是一个ViewGroup,我们Activity中setContentView其实就是将我们的布局做根View的子View。
根据上面的分析,事件分发通过Activity分发后,然后会流向顶级View(一般是ViewGroup),在通过ViewGroup的dispatchTouchEvent方法分发给我们的布局view。在介绍过程之前,先说三个重要的方法
//事件分发器,
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
return super.dispatchTouchEvent(ev);
}
//事件拦截器,只存在于ViewGroup
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return super.onInterceptTouchEvent(ev);
}
//事件处理器
@Override
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event);
}
到了根View后事件分发流程分为两种,一种是ViewGroup,一种是View。
ViewGroup中的事件分发,先来看看dispatchTouchEvent方法中的代码
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. //清除TouchTarget链表数据,重置一些状态 cancelAndClearTouchTargets(ev); resetTouchState(); } // Check for interception. intercepted为false代表viewGroup不拦截,true代表viewGruop拦截 final boolean intercepted; //为down事件时进入判断,或者mFirstTouchTarget被赋值了,也就是子类消费了事件 if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { //disallowIntercept为true代表viewGroup禁止拦截,为false代表viewGroup可以拦截事件 //mGroupFlags 子类通过调用requestDisallowInterceptTouchEvent方法可以改变其值,但在down事件时会被重置
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { //当没有禁止拦截时,进入到viewGroup的onInterceptTouchEvent拦截方法中 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; } ................ }
一开始就是为了过滤掉一些事件,很明显不会过滤down move up事件,当为down事件时,会做清除TouchTarget链表数据和重置状态操作,这一步操作也就是我们不能在子View中禁止父view拦截down事件的原因,接着往下看到了mFirstTouchTarget ,它是TouchTarget链表的头节点,当有子类消费了事件时,它就会被赋值,接着进入到判断,disallowIntercept表示的是禁止拦截,子类之所以可以禁止父View拦截事件,调用requestDisallowInterceptTouchEvent方法改变了mGroupFlags值,所以在move、up事件可以禁止父类拦截事件,disallowIntercept为false时,进入到ViewGroup的onInterceptTouchEvent拦截器,disallowIntercept为true,intercepted为false,表示表示ViewGroup不拦截事件,intercepted为true表示ViewGroup拦截事件。
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
//判断事件是否取消,或者是viewGroup否拦截,不拦截不取消,则进入viewGroup将事件分发给子View的流程
if (!canceled && !intercepted) {
// If the event is targeting accessibility focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
.....................
}
.....................
//如果事件被viewGroup拦截,或者没有子View消费,则事件又回到ViewGroup,进入到view的事件分发流程
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}
当intercepted拦截了down事件时,intercepted为true,事件就由dispatchTransformedTouchEvent流向ViewGroup的onTouchEvent方法中,如果onTouchEvent不做处理最终流向Activity,也就是View的事件分发流程,下文会介绍,我们先来分析
intercepted为false,不拦截事件时,做了一些什么操作。
final ArrayList preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//开始遍历子view,寻找是哪个view消费了事件
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
....................
//判断view是否可见或者有动画在执行
if (!canViewReceivePointerEvents(child)
//判断事件是否落入该view范围
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
...................
//将事件分发给该子view,进入到子view的事件分发流程,
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//如果子view消费了事件,则在addTouchTarget 为mFirstTouchTarget赋值,然后跳出循环
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
只截取了部分利于分析的代码,开始可以分明显的看出去遍历每个子View,然后就是去判断子View是否有动画或者可见 && 点击事件是否落在子View范围内,如果不满足条件就进行下一次循环,满足条件则会执行dispatchTransformedTouchEvent将事件分发给该子View,当该子View消费了事件,addTouchTarget方法就会被执行,这个方法里面会给mFirstTouchTarget赋值,前面已经介绍了,当该子View不消费事件时,最终事件又会流向ViewGroup的onTouchEvent方法。
View的事件分发流程,同样先来看dispatchTouchEvent方法中的代码
if (onFilterTouchEventForSecurity(event)) { if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) { result = true; } //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; // 这里判断该view有没有使用OnTouchListener监听,有没有在回调方法onTouch中消费事件,并且该view是不是可以点击 // 如果上述条件符合,返回true,表示消费了事件,可明显看出onTouch比onTouchEvent优先级高 if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } //如果前面没有消费,则进入到该view的onTouchEvent方法 if (!result && onTouchEvent(event)) { result = true; } } if (!result && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } // Clean up after nested scrolls if this is the end of a gesture; // also cancel it if we tried an ACTION_DOWN but we didn't want the rest // of the gesture. if (actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL || (actionMasked == MotionEvent.ACTION_DOWN && !result)) { stopNestedScroll(); }
注释已经很清楚了,我就不重复描述了,接着看onTouchEvent方法中的代码
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE; ............................... if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) { 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)) { performClickInternal(); } ............................ break; } return true; }
上述代码表明只要View clickable或者long_clickable有一个属性为true, onTouchEvent就会消耗事件,当为up事件时,会去执行performClick()方法,在这个方法中回去调用我们经常用的监听事件onClick。至于onLongClick事件在down事件时就会去发送延时意图,等待调用,当onClick执行前就会去移除长按回调方法。
事件分发介绍完了,如有问题,欢迎指正。