上一篇讲了Android事件分发的基本概念,这一篇就接着上一篇事件分发接着写后面的故事。
首先先回顾一下上一篇的内容
上一篇主要讲了Android事件分发的三个重要方法以及Activity对事件分发,以及这三个方法之间的关系在这里就复习一下Activity对事件分发的过程
1.首先,当一个事件产生后,首先会传递Activity中。
2.当该事件传递到Activity之后会调用dispatchTouchEvent方法,如果当前Activity不拦截该事件,则会调用他的子View中的dispatchTouchEvent方法,以此循环直到子View拦截该事件。
3.如果当前Activity拦截了此事件,则会进行判断如果OnTouchListener被设置,则onTouch会被调用,否则OnTouchEvent会被调用,如果在OnTouchEvent中设置OnlickListener则onClick会被调用,从这也说明了OnlickListener的优先级最低,而OnTouchListener的优先级则高于OnTouchEvent
一、ViewGroup对事件分发过程
- 事件传递过程
Activity–>window–>顶级View
当事件传递到顶级View是会调用顶级View的dispatchTouchEvent方法,在dispatchTouchEvent中会在如下两种情况下会判断是否要拦截当前事件,下面是View判断是否拦截当前事件的源码
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;
}
解释一下上面的源码
1、
首先会判断当前的事件类型为MotionEvent.ACTION_DOWN或者mFirstTouchTarget != null。当前的事件类型为MotionEvent.ACTION_DOWN很好理解那么mFirstTouchTarget != null是什么呢,其实mFirstTouchTarget 是在ViewGroup不拦截事件将事件交给子View处理时会给mFirstTouchTarget 赋值。
2、
如果 (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null)的条件返回false的时候ViewGroup中的onInterceptTouchEvent(ev)方法将不调用,也就代表默认由当前ViewGroup处理该事件。
上面就是在dispatchTouchEvent中判断是否要拦截当前事件的两种情况。
二.ViewGroup如何将事件向下传递到子View中
如果ViewGroup不拦截当前事件,交给子View处理该事件,则会调用下面的代码。
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
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();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
上面的代码虽然多一点,但是逻辑确非常清晰,首先遍历ViewGroup的所有子元素,判断子元素是否能够接收点击事件,如果子元素能够接收点击事件,则会调用子元素的dispatchTouchEvent方法。这样事件就会交给子元素处理。从而完成一轮事件分发。
如果ViewGroup没有子元素或者子元素处理了点击事件,在这两中情况下ViewGroup就会自己处理点击事件。
二、View对点击事件的处理
View的事件分发(这里不包含ViewGroup)和ViewGroup差不多,但是要比ViewGroup还要简单很多,因为他没有子元素向下将事件传递下去
同样当一个事件产生以后他的传递过程也是 Activity–>window–>顶级View,当传递到View中是会调用VIew的dispatchTouchEvent方法,下面是View的dispatchTouchEvent方法的源码
public boolean dispatchTouchEvent(MotionEvent event) {
...
boolean result = false;
...
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;
}
}
...
return result;
}
这里我省略了一些源码只是将重要部分的代码展示出来,首先他会判断有没有设置onTuchListener,如果OnTouchListener的onTouch方法返回true那么OnTuchEvent方法就不会被调用,这里也再次证实了OnTouchListener是优先级高于OnTuchEvent
- OnTouchListener和OnTuchEven对事件是怎么处理的
OnTouchListener和OnTuchEven他们对事件处理的的方式是相同的,所以在这里我只说OnTuchEven方法
下面是OnTuchEven对事件处理的部分源码
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
从上面源码可以看出,只要View的CLICKABLE 和LONG_CLICKABLE有一个为true,那么他就会消耗这个事件,即OnTuchEven返回true,这里需要说明一下LONG_CLICKABLE是默认为false,而CLICKABLE 的返回值是和View的状态有关,如果当前View是可点击的那么CLICKABLE 则为true,如果当前View是不可点击那么CLICKABLE 则为false
这里android的事件分发机制就全部完了。