touchEvent也是一种Input,所以要从InputManager说起,不管触摸屏幕还是通过cmd命令adb input都是走的InputManger,InputManager会把事件分发对当前获得焦点的ViewRootImpl。即用户进程中。递交到Activity的处理是这样的。
Activity.java
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
PhoneWindow.java
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
//mDecor view inner
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
mDecor就是应用的根View,所以点击的事件就从这里传递下去。可能我们比较关心的是在用户进程的传递,所以接下来的分析都集中在APP的UI进程中。这里很难描述清楚全部细节,所以这里我提出几个问题,带着问题去找答案。
1,点击的事件的传递是否是单向的?
2,我们执行拖拽的时候,讲道理应该是找不到最根部的View。那么是否有规则,使得handle了ACTION_DOWN事件的View可以继续handle后续的TouchEvent?
3,传递过程中,谁来决定事件传递的停止?
重点要分析的代码应该是集中在ViewGroup.java && View.java中,典型的View结构是这样的
ViewGroup
| \
ViewGroup ViewGroup
| |
ViewGroup ...
| \
View View
这里只是简单的分析单点触控。先从ViewGroup说起,以每次ACTION_DOWN为界,ViewGroup会找能handle的ViewGroup
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
...
}
}
所以第一个问题,dispatchTransformedTouchEvent 会把事件dispatchTouchEvent进子View,如果存在的话。从代码中确实可以看到是单向的,自根向下的,从最后一个chrildview到第一个childview,所以越晚add的view,越先被dispatch。
第二个问题在代码中找到一个成员变量mFirstTouchTarget
缓存了上一次ACTION_DOWN获得的对象,TouchTarget是个链表。所以多指触控的时候,是同时缓存了多个对象。所以是针对ACTION_DOWN这一类的按下事件做了特殊处理。
第三个问题,dispatchTouchEvent有返回值,如果返回值是True,那么事件已经被handle。handle之后就返回了,所以事件的传递就中止了。在dispatchTouchEvent中,使用了一个onTouchEvent作为其中一个判断依据,即dispatchTouchEvent会依赖onTouchEvent的处理结果。
需要指出的是ViewGroup中有一个onInterceptTouchEvent的方法,在分发过程中,ViewGroup可以主动停止事件的分发。
// Check for interception.
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;
}
两个dispatchTouchEvent的函数接近两百行代码,不带着问题看容易迷失…
代码主要在
./framework/base/core/java/android/view/ViewGroup.java
./framework/base/core/java/android/view/View.java