Android事件分发

Android事件分发机制首先会将点击事件传递到Activity中,具体是执行dispatchTouchEvent()进行事件分发。

Activity#dispatchTouchEvent

1
2
3
4
5
6
7
8
9
10
11
   public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
//如果返回true,Activity.dispatchTouchEvent就返回true,则方法结束。

if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}

getWindow()返回的是PhoneWindow,内部调用的是DecorView的dispatchTouchEvent方法。
DecorView继承FrameLayout,也是所有页面的父类。FrameLayout是ViewGroup的子类,故Activity的事件分发与ViewGroup一致。

当一个点击事件未被Activity下任何一个View接收/处理,则会调用Activity的onTouchEvent方法:
Activity#onTouchEvent

1
2
3
4
5
6
7
8
9
public boolean onTouchEvent(MotionEvent event) {
//当点击事件在window边界外才会被返回true,
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}

return false;
}

Window#shouldCloseOnTouch

1
2
3
4
5
6
7
8
9
10
11
//判断是否是边界外点击事件的判断    
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
final boolean isOutside =
event.getAction() == MotionEvent.ACTION_UP && isOutOfBounds(context, event)
|| event.getAction() == MotionEvent.ACTION_OUTSIDE;
if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) {
return true;
}

return false;
}
  1. mCloseOnTouchOutside是个boolean变量,它是由Window的android:windowCloseOnTouchOutSize属性值决定的,在Dialog中经常遇到需要点击Dialog区域之外Dialog消失,其实就李控制的。
  2. isOutOfBounds(context, event)是判断该event的坐标是否在当前Activity之外,是的话,返回true,否则为false
  3. peekDecorView()就是Decorview

shouldCloseOnTouch方法的意思就是说,如果设置了android:windowCloseOnTouchOutSize=true,并且当前触摸事件的坐标在Activity之外。

  1. Activity中的dispathcTouchEvent将触发事件传递给Activity所包含的试图,具体的实现方法是通过调用Activity所属的Window的superDispatchTouchEvent,进而调用DecorView的superDispatcherTouchEvent,最用调用ViewGroup的DispatchTouchEvent。
    a. 如果Activity所包含的视图拦截或者消费了该触摸事件,则不会执行Activity的onTouchEvent 方法。
    b. 如果Activity所包含的视图没有拦截或消费该触摸事件,则会继续执行Activity的onTouchEvent方法。
  2. Activity中onTouchEvent是Activity自身对触摸事件的处理。
    a. 如果该Activity的android:windowCloseOnTouchOutSize属性为true,并且当前触摸事件的坐标在Activity之外,同时Activity的DecorView视图不为空,就会导致Activity被结束
    ViewGroup的事件分发机制

