首页 > 代码库 > Android事件分发详解(三)——ViewGroup的dispatchTouchEvent()源码学习
Android事件分发详解(三)——ViewGroup的dispatchTouchEvent()源码学习
package cc.aa; import android.os.Environment; import android.view.MotionEvent; import android.view.View; public class UnderstandDispatchTouchEvent { /** * dispatchTouchEvent()源码学习及其注释 * 常说事件传递中的流程是:dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent * 在这个链条中dispatchTouchEvent()是处在链首的位置当然也是最重要的. * 在dispatchTouchEvent()决定了Touch事件是由自己的onTouchEvent()处理 * 还是分发给子View处理让子View调用其自身的dispatchTouchEvent()处理. * * * 其实dispatchTouchEvent()和onInterceptTouchEvent()以及onTouchEvent()的关系 * 在dispatchTouchEvent()方法的源码中体现得很明显. * 比如dispatchTouchEvent()会调用onInterceptTouchEvent()来判断是否要拦截. * 比如dispatchTouchEvent()会调用dispatchTransformedTouchEvent()方法且在该方法中递归调用 * dispatchTouchEvent();从而会在dispatchTouchEvent()里最终调用到onTouchEvent() * * * * 重点关注: * 1 子View对于ACTION_DOWN的处理十分重要!!!!! * ACTION_DOWN是一系列Touch事件的开端,如果子View对于该ACTION_DOWN事件在onTouchEvent()中返回了false即未消费. * 那么ViewGroup就不会把后续的ACTION_MOVE和ACTION_UP派发给该子View.在这种情况下ViewGroup就和普通的View一样了, * 调用该ViewGroup自己的dispatchTouchEvent()从而调用自己的onTouchEvent();即不会将事件分发给子View. * 详细代码请参见如下代码分析. * * 2 为什么子view对于Touch事件处理返回true那么其上层的ViewGroup就无法处理Touch事件了????? * 这个想必大家都知道了,因为该Touch事件被子View消费了其上层的ViewGroup就无法处理该Touch事件了. * 那么在源码中的依据是什么呢??请看下面的源码分析 * * 参考资料: * 0 http://blog.csdn.net/sahadev_/article/details/23839039 * 1 http://www.cnblogs.com/xiaoweiz/p/3838682.html * 2 http://hukai.me/android-deeper-touch-event-dispatch-process/ * 3 http://blog.csdn.net/hdxiaoyu2/article/details/25563453 * 4 http://www.eoeandroid.com/thread-542296-1-1.html * 5 http://www.eoeandroid.com/forum.php?mod=viewthread&tid=319301 * 6 http://stackvoid.com/details-dispatch-onTouch-Event-in-Android/ * Thank you very much */ @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(ev, 1); } boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; /** * 第一步:对于ACTION_DOWN进行处理(Handle an initial down) * 因为ACTION_DOWN是一系列事件的开端,当是ACTION_DOWN时进行一些初始化操作. * 从源码的注释也可以看出来:清除以往的Touch状态(state)开始新的手势(gesture) * cancelAndClearTouchTargets(ev)中有一个非常重要的操作: * 将mFirstTouchTarget设置为null!!!! * 随后在resetTouchState()中重置Touch状态标识 */ if (actionMasked == MotionEvent.ACTION_DOWN) { // Throw away all previous state when starting a new touch gesture. // The framework may have dropped the up or cancel event for the previous gesture // due to an app switch, ANR, or some other state change. cancelAndClearTouchTargets(ev); resetTouchState(); } /** * 第二步:检查是否要拦截(Check for interception) * 在dispatchTouchEvent(MotionEventev)这段代码中 * 使用变量intercepted来标记ViewGroup是否拦截Touch事件的传递. * 该变量在后续代码中起着很重要的作用. */ final boolean intercepted; // 事件为ACTION_DOWN或者mFirstTouchTarget不为null(即已经找到能够接收touch事件的目标组件)时if成立 if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { //判断disallowIntercept(禁止拦截)标志位 //因为在其他地方可能调用了requestDisallowInterceptTouchEvent(boolean disallowIntercept) //从而禁止执行是否需要拦截的判断(有点拗口~其实看requestDisallowInterceptTouchEvent()方法名就可明白) final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; //当没有禁止拦截判断时(即disallowIntercept为false)调用onInterceptTouchEvent(ev)方法 if (!disallowIntercept) { //既然disallowIntercept为false那么就调用onInterceptTouchEvent()方法将结果赋值给intercepted //常说事件传递中的流程是:dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent //其实在这就是一个体现,在dispatchTouchEvent()中调用了onInterceptTouchEvent() intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { //当禁止拦截判断时(即disallowIntercept为true)设置intercepted = false intercepted = false; } } else { //当事件不是ACTION_DOWN并且mFirstTouchTarget为null(即没有Touch的目标组件)时 //设置 intercepted = true表示ViewGroup执行Touch事件拦截的操作。 //There are no touch targets and this action is not an initial down //so this view group continues to intercept touches. intercepted = true; } /** * 第三步:检查cancel(Check for cancelation) * */ final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; /** * 第四步:事件分发(Update list of touch targets for pointer down, if needed) */ final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; //不是ACTION_CANCEL并且ViewGroup的拦截标志位intercepted为false(不拦截) if (!canceled && !intercepted) { //处理ACTION_DOWN事件.这个环节比较繁琐. if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { final int actionIndex = ev.getActionIndex(); // always 0 for down final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex):TouchTarget.ALL_POINTER_IDS; // Clean up earlier touch targets for this pointer id in case they // have become out of sync. removePointersFromTouchTargets(idBitsToAssign); final int childrenCount = mChildrenCount; if (childrenCount != 0) { // 依据Touch坐标寻找子View来接收Touch事件 // Find a child that can receive the event. // Scan children from front to back. final View[] children = mChildren; final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); final boolean customOrder = isChildrenDrawingOrderEnabled(); // 遍历子View判断哪个子View接受Touch事件 for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; final View child = children[childIndex]; if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { continue; } newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { // 找到接收Touch事件的子View!!!!!!!即为newTouchTarget. // 既然已经找到了,所以执行break跳出for循环 // Child is already receiving touch within its bounds. // Give it the new pointer in addition to the ones it is handling. newTouchTarget.pointerIdBits |= idBitsToAssign; break; } resetCancelNextUpFlag(child); /** * 如果上面的if不满足,当然也不会执行break语句. * 于是代码会执行到这里来. * * 调用方法dispatchTransformedTouchEvent()将Touch事件传递给子View做 * 递归处理(也就是遍历该子View的View树) * 该方法很重要,看一下源码中关于该方法的描述: * Transforms a motion event into the coordinate space of a particular child view, * filters out irrelevant pointer ids, and overrides its action if necessary. * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead. * 将Touch事件传递给特定的子View. * 该方法十分重要!!!!在该方法中为一个递归调用,会递归调用dispatchTouchEvent()方法!!!!!!!!!!!!!! * 在dispatchTouchEvent()中: * 如果子View为ViewGroup并且Touch没有被拦截那么递归调用dispatchTouchEvent() * 如果子View为View那么就会调用其onTouchEvent(),这个就不再赘述了. * * * 该方法返回true则表示子View消费掉该事件,同时进入该if判断. * 满足if语句后重要的操作有: * 1 给newTouchTarget赋值 * 2 给alreadyDispatchedToNewTouchTarget赋值为true. * 看这个比较长的英语名字也可知其含义:已经将Touch派发给新的TouchTarget * 3 执行break. * 因为该for循环遍历子View判断哪个子View接受Touch事件,既然已经找到了 * 那么就跳出该for循环. * 4 注意: * 如果dispatchTransformedTouchEvent()返回false即子View * 的onTouchEvent返回false(即Touch事件未被消费)那么就不满足该if条件,也就无法执行addTouchTarget() * 从而导致mFirstTouchTarget为null.那么该子View就无法继续处理ACTION_MOVE事件 * 和ACTION_UP事件!!!!!!!!!!!!!!!!!!!!!! * 5 注意: * 如果dispatchTransformedTouchEvent()返回true即子View * 的onTouchEvent返回true(即Touch事件被消费)那么就满足该if条件. * 从而mFirstTouchTarget不为null!!!!!!!!!!!!!!!!!!! * 6 小结: * 对于此处ACTION_DOWN的处理具体体现在dispatchTransformedTouchEvent() * 该方法返回boolean,如下: * true---->事件被消费----->mFirstTouchTarget!=null * false--->事件未被消费---->mFirstTouchTarget==null * 因为在dispatchTransformedTouchEvent()会调用递归调用dispatchTouchEvent()和onTouchEvent() * 所以dispatchTransformedTouchEvent()的返回值实际上是由onTouchEvent()决定的. * 简单地说onTouchEvent()是否消费了Touch事件(true or false)的返回值决定了dispatchTransformedTouchEvent() * 的返回值!!!!!!!!!!!!!从而决定了mFirstTouchTarget是否为null!!!!!!!!!!!!!!!!从而进一步决定了ViewGroup是否 * 处理Touch事件.这一点在下面的代码中很有体现. * * */ if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // Child wants to receive touch within its bounds. mLastTouchDownTime = ev.getDownTime(); mLastTouchDownIndex = childIndex; mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } } } /** * 该if条件表示: * 经过前面的for循环没有找到子View接收Touch事件并且之前的mFirstTouchTarget不为空 */ if (newTouchTarget == null && mFirstTouchTarget != null) { // Did not find a child to receive the event. // Assign the pointer to the least recently added target. newTouchTarget = mFirstTouchTarget; while (newTouchTarget.next != null) { newTouchTarget = newTouchTarget.next; } //newTouchTarget指向了最初的TouchTarget newTouchTarget.pointerIdBits |= idBitsToAssign; } } } /** * 分发Touch事件至target(Dispatch to touch targets) * * 经过上面对于ACTION_DOWN的处理后mFirstTouchTarget有两种情况: * 1 mFirstTouchTarget为null * 2 mFirstTouchTarget不为null * * 当然如果不是ACTION_DOWN就不会经过上面较繁琐的流程 * 而是从此处开始执行,比如ACTION_MOVE和ACTION_UP */ if (mFirstTouchTarget == null) { /** * 情况1:mFirstTouchTarget为null * * 经过上面的分析mFirstTouchTarget为null就是说Touch事件未被消费. * 即没有找到能够消费touch事件的子组件或Touch事件被拦截了, * 则调用ViewGroup的dispatchTransformedTouchEvent()方法处理Touch事件则和普通View一样. * 即子View没有消费Touch事件,那么子View的上层ViewGroup才会调用其onTouchEvent()处理Touch事件. * 在源码中的注释为:No touch targets so treat this as an ordinary view. * 也就是说此时ViewGroup像一个普通的View那样调用dispatchTouchEvent(),且在dispatchTouchEvent() * 中会去调用onTouchEvent()方法. * 具体的说就是在调用dispatchTransformedTouchEvent()时第三个参数为null. * 第三个参数View child为null会做什么样的处理呢? * 请参见下面dispatchTransformedTouchEvent()的源码分析 * * 这就是为什么子view对于Touch事件处理返回true那么其上层的ViewGroup就无法处理Touch事件了!!!!!!!!!! * 这就是为什么子view对于Touch事件处理返回false那么其上层的ViewGroup才可以处理Touch事件!!!!!!!!!! * */ handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS); } else { /** * 情况2:mFirstTouchTarget不为null即找到了可以消费Touch事件的子View且后续Touch事件可以传递到该子View * 在源码中的注释为: * Dispatch to touch targets, excluding the new touch target if we already dispatched to it. * Cancel touch targets if necessary. */ 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; //对于非ACTION_DOWN事件继续传递给目标子组件进行处理,依然是递归调用dispatchTransformedTouchEvent() 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; } } /** * 处理ACTION_UP和ACTION_CANCEL * Update list of touch targets for pointer up or cancel, if needed. * 在此主要的操作是还原状态 */ 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); removePointersFromTouchTargets(idBitsToRemove); } } if (!handled && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1); } return handled; } //=====================以上为dispatchTouchEvent()源码分析====================== //===============以下为dispatchTransformedTouchEvent()源码分析================= /** * 在dispatchTouchEvent()中调用dispatchTransformedTouchEvent()将事件分发给子View处理 * * Transforms a motion event into the coordinate space of a particular child view, * filters out irrelevant pointer ids, and overrides its action if necessary. * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead. * * 在此请着重注意第三个参数:View child * 在dispatchTouchEvent()中多次调用了dispatchTransformedTouchEvent(),但是有时候第三个参数为null,有时又不是. * 那么这个参数是否为null有什么区别呢? * 在如下dispatchTransformedTouchEvent()源码中可见多次对于child是否为null的判断,并且均做出如下类似的操作: * if (child == null) { * handled = super.dispatchTouchEvent(event); * } else { * handled = child.dispatchTouchEvent(event); * } * 这个代码是什么意思呢?? * 当child == null时会将Touch事件传递给该ViewGroup自身的dispatchTouchEvent()处理. * 即super.dispatchTouchEvent(event)正如源码中的注释描述的一样: * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead. * 当child != null时会调用该子view(当然该view可能是一个View也可能是一个ViewGroup)的dispatchTouchEvent(event)处理. * 即child.dispatchTouchEvent(event); * * */ 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; } // Calculate the number of pointers to deliver. final int oldPointerIdBits = event.getPointerIdBits(); final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits; // If for some reason we ended up in an inconsistent state where it looks like we // might produce a motion event with no pointers in it, then drop the event. if (newPointerIdBits == 0) { return false; } // If the number of pointers is the same and we don't need to perform any fancy // irreversible transformations, then we can reuse the motion event for this // dispatch as long as we are careful to revert any changes we make. // Otherwise we need to make a copy. 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); } // Perform any necessary transformations and dispatch. 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; } }
Android事件分发详解(三)——ViewGroup的dispatchTouchEvent()源码学习
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。