ViewGroup对触摸事件的分发响应过程
来源:互联网 发布:网络直销模式典型公司 编辑:程序博客网 时间:2024/06/16 19:52
以前对于触摸事件的分发过程有过专门的研究,当时把郭林大神讲ViewGroup触摸分发的博客(http://blog.csdn.net/guolin_blog/article/details/9153747)看了不下3边,当时感觉对于分发已经了然于心。但是,后来在项目实战中发现好多一想不到的问题,郭大神的博客完全没法解释,凸(艹皿艹 ),然后自己打开ide,看ViewGroup源码发现跟郭大神博客里面的源码完全不一样,原来郭大神的博客说得是2.x版本ViewGroup关于触摸的分发的源码,无奈,相当于以前的研究都白费了。正好马上就进行技术分享会,我直接报,我要说触摸分发的过程。也借此机会,可以好好研究一下触摸的分发,并且解开我的疑惑。
触摸分发和响应的过程都是在dispatchTouchEvent方法中完成的,所以,此文主要分析ViewGroup中dispatchTouchEvent方法的实现。
想看懂ViewGroup中dispatchTouchEvent方法的代码,必须要先大致了解ViewGroup的一个内部类,TouchTarget类
/* Describes a touched view and the ids of the pointers that it has captured. * * This code assumes that pointer ids are always in the range 0..31 such that * it can use a bitfield to track which pointer ids are present. * As it happens, the lower layers of the input dispatch pipeline also use the * same trick so the assumption should be safe here... */ private static final class TouchTarget { private static final int MAX_RECYCLED = 32; private static final Object sRecycleLock = new Object[0]; private static TouchTarget sRecycleBin; // 回收再利用的链表头 private static int sRecycledCount; public static final int ALL_POINTER_IDS = -1; // all ones // The touched child view. public View child; // The combined bit mask of pointer ids for all pointers captured by the target. public int pointerIdBits; // The next target in the target list. public TouchTarget next; private TouchTarget() { } // 看到这个有没有很眼熟?是的Message里也有类似的实现,我们在之前介绍Message的文章里详细地分析过 public static TouchTarget obtain(View child, int pointerIdBits) { final TouchTarget target; synchronized (sRecycleLock) { if (sRecycleBin == null) { // 没有可以回收的目标,则new一个返回 target = new TouchTarget(); } else { target = sRecycleBin; // 重用当前的sRecycleBin sRecycleBin = target.next; // 更新sRecycleBin指向下一个 sRecycledCount--; // 重用了一个,可回收的减1 target.next = null; // 切断next指向 } } target.child = child; // 找到合适的target后,赋值 target.pointerIdBits = pointerIdBits; return target; } public void recycle() { // 基本是obtain的反向过程 synchronized (sRecycleLock) { if (sRecycledCount < MAX_RECYCLED) { next = sRecycleBin; // next指向旧的可回收的头 sRecycleBin = this; // update旧的头指向this,表示它自己现在是可回收的target(第一个) sRecycledCount += 1; // 多了一个可回收的 } else { next = null; // 没有next了 } child = null; // 清空child字段 } } }源码分析:
TouchTarget类用来记录触摸对象,包括此次触摸到的view(child成员变量)和触摸的id(pointerIdBits成员变量)。TouchTarget自身维护着一个TouchTarget对象形成的链表,obtain方法会从这个链表头上取出一个TouchTarget来使用,如果此时链表没有TouchTarget可用,则新造一个给调用端;recycle方法会将不用的TouchTarget对象重置后添加到链表的头部,供以后使用。当多点触摸的时候,会有多个TouchTarget,这些TouchTarget都是通过obtian方法获取的,获取的TouchTarget对象会形成一个触摸链,这个触摸链的起点是mFirstTouchTarget。本文重点说明触摸的分发响应过程,简单起见,以单点触摸的情况来说明,单点触摸中只会有一个TouchTarget对象。
下面我们来看ViewGroup中dispatchTouchEvent的实现:
@Override public boolean dispatchTouchEvent(MotionEvent ev) { if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(ev, 1); } boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { // view没有被遮罩,一般都成立 final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; // Handle an initial down. if (actionMasked == MotionEvent.ACTION_DOWN) { // 一堆touch事件(从按下到松手)中的第一个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(); // 作为新一轮的开始,reset所有相关的状态 } // Check for interception. final boolean intercepted; // 检查是否要拦截 if (actionMasked == MotionEvent.ACTION_DOWN // down事件 || mFirstTouchTarget != null) { // 或者之前的某次事件已经经由此ViewGroup派发给children后被处理掉了 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { // 只有允许拦截才执行onInterceptTouchEvent方法 intercepted = onInterceptTouchEvent(ev); // 默认返回false,不拦截 ev.setAction(action); // restore action in case it was changed } else { intercepted = false; // 不允许拦截的话,直接设为false } } else { // There are no touch targets and this action is not an initial down // so this view group continues to intercept touches. // 在这种情况下,actionMasked != ACTION_DOWN && mFirstTouchTarget == null // 第一次的down事件没有被此ViewGroup的children处理掉(要么是它们自己不处理,要么是ViewGroup从一 // 开始的down事件就开始拦截),则接下来的所有事件 // 也没它们的份,即不处理down事件的话,那表示你对后面接下来的事件也不感兴趣 intercepted = true; // 这种情况下设置ViewGroup拦截接下来的事件 } // Check for cancelation. final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; // 此touch事件是否取消了 // Update list of touch targets for pointer down, if needed. // 是否拆分事件,3.0(包括)之后引入的,默认拆分 final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; TouchTarget newTouchTarget = null; // 接下来ViewGroup判断要将此touch事件交给谁处理 boolean alreadyDispatchedToNewTouchTarget = false; if (!canceled && !intercepted) { // 没取消也不拦截,即是个有效的touch事件 if (actionMasked == MotionEvent.ACTION_DOWN // 第一个手指down || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) // 接下来的手指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 (newTouchTarget == null && childrenCount != 0) { // 基本都成立 final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); // Find a child that can receive the event. // Scan children from front to back. final View[] children = mChildren; final boolean customOrder = isChildrenDrawingOrderEnabled(); // 从最后一个向第一个找 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; // 不满足这2个条件直接跳过,看下一个child } // child view能receive touch事件而且touch坐标也在view边界内 newTouchTarget = getTouchTarget(child);// 查找child对应的TouchTarget if (newTouchTarget != null) { // 比如在同一个child上按下了多跟手指 // 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; // newTouchTarget已经有了,跳出for循环 } resetCancelNextUpFlag(child); // 将此事件交给child处理 // 有这种情况,一个手指按在了child1上,另一个手指按在了child2上,以此类推 // 这样TouchTarget的链就形成了 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(); // 如果处理掉了的话,将此child添加到touch链的头部 // 注意这个方法内部会更新 mFirstTouchTarget newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; // down或pointer_down事件已经被处理了 break; // 可以退出for循环了。。。 } } } // 本次没找到newTouchTarget但之前的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; } // while结束后,newTouchTarget指向了最初的TouchTarget newTouchTarget.pointerIdBits |= idBitsToAssign; } } } // 非down事件直接从这里开始处理,不会走上面的一大堆寻找TouchTarget的逻辑 // Dispatch to touch targets. if (mFirstTouchTarget == null) { // 没有children处理则派发给自己处理 // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { // 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) { // 遍历TouchTarget形成的链表 final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; // 已经处理过的不再让其处理事件 } else { // 取消child标记 final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; // 如果ViewGroup从半路拦截了touch事件则给touch链上的child发送cancel事件 // 如果cancelChild为true的话 if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; // TouchTarget链中任意一个处理了则设置handled为true } if (cancelChild) { // 如果是cancelChild的话,则回收此target节点 if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; // 相当于从链表中删除一个节点 } target.recycle(); // 回收它 target = next; continue; } } predecessor = target; // 访问下一个节点 target = next; } } // Update list of touch targets for pointer up or cancel, if needed. if (canceled || actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { // 取消或up事件时resetTouchState 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; // 返回处理的结果 }
源码分析(以代码执行的顺序分析):
重要成员变量
// First touch target in the linked list of touch targets. private TouchTarget mFirstTouchTarget;//ViewGroup的child消耗touch,此变量记录下消耗touch的view protected int mGroupFlags;//各种flag值的集合,其中包含FLAG_DISALLOW_INTERCEPT
1.触摸以MotionEvent.ACTION_DOWN为开始,在触摸的开始,ViewGroup会清除所有的关于上次触摸的信息。
8行一般为true,13行判断如果是ACTION_DOWN,则调用cancelAndClearTouchTargets()和resetTouchState(),在这两个方法中分别mFirstTouchTarget=null和重置mGroupFlags的FLAG_DISALLOW_INTERCEPT标志位。
代码如下:
/** * Cancels and clears all touch targets. */ private void cancelAndClearTouchTargets(MotionEvent event) { if (mFirstTouchTarget != null) { boolean syntheticEvent = false; if (event == null) { final long now = SystemClock.uptimeMillis(); event = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); event.setSource(InputDevice.SOURCE_TOUCHSCREEN); syntheticEvent = true; } for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) { resetCancelNextUpFlag(target.child); dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits); } clearTouchTargets(); if (syntheticEvent) { event.recycle(); } } } /** * Resets the cancel next up flag. * Returns true if the flag was previously set. */ private static boolean resetCancelNextUpFlag(View view) { if ((view.mPrivateFlags & PFLAG_CANCEL_NEXT_UP_EVENT) != 0) { view.mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT; return true; } return false; } /** * Clears all touch targets. */ private void clearTouchTargets() { TouchTarget target = mFirstTouchTarget; if (target != null) { do { TouchTarget next = target.next; target.recycle(); target = next; } while (target != null); mFirstTouchTarget = null; } }
5行如果mFirsetTouchTarget!=null,会走到19行调用clearTouchTargets(),clearTouchTargets()中将mFirstTouchTarget为起点的链表都清空,最终将mFirstTouchTarget=null.
/** * Resets all touch state in preparation for a new cycle. */ private void resetTouchState() { clearTouchTargets(); resetCancelNextUpFlag(this); mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;//重置mGroupFlags中FLAG_DISALLOW_INTERCEPT标志位 mNestedScrollAxes = SCROLL_AXIS_NONE; }FLAG_DISALLOW_INTERCEPT标志位,决定了ViewGroup是否允许拦截touch事件,往往通过child.getParent().requestDisallowInterceptTouchEvent(true)方法来设置,当传入true的话,child的所有父控件都不会拦截touch事件,这样保证了child肯定会收到此touch事件。
requestDisallowInterceptTouchEvent代码如下:
/** * {@inheritDoc} */ public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) { // We're already in this state, assume our ancestors are too return; } if (disallowIntercept) { mGroupFlags |= FLAG_DISALLOW_INTERCEPT; } else { mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; } // Pass it up to our parent if (mParent != null) { mParent.requestDisallowInterceptTouchEvent(disallowIntercept); } }
tip:我们自己做项目时经常会重写child的onTouchEvent方法,判断当情况A时调用child.getParent().requestDisallowInterceptTouchEvent(true),来让父控件不去拦截touch事件,当情况B时,调用child.getParent().requestDisallowInterceptTouchEvent(false),运许父控件拦截touch事件,如果此时父控件也需要拦截touch,那么父控件会拦截touch,child再无法收到touch事件,child的onTouchEvent方法也就再得不到执行。但是,当用户松开手机再按下,产生下一个触摸事件时,child的onTouchEvent又得到执行,就是因为父控件ViewGroup在dispatchTouchEvent方法中判断MotionEvent的action为ACTION_DOWN,重置了mGroupFlags标志位所致。
2.ViewGroup通过MotionEvent的action,mFirstTouchTarget,mGroupFlags中FLAG_DISALLOW_INTERCEPT,三个值共同判断是否要拦截此touch。
(不拦截touch,ViewGroup会根据触摸在哪个view上,将touch逐层分发到那个view去,即逐层调用view的dispatchTouchEvent方法,如果拦截touch,则不会向下分发,直接调用ViewGroup的onTouchEvent方法,之后便结束了此ViewGroup的dispatchTouchEvent方法)
dispatchTouchEvent代码片段:
22到40行代码判断ViewGroup是否拦截touch事件
mFirstTouchTarget记录的是ViewGroup中消耗了touch事件的child的引用;
当一个新的触摸事件到来,由于之前重置了ViewGroup中记录的上一个touch的信息,此时MotionEvent的action为ACTION_DOWN并且mFirstTouchTarget==null,mGroupFlags中FLAG_DISALLOW_INTERCEPT为false,所以,进入27行代码,即此处触摸是否拦截又ViewGroup的onInterceptTouch方法决定。
当MotionEvent为非ACTION_DOWN时,如果mFirstTouchEvent!=null,即ViewGroup中被触摸到的子View消耗了此touch事件,此时是否拦截touch由mGroupFlags中FLAG_DISALLOW_INTERCEPT标志位值和onInterceptTouch方法返回值共同决定。
当MotionEvent为非ACTION_DOWN时,如果mFirstTouchEvent==null,即ViewGroup中被触摸到的子View没有消耗此touch事件,进入39行,ViewGroup拦截touch事件。
tips:当我们自定义的View重写onTouchEvent方法,在onTouchEvent方法的ACITON_DOWN中返回false,此View便再无法收到其后的touch事件,就是因为ViewGroup的子View没有消耗touch事件,mFirstTouchTarget==null,intercept=true,ViewGroup便会拦截其后的touch事件。
3.根据intercept的值,决定是否拦截touch事件,不拦截将touch传递到触摸到的View上去,当分发到的View消耗了此touch,mFirstTouchTarget会记下此View;分发到的View没有消耗此touch,mFirstTouchTarget一直为null。
51行if(!cancel&&!intercepted),默认cancel为false,当intercept为false,即不拦截时,代码会走到64行,childrenCount !=0,即ViewGroup有child,会走到73行,从后往前遍历ViewGroup的所有child,77,78行判断是否触摸在此child上,并且此child接受触摸事件。
tips:我们经常使用FrameLayout来做重叠的布局,当我们的触摸事件落在多个重叠view上时,这时候如果前边的view消耗了事件,后边的view就收不到触摸事件了;就是因为ViewGroup在分发touch的时候,是从后往前遍历的(view重叠时,就是从上向下),如果上面的view消耗了touch,后边的view自然就接收不到touch了。
84-92行主要是对多点触控进行处理,暂时不管,代码进入关键的96行,调用dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign),此方法将dispatchTouchEvent方法中传入的MotionEvent的相对于ViewGroup的x,y坐标转化为相对于child的x,y坐标,然后,分发给此child。
dispatchTransformedTouchEvent代码:
/** * 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. */ 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; }
13行,如果传入的cancel==true或者event.getAction()==MotionEvent.ACTION_CANCEL,把event的action设置为ACTION_CANCEL。
15行,如果child==null,super.dispatchTouchEvent();否则,child.dispatchTouchEvent();
super.dispatchTouchEvent()就是调用view的dispatchTouchEvent()方法,view的dispatchTouchEvent实现,其实就是调用自身的onTouchEvent(),即child==null,调用ViewGroup的onTouchEvent(),child!=null,将touch分发给传入的child。
20行,恢复event的action为原来的action;
38-57行,60-71行也是类似逻辑,如果child==null,super.dispatchTouchEvent();否则,child.dispatchTouchEvent();
由dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)调用参数可知,最终执行了child.dispatchTouchEvent()将触摸事件分发给了子view。
下面为View的dispatchTouchEvent源码
/** * Pass the touch screen motion event down to the target view, or this * view if it is the target. * * @param event The motion event to be dispatched. * @return True if the event was handled by the view, false otherwise. */ public boolean dispatchTouchEvent(MotionEvent event) { if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(event, 0); } if (onFilterTouchEventForSecurity(event)) { // 一般都成立 //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { // 先在ENABLED状态下尝试调用onTouch方法 return true; // 如果被onTouch处理了,则直接返回true } // 从这里我们可以看出,当你既设置了OnTouchListener又设置了OnClickListener,那么当前者返回true的时候, // onTouchEvent没机会被调用,当然你的OnClickListener也就不会被触发;另外还有个区别就是onTouch里可以 // 收到每次touch事件,而onClickListener只是在up事件到来时触发。 if (onTouchEvent(event)) { return true; } } if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } return false; // 上面的都没处理,则返回false }
View的dispatchTouchEvent方法很简单,16-17行,就是判断有没有设置OnTouchListener,如果设置了,并且OnTouchListener.onTouch()返回了true,则dispatchTouchEvent直接返回true;如果没有设置OnTouchListener或者OnTouchListener.onTouch()返回false,则到23行,调用onTouchEvent(),将onTouchEvent()的返回值,作为dispatchTouchEvent的返回值。
继续说ViewGroup的dispatchTouchEvent过程,如果child消耗了此touch,则会进入96行的if语句块中,最终执行到第104行,调用addTouchTarget()方法,那么在此方法中都做了什么呢,我们来看看其源码:
/** * Adds a touch target for specified child to the beginning of the list. * Assumes the target child is not already present. */ private TouchTarget addTouchTarget(View child, int pointerIdBits) { TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; mFirstTouchTarget = target; return target; }看以上代码,我们知道此方法就是在拿消耗掉触摸的child生成一个TouchTarget,添加到touch链的头部,并且mFirstTouchTarget指向此TouchTarget。主要就是更新mFirstTouchTarget的引用指向,消耗了此touch的child。
继续分析ViewGroup的dispatchTouchEvent方法
前面所有的判断机会都要求action为ACTION_DOWN,126行后终于没有了这条判断,也就是处理所有action。
126行,如果此ViewGroup的child没有处理此touch,mFirstTouchTarget==null,调用dispatchTransformedTouchEvent(ev, canceled, null, idBitsToAssign); 由上面分析dispatchTransformedTouchEvent方法代码可知,第三个参数child为null,会调用super.dispatchTouchEvent(),即ViewGroup的onTouchEvent。
也就是如果ViewGroup的child不消耗此touch,则调用ViewGroup的onTouchEvent方法。
134行后代码,则代表ViewGroup中有child消耗了此touch,135行将mFirstTouchTarget赋值给target,因为进入此else语句mFirstTouchTarget肯定不为空,所以target不为空,进入136行while循环,138行的if判断,对应于上边96行的if括号内的代码,如果这个touch第一次进行分发,走入了96行的if括号,也就是此touch的ACTION_DOWN被子view消耗后,代码走到138行,直接handled=true,不再处理。如果不是第一次分发,49行newTouchTarget=null,MotionEvent的action不是ACTION_DOWN,不会进入51,52行if语句块;所以这里会走到else语句。142行就是判断是否取消的操作,或者本来没有拦截现在变为了拦截。这里我们不讨论取消的情况,只考虑拦截和不拦截的情况,即cancelChild的值去intercepted的值。如果不拦截,cancelChild==false,调用dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits),会将此touch直接分发给mFirstTouchTarget记录的child;如果拦截(ViewGroup由原来不拦截件变为拦截),cancelChild==true,调用dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits),会将ACTION_CANCEL分发给mFirstTouchTarget记录的child。
150行,如果cancelChild==true,predecessor会一直为null,因为158行有continue语句,161行predecessor=target这句永远得不到执行,最终会把mFirstTouchTarget置为null,这样会导致23行的if语句块再得不到执行,及onInterceptTouchEvent方法再得不到执行。如果cancelChild==false,则mFirstTouchTarget会保存不变,只是把此touch分发到mFirstTouchTarget记录的child上去。
tips:如果ViewGroup的子View消耗了touch的ACTION_DOWN,此touch的后续事件,不再重新分发,直接传递到消耗ACTION_DOWN的View上去。
如果ViewGroup一开始不拦截,在后续事件中变为拦截(在onInterceptTouchEvent方法中根据滑动动作,返回了true),原来消耗touch的子View会受到ACTION_CANCEL事件,并且ViewGroup的onInterceptTouchEvent方法在此次touch事件中再不会执行。
nnd,终于写完了,写了好几天,语言不太会组织,程序员通病吧,单点触控只有一个TouchTarget,多点触控按下几个手指这几个手指触点会形成一个TouchTarget的链,简单起见,多点触控就没讲,单点和多点事件分发处理的过程是完全一样的的。大伙看思路就好。如有理解不一致的地方,还望在评论区留言讨论。
- ViewGroup对触摸事件的分发响应过程
- 触摸事件分发响应
- 触摸事件分发机制-viewgroup
- 触摸事件分发机制-viewGroup
- ViewGroup学习之触摸事件分发
- ViewGroup的事件分发
- ViewGroup的事件分发
- UIImageView对触摸事件的响应
- Android 触摸事件分发过程
- 对Android view/viewgroup事件分发的理解
- Android触摸事件分发与响应
- ViewGroup的事件分发机制
- ViewGroup的事件分发机制
- ViewGroup的事件分发机制
- ViewGroup 的事件分发机制
- ViewGroup触摸事件的分发,拦截,消耗(dispatchTouchEvent , onInterceptTouchEvent , onTouchEvent)
- 触摸事件的分发机制
- andorid事件分发机制 ViewGroup的事件分发机制
- 负载均衡调度算法大全
- 【CF 732E】Sockets(优先队列+贪心)
- 集合和数组
- NYOJ-75 日期计算
- Sublime Text 3 快捷键总结
- ViewGroup对触摸事件的分发响应过程
- redis 集群服务搭建及配置
- Android多线程用法及消息机制
- 1080. Graduate Admission
- 网络传输数据格式的选择:
- Android实现一款不错Banner界面广告图片循环轮播
- 数据库连接池
- 利用ajax实现省市县级联选择
- js面向对象