上节概述
从上节View·dispatchTouchEvent 源码分析(三)中,我们分析了 ACTION_DOWN 事件的派发和拦截过程。
接下去,我们分析后续的事件是怎么被处理的!
正文
1、 InputEvent
事件传入 ViewRootImpl
中的 ViewPostImeInputStage
方法中。
2、ViewPostImeInputStage
调用 processPointerEvent
方法处理 InputEvent
事件。
3、 在processPointerEvent
内部调用是 mView.dispatchPointerEvent(event);
,将事件进行处理。
// ViewPostImeInputStage.class
private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
mAttachInfo.mUnbufferedDispatchRequested = false;
boolean handled = mView.dispatchPointerEvent(event);
if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
mUnbufferedInputDispatch = true;
if (mConsumeBatchedInputScheduled) {
scheduleConsumeBatchedInputImmediately();
}
}
return handled ? FINISH_HANDLED : FORWARD;
}
4、mView
代表DecorView
对象 (DecorView
是PhoneWindow
的内部类),而DecorView
并未覆写dispatchPointerEvent
方法。所以追述到View
中。
// View.java
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}
5、此处我们只分析是触摸事件的情况,所以程序会走分支return dispatchTouchEvent(event);
。此处会涉及到dispatchTouchEvent()
方法的继承关系,所以有必要弄清楚mView
的继承链。
// PhoneWindow#DecorView.class
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
// ... 省略...
}
6、首先关注下DecorView、FrameLayout
是否覆写dispatchTouchEvent()
方法,检查后发现并未覆写。所以dispatchTouchEvent()
方法的分析,被限定在View、ViewGroup
的范围内。
7、因为当前的实例是DecorView
,所以类型是ViewGroup
。所以关注下ViewGroup
的dispatchTouchEvent()
方法。因为 ACTION_DOWN
事件已经在前一章被处理过了,所以我们跳过对ACTION_DOWN
事件处理的代码片段。
// ViewGroup.class
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
// 对 ACTION_DOWN 事件的处理
// 构造 TouchTarget 链
// 都忽略
}
// 分发 TOUCH 事件
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// 训练 TouchTarget 的链,执行事件派发
// 【代码贴在第8小节里面】
}
}
8、之前DecorView
的实例在处理ACTION_DOWN
事件时,mFirstTouchTarget
已经被赋值了。所以mFirstTouchTarget
代表当前获取焦点的视图(或被代码拦截的视图),作为事件的处理源头开始执行回朔操作。
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;
}
// 当视图被 detach 的时候,即回收 TouchTarget
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
9、首次调用dispatchTouchEvent
处理 ACTION_DOWN
事件,获得的TouchTarget
链的过程。经过dispatchTouchEvent
的处理,得到了如下图的TouchTarget
链—— 3、2、1,同时使用mFirstTouchTarget 指向3这个位置
。
10、确定了3、2、1
这条链之后,在第8点中就能直接对这条链进行事件派发,派发顺序是3、2、1
。
11、最后的事件派发的一根稻草,被定位在ViewGroup#dispatchTransformedTouchEvent()
方法上。
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// 检测是否需要发送ACTION_CANCEL。
// 如果cancel为true 或者 action是ACTION_CANCEL;
// 则设置消息为ACTION_CANCEL,并将ACTION_CANCEL消息分发给对应的对象,并返回。
// (01) 如果child是空,则将ACTION_CANCEL消息分发给当前ViewGroup;
// 只不过会将ViewGroup看作它的父类View,调用View的dispatchTouchEvent()接口。
// (02) 如果child不是空,调用child的dispatchTouchEvent()。
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;
}
// 计算新旧的手指数目
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
// 如果产生的新手指数目与原先的数目不一致,则不消费此次事件。
if (newPointerIdBits == 0) {
return false;
}
// 如果手指的数量是相同的,我们不需要执行任何花哨的不可逆转换,
// 如果我们想重用分派的事件,需要谨慎地还原任何我们做的更改。
// 否则,我们需要做一个副本。
final MotionEvent transformedEvent;
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);
}
// 如果手指数目不一致,则意味着我们需要先转化 MotionEvent 事件。
// 然后在对转换后的对象进行事件派发
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);
}
// Done.
transformedEvent.recycle();
return handled;
}
12、dispatchTransformedTouchEvent()
会对触摸事件进行重新打包后再分发。如果它的第三个参数child是null,则会将触摸消息分发给ViewGroup
自己,只不过此时是将ViewGroup
看作一个View
,即调用View的dispatchTouchEvent()
进行消息分发。
小结
贴了两张(巨丑)手绘的图,大致描述了事件“分发与回朔”的过程。这一章没有涉及到事件分发的应用技巧,而是对前面两章的小结。借这几篇文章,将事件产生、分发、回朔的流程都吃透了。那么接下去梳理具体的事件分发的应用时,就会轻松不少。
下一章将会分析View
在调用dispatchTouchEvent()
时会影响哪些方法,并且这些影响的方法如何在实际工作中产生效用。