基于Android 6.0.1_r10
事件来自哪里?
一般默认情况下,Android 事件传递的起点。点击事件由 Activity 传递给 view 。
事件传递可以用下图表示
备注
上图可以用来表示 ACTION_DOWN 的传递过程。
ACTION_MOVE 、ACTION_UP 会受到 ACTION_DOWN 的影响。
如果 view 的 onToucheEvent 返回 ture,ACTION_MOVE 、ACTION_UP 不再往下传递。
子 View 可以通过 requestDisallowInterceptTouchEvent 让父 ViewGroup 跳过 onInterceptTouchEvent回调 。
谁把事件传递给 Activity
首先,打印 Activity dispatchTouchEvent(MotionEvent ev) 中的线程栈
public boolean dispatchTouchEvent(MotionEvent ev) {
Thread.dumpStack();
return super.dispatchTouchEvent(ev);
}
log 如下
根据上图的线程栈,可以画一个简易的调用关系图
总结一下
ActivityThread 是由 zygote 创建,
ActivityThread 的 main 方法是整个 app 的入口。
ActivityThread 会创建一个 Looper 对象,
设备收到触摸事件,会通过 MessageQueue ,出发 InputEventImpl.DispathcInputEvent()
然后事件会交给 ViewRootImpl 对象处理,
ViewRootImpl 有个成员变量 mView
通过 mView.dispatchPointerEvent() 把事件传递给 PhoneWindow$DecorView
DecorView 是 FrameLayout 的子类。
然后事件传递到 Activity的dispatchTouchEvent
以上内动简单的描述了一下『点击事件』如何传递到 Activity 中,具体的细节可以通过阅读 Android 源码查看。(源码部分比较庞大,这里不贴代码了)
事件到达 Activity 以后
先贴一段代码
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();// 这里是个空方法,留给开发者自己实现
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
以上代码的重点是 getWindow().superDispatchTouchEvent(ev)
其中 getWindow() 会获得 PhoneWindow 实例。
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
mDecor 是 DecorView 的实例,mDecor.superDispatchTouchEvent 代码如下
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
经过千辛万苦,终于到达了 FrameLayout 的 dispatchTouchEvent。
总结 Activity dispatchTouchEvent 的流程(对比上文的事件传递流程图)
如果 ViewGroup dispatchTouchEvent 返回了 true;事件传递结束
如果 ViewGroup dispatchTouchEvent 返回了 false;调用 Activity onTouchEvent
如果 Activity dispatchTouchEvent 不调用 super.dispatchTouchEvent(ev) ,无论返回 ture/false 事件结束。
事件到达 ViewGroup
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 {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
分析以上代码
1、该断代码主要为了给 intercepted 设置一个值。
2、如果满足一下任意条件 intercepted 进入 3 分支 ,否则为 ture
1) actionMasked == MotionEvent.ACTION_DOWN
2)mFirstTouchTarget != null
3、disallowIntercept 不为 true 则
intercepted = onInterceptTouchEvent(ev)
disallowIntercept 为 true
intercepted = false
disallowIntercept 的值,受到 mGroupFlags 影响。
4、如果子 View 调用 requestDisallowInterceptTouchEvent 方法
则 disallowIntercept 为 true
第二部分代码
if (!canceled && !intercepted) {
……
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
……
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
for (int i = childrenCount - 1; i >= 0; i--) {
……
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
……
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
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;
}
……
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
……
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
分析以上代码,有以下几个核心步骤
1、intercepted = false 并且 canceled = false 进入费部分逻辑
2、该部分代码只处理 ACTION_DOWN 事件
3、遍历 viewGroup 并且调用 dispatchTransformedTouchEvent
4、dispatchTransformedTouchEvent 返回 true,会调用 addTouchTarget
其中
dispatchTransformedTouchEvent 会调用子 View 的 dispatchTouchEvent
addTouchTarget 会把子View 添加到 mFirstTouchTarget 链表中
第三部分代码
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
以上代码内容算是 ViewGroup 最后一部分代码
1、该部分代码会给 handled 局部变量赋值,这个值即为方法返回值。
2、改代码会遍历 mFirstTouchTarget 链表,如果链表里面的 View 没有分发事件,
则调用 dispatchTransformedTouchEvent 分发事件。
3、 mFirstTouchTarget 为null 时,会调用 dispatchTransformedTouchEvent
把事件分发给 ViewGroup 父类的 dispatchTouchEvent
4、dispatchTouchEvent 的返回值
1)ViewGroup 父类的 dispatchTouchEvent (如果没分发下去)
2)子 View 的 dispatchTouchEvent (如果分发下去)
3) 如果所有的子类 dispatchTouchEvent 都为 false,
调用父类 dispatchTouchEvent
以上代码可以解释第一部分的备注:
当 ACTION_DOWN 被取消以后,ACTION_MOVE 、ACTION_UP 不再往下传递
这样也解释了『第一部分』代码中 mFirstTouchTarget != null 的作用
总结一个简单的流程图
事件到达 View
View 的 dispatchTouchEvent 核心很短,如下
if (!result && onTouchEvent(event)) {
result = true;
}
根据以上代码可以推断出
1、调用 super.dispatchTouchEvent 会调用 onTouchEvent
2、如果 onTouchEvent 消耗了事件,返回 true。事件结束
否则返回 false
3、dispatchTouchEvent 返回 true 表示消耗事件,事件结束。
4、如果 dispatchTouchEvent 返回 false 事件交给 ViewGroup 处理
参考资料
ACTIVITY是如何接收到TOUCH事件的(窗口与用户输入系统)
Android事件分发机制详解:史上最全面、最易懂
图解 Android 事件分发机制
Android 触摸事件机制(四) ViewGroup中触摸事件详解