背景
之前面试的时候遇到一个有关滑动事件的问题,让我十分费解,后来查阅相关资料和源码才得以解决,问的是在一个RecyclerView中,有一个item为Button,如果在按下这个Button的同时进行滑动,Button是否能够保持焦点?相关事件是如何传递的?
思考
按照以往我对事件传递机制的理解,在按下的时候是一个Down事件ViewGroup->Button,然后滑动的时候就是一系列Move事件,但是由于向上的Move事件会被ViewGroup(RecyclerView)拦截,所以Button就会失去焦点,实验结果也是如此,但是如果ViewGroup真的拦截了Move之后的所有事件,Button是如何得知自己失去焦点并更新UI为失去焦点的UI(至少得获得一个CANCEL或者UP事件吧?)。实验证明却是在Move被拦截后接收到了CANCEL事件,但是这个CANCEL是如何来的呢?
源码解析
在ViewGroup的dispatchTouchEvent方法的最后一部分有这样的代码
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;
}
大致流程是先判断事件是否已经传递给child处理,如果是就返回,如果没有那么就会尝试通过dispatchTransformedTouchEvent给child传递一个CANCEL事件,部分代码如下
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
...
}
可以看出就是这里给child传递了CANCEL事件