View·dispatchTouchEvent|View·dispatchTouchEvent 源码分析(四)

上节概述 从上节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对象 (DecorViewPhoneWindow的内部类),而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。所以关注下ViewGroupdispatchTouchEvent()方法。因为 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这个位置
View·dispatchTouchEvent|View·dispatchTouchEvent 源码分析(四)
文章图片
ACTION_DOWN事件的派发 10、确定了3、2、1这条链之后,在第8点中就能直接对这条链进行事件派发,派发顺序是3、2、1
View·dispatchTouchEvent|View·dispatchTouchEvent 源码分析(四)
文章图片
ACTION_DOWN 后续事件的派发 【View·dispatchTouchEvent|View·dispatchTouchEvent 源码分析(四)】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()时会影响哪些方法,并且这些影响的方法如何在实际工作中产生效用。

    推荐阅读