前言
Android的事件分发机制也是老生常谈了,这篇文章并不是笼统的介绍这个机制,而是针对ACTION_DOWN这个事件探讨相关的细节。
dispatchTouchEvent
说到Android事件分发,一定绕不开dispatchTouchEvent 函数,View和ViewGroup的该函数有很大的不同。
我们来看看ViewGroup的dispatchTouchEvent 函数,它的部分源码如下:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...
if (onFilterTouchEventForSecurity(ev)) {
...
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex();
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
...
for (int i = childrenCount - 1; i >= 0; i--) {
...
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
...
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
...
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
...
}
}
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;
}
...
}
predecessor = target;
target = next;
}
}
...
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
可以看到整个分发有几个关键因素:intercepted 、canceled 、mFirstTouchTarget 、alreadyDispatchedToNewTouchTarget 。
intercepted、canceled比较好理解,重点来说说后面两个因素的是如何影响整个分发的。
ACTION_DOWN
一个完整的事件应该包含ACTION_DOWN、ACTION_MOVE、ACTION_UP。其中ACTION_DOWN是开始也是关键。
从上面dispatchTouchEvent 源码中可以看到首先单独对ACTION_DOWN事件进行了处理,对所有child进行遍历,是从后向前遍历的,所以在处理上面的也就是最后添加的view会先得到事件
for (int i = childrenCount - 1; i >= 0; i--) {
对于每个child,会先判断事件是不是发生在它的区域内,不是则不处理:
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
如果在区域内,则继续执行,下面dispatchTransformedTouchEvent 这个函数就是下发事件的,我们来看下部分源码:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
...
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
transformedEvent.recycle();
return handled;
}
有不少逻辑在里面,但是仔细观察可以发现,不论那个条件,执行的代码都比较类似,如下:
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
...
handled = child.dispatchTouchEvent(event);
...
}
当child不为null的时候,执行child的dispatchTouchEvent ;为null时执行父类的dispatchTouchEvent ,即View的dispatchTouchEvent 函数,这个函数里会执行onTouchEvent 等。所以在ViewGroup是没有onTouchEvent等函数的代码。
由于这时child不为null,所以执行了child的dispatchTouchEvent 函数.
回到之前的ACTION_DOWN流程中,根据dispatchTransformedTouchEvent 返回值进行不同的处理:
返回ture
如果返回true,即有一个child消费了ACTION_DOWN事件,可以看到后续执行了addTouchTarget 函数,同时将alreadyDispatchedToNewTouchTarget 置为true。
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
...
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
addTouchTarget 函数源码如下:
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
关键的一点是对mFirstTouchTarget 进行了赋值。所以说true的处理是为mFirstTouchTarget 赋值,将alreadyDispatchedToNewTouchTarget 置为true 最后的break则跳出循环,不再遍历其他child。
返回false
如果返回false,即没有任何一个child消费ACTION_DOWN事件,直接跳过if代码,这样mFirstTouchTarget 为null。
mFirstTouchTarget
那么mFirstTouchTarget、alreadyDispatchedToNewTouchTarget这两个属性在分发过程中的作用是什么?我们分别来说:
1、mFirstTouchTarget为null
当mFirstTouchTarget 为null,进入if语句执行dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS)
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}
由于child是null,在dispatchTransformedTouchEvent 代码中可以看到不再给任何child分发,而是调用了super.dispatchTouchEvent ,即ViewGroup自己处理
这样ACTION_DOWN事件分发完了。其他事件分发时由于不再走ACTION_DOWN的处理过程,所以mFirstTouchTarget 会一直为null,所以其他事件也不再向下分发了,直接ViewGroup自己处理
2、mFirstTouchTarget不为null
当mFirstTouchTarget 不为null,进入else语句中,会执行一个while循环
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;
}
...
}
predecessor = target;
target = next;
}
}
这时由于alreadyDispatchedToNewTouchTarget 为true,所以直接给handled 赋值true并不做任何处理。因为之前代码中child对ACTION_DOWN事件已经响应,所以这里的alreadyDispatchedToNewTouchTarget 是为了防止重复分发ACTION_DOWN事件。
这样ACTION_DOWN事件分发完成后,分发其他事件时,alreadyDispatchedToNewTouchTarget 被重新赋值false,由于不再走ACTION_DOWN的处理过程,所以alreadyDispatchedToNewTouchTarget 就一直是false了,而mFirstTouchTarget 会一直保持不变。在这个while循环中则会执行else语句,通过执行dispatchTransformedTouchEvent 将事件直接分发给mFirstTouchTarget 对应的child,即之前消费ACTION_DOWN事件的child。
总结
这样我们得到几个结论:
|