从方法名来看也不难看出,dispatchTouchEvent方法是用来进行事件分发,onTouchEvent是对事件的处理,onInterceptTouchEvent是对事件的拦截。
onInterceptTouchEvent用于改变事件的传递方向。决定传递方向的是返回值,返回为false时事件会传递给子控件,返回值为true时事件会传递给当前控件的onTouchEvent(),这就是所谓的Intercept(拦截)。
[tisa ps:正确的使用方法是,在此方法内仅判断事件是否需要拦截,然后返回。即便需要拦截也应该直接返回true,然后由onTouchEvent方法进行处理。]
onTouchEvent用于处理事件,返回值决定当前控件是否消费(consume)了这个事件。尤其对于ACTION_DOWN事件,返回true,表示我想要处理后续事件;返回false,表示不关心此事件,并返回由父类进行处理。
可能你要问是否消费了又区别吗,反正我已经针对事件编写了处理代码?答案是有区别!比如ACTION_MOVE或者ACTION_UP发生的前提是一定曾经发生了ACTION_DOWN,如果你没有消费ACTION_DOWN,那么系统会认为ACTION_DOWN没有发生过,所以ACTION_MOVE或者ACTION_UP就不能被捕获。
1、事件在Android中的传递顺序
事件在Android的传递顺序:
Activity--> Window-->DecorView --> 布局View
- 1
或者说以上顺序是事件在应用层的传递顺序。如果要说整个事件的传递机制,是Android底层收到触摸屏的事件后,使用socket跨进程通信,用InputDispatcher将事件发送给APP进程,由主线程的Looper去取出消息进行处理。
本文主要分析Android应用层的一个传递过程。
2、事件的传递规则
一个点击事件,或者说触摸事件,被封装为了一个MotionEvent。事件的分发主要由三个重要的方法来完成:
- 1、分发:dispatchTouchEvent;
- 2、拦截:onInterceptTouchEvent;
- 3、处理:onTouchEvent;
如果是ViewGroup容器类view,则以上三个方法都会用到。但是如果是View类型的,不能包含子view,那就没有第二个拦截方法,因为没有子view的话,拦截方法的就是多余的,只有ViewGroup才会有拦截。
2.1 public boolean dispatchTouchEvent(MotionEvent ev)
此方法用来处理事件的分发,当事件传递给当前view时,首先就是通过调用此方法来进行传递的,如果当前view锁包含的子view的dispatchTouchEvent方法或者当前view的onTouchEvent处理了事件, 通常返回true, 表示事件已消费。如果没有处理则返回false。
2.2 public boolean onInterceptTouchEvent(MotionEvent ev)
此方法用来判断某个事件是否需要拦截,如果某个view拦截了此事件,那么同时个事件序列中,此方法不会被再次调用,因为会把当前view赋值给mFirstTouchTarget对象(原本为null),后续父必问判断mFirstTouchTarget != null时,就会去调用它的onTouchEvent方法,交给mFirstTouchTarget处理事件。
2.3 public boolean onTouchEvent(MotionEvent ev)
用来处理事件,如果事件被消耗了,通常就返回true, 如果不做处理,则发挥false,并且在同一个时间序列中,当前view不会再接受到事件。
以上三个方法的关系可以用以下伪代码来表示:
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;
if(onInterceptTouchEvent(ev)) {
consume = onTouchEvent(ev);
} else {
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
根据以上伪代码和图片展示的流程图,我们梳理一下从根ViewGroup(也就是DecorView)往下传递事件的过程:首先,事件产生后,通过调用根ViewGroup的dispatchTouchEvent方法,然后,如果这个ViewGroup要拦截事件, 则它的onInterceptTouchEvent返回true,然后事件交给它的onTouchEvent处理,不再进行传递。如果不拦截,则继续调用子view的dispatchTouchEvent方法,继续往下传递,往下递归,直到最终被处理。如果没有任何一个view处理事件, 则最终又会回调给Activity的onTouchEvent方法,如果Activity也不处理,则此事件结束,且没做任何处理。
3、事件传递的源码分析
3.1 Activity对事件的传递
前面已经讲到,APP层的事件传递是从Activity看是的,首先就是调用Activity的dispatchTouchEvent, 源码如下:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();//注释1
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;//注释2
}
return onTouchEvent(ev);//注释3
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
注释1 是Activity的方法,如果当事件开始传递前,我们需要额外处理一些操作,可以咋onUserInteraction()中进行处理,这本身是一个空方法;
注释2 getWindow返回的就是DecorView对象,相当于最顶层的ViewGroup,然后就开始往下层的view传递, 如果事件在view的传递中被处理,则返回true,否则就调用注释3的代码;
注释3:如果事件在view的传递中未被处理,则调用Activity自己的onTouchEvent方法。
3.2 View对事件的传递
在上面3.1小节讲到, 通过getWindow().superDispatchTouchEvent(ev),把事件传递给了DecorView。其实在我们做APP开发时,有时就会用到(ViewGroup)getWindow().getDecorView().findViewById(android.R.id.content).getChildAt(0) 这种方式来获取我们给Activity的布局view。
DecorView以前是PhoneWindow的一个内部类,不过现在已经独立成单独的一个类了,查看它的superDispatchTouchEvent方法代码
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
- 1
- 2
- 3
发现直接使用的父类,也就是ViewGroup的dispatchTouchEvent方法,这和我们前面的分析就一一对上了,我们进入ViewGroup的dispatchTouchEvent方法,代码很长,先看这一段代码:
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 {
intercepted = true;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
这一段代码是ViewGroup用来判断是否需要拦截事件,如果为ACTION_DOWN或者mFirstTouchTarget != null,则进入判断。mFirstTouchTarget是个什么东东呢? 从后面代码可以看出,它是ViewGroup的子view,即当事件序列已经由子view进行处理的话,则父view不会再进行拦截。
另外就是FLAG_DISALLOW_INTERCEPT这个标记位,一般用于子view中,如果子view拦截了事件,则这个标记位就会设置为1,以便当前时间序列所有事件都将交给子view,当时当再次传来ACTION_DOWN事件时(也就是一个新的时间序列),这个标记位就会被重置为0。
也就是在上一段代码的前一个位置,有这么一段代码:
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();//如果为ACTION_DOWN,则重置标记位
}
- 1
- 2
- 3
- 4
所以以上两段代码可以得出:
- 1、在一个完整的时间序列中,当某个子View拦截ACTION_DOWN后,后续所有事件都会交给它处理,而父view也不会再去调用onInterceptTouchEvent方法;
- 2、当新来到一个事件序列时(也就是以ACTION_DOWN开头),会重置mFirstTouchTarget以及标记位FLAG_DISALLOW_INTERCEPT等;
接着往下看dispatchTouchEvent的其他代码段,当ViewGroup不拦截事件时的处理,那么事件就会分发给它的子view:
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 (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
if (!canViewReceivePointerEvents(child) //注释3.1
|| !isTransformedTouchPointInView(x, y, child, null)) {//注释3.2
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {//注释3.3
// 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);//注释3.4
alreadyDispatchedToNewTouchTarget = true;
break;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
上述代码中:
- 注释3.1: 用来判断view是否可见,是否在播放动画, 因为不可见或播动画都无法获得焦点;
- 注释3.2: 用来判断子view是否在点击区域中,没在点击区域,自然不用接收事件;
- 注释3.3: 实际上dispatchTransformedTouchEvent方法就是调用子view的dispatchTouchEvent方法,继续让子view分发事件;
- 注释3.4: 完成对mFirstTouchTarget的赋值;
这样,就完成了整个View树对事件的传递。
再往后看:
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);//注释3.5
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
....
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
如果mFirstTouchTarget == null, 则没有子view处理事件,则交给父view处理, 否则就去子view处理事件。
到此,应用层的整个事件传递机制就分析完了。