Android事件分发机制二:viewGroup与view对事件的处理 (6)

viewGroup收到的事件类型和子view收到的事件类型并不是完全一致的,在分发给子view的时候,viewGroup需要对事件类型进行修改,一般有以下情况需要修改:

viewGroup收到一个ACTION_POINTER_DOWN事件分发给一个子view,但是该子view前面没有收到其他的down事件,所以对于该view来说这是一个崭新的事件序列,所以需要把这个ACTION_POINTER_DOWN事件类型改为ACTION_DOWN再发送给子view。

viewGroup收到一个ACTION_POINTER_DOWN或ACTION_POINTER_UP事件,假设这个事件类型对应触控点2,但是有一个子view他只对触控点1的事件序列感兴趣,那么在分离出触控点1的信息之后,还需要把事件类型改为ACTION_MOVE再分发给该子view。

注意,把原MotionEvent对象拆分为多个MotionEvent对象之后,触控点的索引也发生了改变,如果需要分发一个ACTION_POINTER_DOWN/UP事件给子view,那么需要注意更新触控点的索引值。

viewGroup中真正执行事件派发的关键方法是 dispatchTransformedTouchEvent ,该方法会完成关键的事件分发逻辑。源码分析如下:

ViewGroup.java api29 // 该方法接收原MotionEvent事件、是否进行取消、目标子view、以及目标子view感兴趣的触控id // 如果不是取消事件这个方法会把原MotionEvent中的触控点信息拆分出目标view感兴趣的触控点信息 // 如果是取消事件则不需要拆分直接发送取消事件即可 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; // 如果是取消事件,那么不需要做其他额外的操作,直接派发事件即可,然后直接返回 // 因为对于取消事件最重要的内容就是事件本身,无需对事件的内容进行设置 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; } // oldPointerIdBits表示现在所有的触控id // desirePointerIdBits来自于该view所在的touchTarget,表示该view感兴趣的触控点id // 因为desirePointerIdBits有可能全是1,所以需要和oldPointerIdBits进行位与 // 得到真正可接收的触控点信息 final int oldPointerIdBits = event.getPointerIdBits(); final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits; // 控件处于不一致的状态。正在接受事件序列却没有一个触控点id符合 if (newPointerIdBits == 0) { return false; } // 来自原始MotionEvent的新的MotionEvent,只包含目标感兴趣的触控点 // 最终派发的是这个MotionEvent final MotionEvent transformedEvent; // 两者相等,表示该view接受所有的触控点的事件 // 这个时候transformedEvent相当于原始MotionEvent的复制 if (newPointerIdBits == oldPointerIdBits) { // 当目标控件不存在通过setScaleX()等方法进行的变换时, // 为了效率会将原始事件简单地进行控件位置与滚动量变换之后 // 发送给目标的dispatchTouchEvent()方法并返回。 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; } // 复制原始MotionEvent 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); // 存在scale等变换,需要进行矩阵转换 if (! child.hasIdentityMatrix()) { transformedEvent.transform(child.getInverseMatrix()); } // 调用子view的方法进行分发 handled = child.dispatchTouchEvent(transformedEvent); } // 分发完毕,回收MotionEvent transformedEvent.recycle(); return handled; }

好了,了解完上面的内容,来看看viewGroup的 dispatchTouchEvent 中派发事件的代码部分:

ViewGroup.java api29 public boolean dispatchTouchEvent(MotionEvent ev) { ... // 对遮盖状态进行过滤 if (onFilterTouchEventForSecurity(ev)) { ... if (mFirstTouchTarget == null) { // 经过了前面的处理,到这里touchTarget依旧为null,说明没有找到处理down事件的子控件 // 或者down事件被viewGroup本身消费了,所以该事件由viewGroup自己处理 // 这里调用了dispatchTransformedTouchEvent方法来分发事件 handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { // 已经有子view消费了down事件 TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; // 遍历所有的TouchTarget并把事件分发下去 while (target != null) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { // 表示事件在前面已经处理了,不需要重复处理 handled = true; } else { // 正常分发事件或者分发取消事件 final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; // 这里调用了dispatchTransformedTouchEvent方法来分发事件 if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } // 如果发送了取消事件,则移除该target if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } } // 如果接收到取消获取up事件,说明事件序列结束 // 直接删除所有的TouchTarget if (canceled || actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { // 清除记录的信息 resetTouchState(); } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) { final int actionIndex = ev.getActionIndex(); final int idBitsToRemove = 1 << ev.getPointerId(actionIndex); // 如果仅仅只是一个PONITER_UP // 清除对应触控点的触摸信息 removePointersFromTouchTargets(idBitsToRemove); } }// 这里对应if (onFilterTouchEventForSecurity(ev)) if (!handled && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1); } return handled; } 小结

到这里,viewGroup的事件分发源码就解析完成了,这里再来小结一下:

每一个触控点的事件序列,只能给一个view消费;如果一个view消费了一个触控点的down事件,那么该触控点的后续事件都会给他处理。

每一个事件到达viewGroup,如果需要分发到子view,那么viewGroup会新判断是否要拦截。

当viewGroup的touchTarget!=null || 事件的类型为down 需要进行判断是否拦截;

判断是否拦截受两个因素影响:onInterceptTouchEvent和FLAG_DISALLOW_INTERCEPT标志

如果该事件是down类型,那么需要遍历所有的子控件判断是否有子控件消费该down事件

当有新的down事件被消费时,viewGroup会把该view和对应的触控点id绑定起来存储到touchTarget中

根据前面的处理情况,将事件派发到viewGroup自身或touchTarget中

如果touchTarget==null,说明没有子控件消费了down事件,那么viewGroup自己处理事件

否则将事件分离成多个MotionEvent,每个MotionEvent只包含对应view感兴趣的触控点的信息,并派发给对应的子view

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpzxwf.html