从Activity的dispatchTouchEvent可知,最终的实现走的就是ViewGroup的dispatchTouchEvent:

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
public boolean dispatchTouchEvent(MotionEvent ev) {
//验证事件是否连续
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
//这个变量用于记录事件是否被处理完
boolean handled = false;
//过滤掉一些不合法的事件:当前的View的窗口被遮挡了。
if (onFilterTouchEventForSecurity(ev)) {
//如果事件发生的View在的窗口,没有被遮挡
final int action = ev.getAction();
//重置前面为0 ,只留下后八位,用于判断相等时候,可以提高性能。
final int actionMasked = action & MotionEvent.ACTION_MASK;
//判断是不是Down事件,如果是的话,就要做初始化操作
if (actionMasked == MotionEvent.ACTION_DOWN) {
//如果是down事件,就要清空掉之前的状态,比如,重置手势判断什么的。
//比如,之前正在判断是不是一个单点的滑动,但是第二个down来了,就表示,不可能是单点的滑动,要重新开始判断触摸的手势
//清空掉mFirstTouchTarget
// 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();
}


//检查是否拦截事件
final boolean intercepted;
//如果当前是Down事件,或者已经有处理Touch事件的目标了
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//判断允不允许这个View拦截
//使用与运算作为判断,可以让我们在flag中,存储好几个标志
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//如果说允许拦截事件
if (!disallowIntercept) {
//确定是不是拦截了
intercepted = onInterceptTouchEvent(ev);
//重新恢复Action,以免action在上面的步骤被人为地改变了
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.
//如果说,事件已经初始化过了,并且没有子View被分配处理,那么就说明,这个ViewGroup已经拦截了这个事件
intercepted = true;
}

// Check for cancelation.
//如果viewFlag被设置了PFLAG_CANCEL_NEXT_UP_EVENT ,那么就表示,下一步应该是Cancel事件
//或者如果当前的Action为取消,那么当前事件应该就是取消了。
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;

// Update list of touch targets for pointer down, if needed.
//如果需要(不是取消,也没有被拦截)的话,那么在触摸down事件的时候更新触摸目标列表
//split代表,当前的ViewGroup是不是支持分割MotionEvent到不同的View当中
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
//新的触摸对象,
TouchTarget newTouchTarget = null;
//是否把事件分配给了新的触摸
boolean alreadyDispatchedToNewTouchTarget = false;
//事件不是取消事件,也没有拦截那么就要判断
if (!canceled && !intercepted) {
//如果是个全新的Down事件
//或者是有新的触摸点
//或者是光标来回移动事件(不太明白什么时候发生)
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
//这个事件的索引,也就是第几个事件,如果是down事件就是0
final int actionIndex = ev.getActionIndex(); // always 0 for down
//获取分配的ID的bit数量
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;

// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
//清理之前触摸这个指针标识,以防他们的目标变得不同步。
removePointersFromTouchTargets(idBitsToAssign);

final int childrenCount = mChildrenCount;
//如果新的触摸对象为null(这个不是铁定的吗)并且当前ViewGroup有子元素
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 View[] children = mChildren;
//是否使用自定义的顺序来添加控件
final boolean customOrder = isChildrenDrawingOrderEnabled();
for (int i = childrenCount - 1; i >= 0; i--) {
//如果是用了自定义的顺序来添加控件,那么绘制的View的顺序和mChildren的顺序是不一样的
//所以要根据getChildDrawingOrder取出真正的绘制的View
//自定义的绘制,可能第一个会画到第三个,和第四个,第二个画到第一个,这样里面的内容和Children是不一样的
final int childIndex = customOrder ?
getChildDrawingOrder(childrenCount, i) : i;
final View child = children[childIndex];
//如果child不可以接收这个触摸的事件,或者触摸事件发生的位置不在这个View的范围内
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
//获取新的触摸对象,如果当前的子View在之前的触摸目标的列表当中就返回touchTarget
//子View不在之前的触摸目标列表那么就返回null
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.
//如果新的触摸目标对象不为空,那么就把这个触摸的ID赋予它,这样子,
//这个触摸的目标对象的id就含有了好几个pointer的ID了

newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
//如果子View不在之前的触摸目标列表中,先重置childView的标志,去除掉CACEL的标志
resetCancelNextUpFlag(child);
//调用子View的dispatchTouchEvent,并且把pointer的id 赋予进去
//如果说,子View接收并且处理了这个事件,那么就更新上一次触摸事件的信息,
//并且为创建一个新的触摸目标对象,并且绑定这个子View和Pointer的ID
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
mLastTouchDownIndex = childIndex;
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//这里给mFirstTouchTarget赋值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
//如果newTouchTarget为null,就代表,这个事件没有找到子View去处理它,
//那么,如果之前已经有了触摸对象(比如,我点了一张图,另一个手指在外面图的外面点下去)
//那么就把这个之前那个触摸目标定为第一个触摸对象,并且把这个触摸(pointer)分配给最近添加的触摸目标
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}

// Dispatch to touch targets.
//如果没有触摸目标
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
//那么就表示我们要自己在这个ViewGroup处理这个触摸事件了
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
//遍历TouchTargt树.分发事件,如果我们已经分发给了新的TouchTarget那么我们就不再分发给newTouchTarget
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
//是否让child取消处理事件,如果为true,就会分发给child一个ACTION_CANCEL事件
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//派发事件
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
//cancelChild也就是说,派发给了当前child一个ACTION_CANCEL事件,
//那么就移除这个child
if (cancelChild) {
//没有父节点,也就是当前是第一个TouchTarget
//那么就把头去掉
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
//把下一个赋予父节点的上一个,这样当前节点就被丢弃了
predecessor.next = next;
}
//回收内存
target.recycle();
//把下一个赋予现在
target = next;
//下面的两行不执行了,因为我们已经做了链表的操作了。
//主要是我们不能执行predecessor=target,因为删除本节点的话,父节点还是父节点
continue;
}
}
//如果没有删除本节点,那么下一轮父节点就是当前节点,下一个节点也是下一轮的当前节点
predecessor = target;
target = next;
}
}

// Update list of touch targets for pointer up or cancel, if needed.
//遇到了取消事件、或者是单点触摸下情况下手指离开,我们就要更新触摸的状态
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
//如果是多点触摸下的手指抬起事件,就要根据idBit从TouchTarget中移除掉对应的Pointer(触摸点)
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}

if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}

viewGroup的dispatchTouchEvent

  1. 内部会走onIntercepTouchEvent方法判断是否拦截
  2. 如果拦截,执行super.dispatchTouchEvent
    a. 也就是View的dispatchTouchEvent
    ⅰ. View的dispatchTouchEvent内如果设置了OnTouchListener则先执行
    ⅱ. 如果OnTouchListener的OnTouch返回false,则执行onTouchEvent
    ⅲ. 在onTouchEvent的MotionEvent.ACTION_UP会执行onClick
  3. 如果不拦截,调用子view(ViewGroup或View )的dispatchTouchEvent,重复步骤1
  1. onIntercepTouchEvent返回值true表示事件拦截,onTouch/onTouchEvent返回值true表示事件消费
  2. 触摸事件先交给Activity.dispatchTouchEvent,再一层层往下分发,当前中间的ViewGroup都不拦截时,进入最底层的View后,开始由最底层的onTouchEvent来处理,如果一直不消费,则最后返回到Activity.onTouchEvent
  3. ViewGroup才有onIntercepTouchEvent拦截方法,在分发的过程中,中间任何一层ViewGroup都可可以直接拦截,则不再往下分发,而交由发送拦截的ViewGroup的onTouchEvent来处理
  4. 子View可调用requestDisallowInterceptorTouchEvetn方法,设置disallowIntercept=true,从而阻止父ViewGroup的onIntercepTouchEvent拦截操作
  5. onTouchEvent由下往上冒泡时,当中间任何一层的onTouchEvent消费该事件,则不再往上传递,表示事件已经处理
  6. 如果View没有消费ACTION_DOWN事件,则之后的ACTION_MOVE等事件都不会再接收
  7. 只要View.onToucherEvent是可点击或可长按,则消费该事件
  8. onTouch优先返回true,则不再执行onToucherEvent,否则会执行onToucherEvent,onTouch只有View设置了OnTouchListener,并且是enable才会执行