1、事件在Android中的传递顺序
Activity--> Window-->DecorView --> 布局View
![](https://img.haomeiwen.com/i26777047/c5398d92cbf1a77a.png)
或者说以上顺序是事件在应用层的传递顺序。如果要说整个事件的传递机制,是Android底层收到触摸屏的事件后,使用socket跨进程通信,用InputDispatcher将事件发送给APP进程,由主线程的Looper去取出消息进行处理。
2、事件的传递规则
一个点击事件,或者说触摸事件,被封装为了一个MotionEvent。事件的分发主要由三个重要的方法来完成:
1、分发:dispatchTouchEvent;
2、拦截:onInterceptTouchEvent;
3、处理:onTouchEvent;
如果是ViewGroup容器类view,则以上三个方法都会用到。但是如果是View类型的,不能包含子view,那就没有第二个拦截方法,因为没有子view的话,拦截方法的就是多余的,只有ViewGroup才会有拦截。
MotionEvent的类型
![](https://img.haomeiwen.com/i26777047/164565d29e7c5972.png)
![](https://img.haomeiwen.com/i26777047/6de537682431d9f2.png)
1、public boolean dispatchTouchEvent(MotionEvent ev)
此方法用来处理事件的分发,当事件传递给当前view时,
首先就是通过调用此方法来进行传递的,
如果当前view锁包含的子view的dispatchTouchEvent方法或者当前view的onTouchEvent处理了事件, 通常返回true, 表示事件已消费。如果没有处理则返回false。
2、public boolean onInterceptTouchEvent(MotionEvent ev)
此方法用来判断某个事件是否需要拦截,如果某个view拦截了此事件,那么同时在这个事件序列中,此方法不会被再次调用,因为会把当前view赋值给mFirstTouchTarget对象(原本为null),后续父必问判断mFirstTouchTarget != null时,就会去调用它的onTouchEvent方法,交给mFirstTouchTarget处理事件。
3、public boolean onTouchEvent(MotionEvent ev)
用来处理事件,如果事件被消耗了,通常就返回true, 如果不做处理,否则返回false,并且在同一个时间序列中,当前view不会再接受到事件。
以上三个方法的关系可以用以下伪代码来表示:
![](https://img.haomeiwen.com/i26777047/9e173637349e2c85.png)
根据以上伪代码和图片展示的流程图,我们梳理一下从根ViewGroup(也就是DecorView)往下传递事件的过程:
- 首先,事件产生后,通过调用根ViewGroup的dispatchTouchEvent方法,
然后,如果这个ViewGroup要拦截事件, 则它的onInterceptTouchEvent返回true,然后事件交给它的onTouchEvent处理,不再进行传递。如果不拦截,则继续调用子view的dispatchTouchEvent方法,继续往下传递,往下递归,直到最终被处理。
- 如果没有任何一个view处理事件, 则最终又会回调给Activity的onTouchEvent方法,如果Activity也不处理,则此事件结束,且没做任何处理。
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:
是Activity的方法,如果当事件开始传递前,我们需要额外处理一些操作,可以用onUserInteraction()中进行处理,这本身是一个空方法;
注释2:
getWindow返回的就是DecorView对象,相当于最顶层的ViewGroup,然后就开始往下层的view传递, 如果事件在view的传递中被处理,则返回true,否则就调用注释3的代码;
注释3:
如果事件在view的传递中未被处理,则调用Activity自己的onTouchEvent方法。
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);
}
发现直接使用的父类,也就是ViewGroup的dispatchTouchEvent方法,这和我们前面的分析就一一对上了,我们进入ViewGroup的dispatchTouchEvent方法,
代码很长,先看这一段代码:
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
// restore action in case it was changed
intercepted = onInterceptTouchEvent(ev);ev.setAction(action);
} else {
intercepted = false;
}
} else {
intercepted = true;
}
这一段代码是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){
//如果为ACTION_DOWN,则重置标记位
cancelAndClearTouchTargets(ev);resetTouchState();
}
所以以上两段代码可以得出:
- 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.2ev.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 indexfor ( 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;
}
上述代码中:
注释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) {....}
}
如果mFirstTouchTarget == null, 则没有子view处理事件,则交给父view处理, 否则就去子view处理事件。
到此,应用层的整个事件传递机制就分析完了。
网友评论