ViewGroup与View
ViewGroup和View事件的分发,拦截,消费的相关方法如下表:
类型 | 相关方法 | ViewGroup | View |
---|---|---|---|
事件分发 | dispatchTouchEvent | 有 | 有 |
事件拦截 | onInterceptTouchEvent | 有 | 无 |
事件消费 | onTouchEvent | 有 | 有 |
这个三个方法的返回值均是Boolean类型,通过true和false来控制事件的传递和消费流程。我们先通过实际例子来看看事件传递,消费的流程。
xml:复制代码
TouchViewGroup:
class TouchViewGroup : RelativeLayout { val TAG : String = "TouchStudy" constructor(ctx: Context):super(ctx) constructor(ctx: Context,attrs: AttributeSet):super(ctx,attrs) override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { when(ev!!.action){ MotionEvent.ACTION_DOWN -> { Log.i(TAG,"ParentView dispatchTouchEvent ACTION_DOWN") } MotionEvent.ACTION_MOVE -> { Log.i(TAG,"ParentView dispatchTouchEvent ACTION_MOVE") } MotionEvent.ACTION_UP,MotionEvent.ACTION_CANCEL ->{ Log.i(TAG,"ParentView dispatchTouchEvent ACTION_CANCEL") } } var ret = super.dispatchTouchEvent(ev) Log.i(TAG,"ParentView dispatchTouchEvent return :" + ret) return ret } override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { when(ev!!.action){ MotionEvent.ACTION_DOWN -> { Log.i(TAG,"ParentView onInterceptTouchEvent ACTION_DOWN") } MotionEvent.ACTION_MOVE -> { Log.i(TAG,"ParentView onInterceptTouchEvent ACTION_MOVE") // return true } MotionEvent.ACTION_UP,MotionEvent.ACTION_CANCEL ->{ Log.i(TAG,"ParentView onInterceptTouchEvent ACTION_CANCEL") } } var ret = super.onInterceptTouchEvent(ev) Log.i(TAG,"ParentView onInterceptTouchEvent return :" + ret) return ret } override fun onTouchEvent(event: MotionEvent?): Boolean { when (event!!.action) { MotionEvent.ACTION_DOWN -> { Log.i(TAG, "ParentView onTouchEvent ACTION_DOWN") } MotionEvent.ACTION_MOVE -> { Log.i(TAG, "ParentView onTouchEvent ACTION_MOVE") } MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { Log.i(TAG, "ParentView onTouchEvent ACTION_CANCEL") } } var ret = super.onTouchEvent(event) Log.i(TAG,"ParentView onTouchEvent return :" + ret) return super.onTouchEvent(event) }}复制代码
TouchChildView:
class TouchChildView : TextView { val TAG : String = "TouchStudy" constructor(ctx: Context): super(ctx) constructor(ctx: Context,attrs: AttributeSet): super(ctx,attrs) override fun dispatchTouchEvent(event: MotionEvent?): Boolean { when(event!!.action){ MotionEvent.ACTION_DOWN -> { Log.i(TAG,"ChildView dispatchTouchEvent ACTION_DOWN") } MotionEvent.ACTION_MOVE -> { Log.i(TAG,"ChildView dispatchTouchEvent ACTION_MOVE") } MotionEvent.ACTION_UP,MotionEvent.ACTION_CANCEL ->{ Log.i(TAG,"ChildView dispatchTouchEvent ACTION_CANCEL") } } var ret : Boolean = super.dispatchTouchEvent(event) Log.i(TAG,"ChildView dispatchTouchEvent return :" + ret) return ret } override fun onTouchEvent(event: MotionEvent?): Boolean { when (event!!.action) { MotionEvent.ACTION_DOWN -> { Log.i(TAG, "ChildView onTouchEvent ACTION_DOWN") } MotionEvent.ACTION_MOVE -> { Log.i(TAG, "ChildView onTouchEvent ACTION_MOVE") } MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { Log.i(TAG, "ChildView onTouchEvent ACTION_CANCEL") } } var ret : Boolean = super.onTouchEvent(event) Log.i(TAG,"ChildView onTouchEvent return :" + ret) return ret }}复制代码
TouchViewGroup与TouchChildView主要是将接收到的事件打印出来。
TouchStudy: ParentView dispatchTouchEvent ACTION_DOWNTouchStudy: ParentView onInterceptTouchEvent ACTION_DOWNTouchStudy: ParentView onInterceptTouchEvent return :falseTouchStudy: ChildView dispatchTouchEvent ACTION_DOWNTouchStudy: ChildView onTouchEvent ACTION_DOWNTouchStudy: ChildView onTouchEvent return :falseTouchStudy: ChildView dispatchTouchEvent return :falseTouchStudy: ParentView onTouchEvent ACTION_DOWNTouchStudy: ParentView onTouchEvent return :falseTouchStudy: ParentView dispatchTouchEvent return :false复制代码
其事件的分发消费流程如下图 :
由于childView在onTouchEvent()中没有消耗down事件导致childView dispatchTouchEvent返回false,这会让ParentView不会把后续的ACTION_MOVE和ACTION_UP分发给childView。同理由于childView和parentView的onTouchEvent()都没有消耗down事件,所以parentView也没有收到后续的ACTION_DOWN和ACTION_UP事件。② 现在我们让childView的onTouchEvent()消耗ACTION_DOWN事件,但ViewGroup中不拦截ACTION_DOWN事件,改动代码如下:
/** * childView onTouchEvent */ override fun onTouchEvent(event: MotionEvent?): Boolean { when (event!!.action) { MotionEvent.ACTION_DOWN -> { Log.i(TAG, "ChildView onTouchEvent ACTION_DOWN") return true } MotionEvent.ACTION_MOVE -> { Log.i(TAG, "ChildView onTouchEvent ACTION_MOVE") } MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { Log.i(TAG, "ChildView onTouchEvent ACTION_CANCEL") } } var ret : Boolean = super.onTouchEvent(event) Log.i(TAG,"ChildView onTouchEvent return :" + ret) return ret }复制代码
在childView中滑动之后,其log为:
TouchStudy: ParentView dispatchTouchEvent ACTION_DOWNTouchStudy: ParentView onInterceptTouchEvent ACTION_DOWNTouchStudy: ParentView onInterceptTouchEvent return :falseTouchStudy: ChildView dispatchTouchEvent ACTION_DOWNTouchStudy: ChildView onTouchEvent ACTION_DOWNTouchStudy: ChildView onTouchEvent return :trueTouchStudy: ChildView dispatchTouchEvent return :trueTouchStudy: ParentView dispatchTouchEvent return :trueTouchStudy: ParentView dispatchTouchEvent ACTION_MOVETouchStudy: ParentView onInterceptTouchEvent ACTION_MOVETouchStudy: ParentView onInterceptTouchEvent return :falseTouchStudy: ChildView dispatchTouchEvent ACTION_MOVETouchStudy: ChildView onTouchEvent ACTION_MOVETouchStudy: ChildView onTouchEvent return :falseTouchStudy: ChildView dispatchTouchEvent return :falseTouchStudy: ParentView dispatchTouchEvent return :falseTouchStudy: ParentView dispatchTouchEvent ACTION_MOVETouchStudy: ParentView onInterceptTouchEvent ACTION_MOVETouchStudy: ParentView onInterceptTouchEvent return :falseTouchStudy: ChildView dispatchTouchEvent ACTION_MOVETouchStudy: ChildView onTouchEvent ACTION_MOVETouchStudy: ChildView onTouchEvent return :falseTouchStudy: ChildView dispatchTouchEvent return :falseTouchStudy: ParentView dispatchTouchEvent return :falseTouchStudy: ParentView dispatchTouchEvent ACTION_CANCELTouchStudy: ParentView onInterceptTouchEvent ACTION_CANCELTouchStudy: ParentView onInterceptTouchEvent return :falseTouchStudy: ChildView dispatchTouchEvent ACTION_CANCELTouchStudy: ChildView onTouchEvent ACTION_CANCELTouchStudy: ChildView onTouchEvent return :falseTouchStudy: ChildView dispatchTouchEvent return :falseTouchStudy: ParentView dispatchTouchEvent return :false复制代码
其ACTION_DOWN事件的分发,消费流程图如下:
其ACTION_MOVE和ACITON_UP(ACTION_CANCEL)事件的分发,消费入下图: 通过log可以看到,ACTION_MOVE与ACTION_DOWN有很大不同, 当childView不消费ACTION_DOWN事件时,childView将不会收到后续的ACTION_MOVE与ACTION_UP事件,但是如果childView消费了ACTION_DOWN事件,在parentView不拦截事件的情况下,不管childView是否消费ACTION_MOVE事件,childView 还是会收到后续的ACTION_MOVE和ACTION_UP事件。换句话说View是否消费完整的一系列事件(完整的一系列事件指的是:手指按下到抬起中间所产生的所有事件)的关键取决与是否消费了ACTION_DOWN事件。③ 让ViewGroup中拦截ACTION_DOWN事件,改动代码如下:
/** * parentView onInterceptTouchEvent */ override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { when(ev!!.action){ MotionEvent.ACTION_DOWN -> { Log.i(TAG,"ParentView onInterceptTouchEvent ACTION_DOWN") return true; } MotionEvent.ACTION_MOVE -> { Log.i(TAG,"ParentView onInterceptTouchEvent ACTION_MOVE") // return true } MotionEvent.ACTION_UP,MotionEvent.ACTION_CANCEL ->{ Log.i(TAG,"ParentView onInterceptTouchEvent ACTION_CANCEL") } } var ret = super.onInterceptTouchEvent(ev) Log.i(TAG,"ParentView onInterceptTouchEvent return :" + ret) return ret } /** * childView onTouchEvent */ override fun onTouchEvent(event: MotionEvent?): Boolean { when (event!!.action) { MotionEvent.ACTION_DOWN -> { Log.i(TAG, "ChildView onTouchEvent ACTION_DOWN") Log.i(TAG,"ChildView onTouchEvent return :" + true) return true } MotionEvent.ACTION_MOVE -> { Log.i(TAG, "ChildView onTouchEvent ACTION_MOVE") } MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { Log.i(TAG, "ChildView onTouchEvent ACTION_CANCEL") } } var ret : Boolean = super.onTouchEvent(event) Log.i(TAG,"ChildView onTouchEvent return :" + ret) return ret }复制代码
这种情景下的运行log如下:
TouchStudy: ParentView dispatchTouchEvent ACTION_DOWNTouchStudy: ParentView onInterceptTouchEvent ACTION_DOWNTouchStudy: ParentView onTouchEvent ACTION_DOWNTouchStudy: ParentView onTouchEvent return :falseTouchStudy: ParentView dispatchTouchEvent return :false复制代码
ACTION_DOWN事件的分发消费流程图:
这种情况下,parentView不会将任何事件分发给childView,同时parentView也没有消费ACTION_DOWN事件,导致parentView不会收到ACTION_MOVE,ACTION_UP等事件。④ 让childView消耗ACTION_DOWN事件,同时让ViewGroup中拦截ACTION_MOVE事件,改动代码如下:
/** * parentView onInterceptTouchEvent */ override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { when(ev!!.action){ MotionEvent.ACTION_DOWN -> { Log.i(TAG,"ParentView onInterceptTouchEvent ACTION_DOWN") } MotionEvent.ACTION_MOVE -> { Log.i(TAG,"ParentView onInterceptTouchEvent ACTION_MOVE") return true } MotionEvent.ACTION_UP,MotionEvent.ACTION_CANCEL ->{ Log.i(TAG,"ParentView onInterceptTouchEvent ACTION_CANCEL") } } var ret = super.onInterceptTouchEvent(ev) Log.i(TAG,"ParentView onInterceptTouchEvent return :" + ret) return ret } /** * childView onTouchEvent */ override fun onTouchEvent(event: MotionEvent?): Boolean { when (event!!.action) { MotionEvent.ACTION_DOWN -> { Log.i(TAG, "ChildView onTouchEvent ACTION_DOWN") Log.i(TAG,"ChildView onTouchEvent return :" + true) return true } MotionEvent.ACTION_MOVE -> { Log.i(TAG, "ChildView onTouchEvent ACTION_MOVE") } MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { Log.i(TAG, "ChildView onTouchEvent ACTION_CANCEL") } } var ret : Boolean = super.onTouchEvent(event) Log.i(TAG,"ChildView onTouchEvent return :" + ret) return ret }复制代码
其运行log如下:
TouchStudy: ParentView dispatchTouchEvent ACTION_DOWNTouchStudy: ParentView onInterceptTouchEvent ACTION_DOWNTouchStudy: ParentView onInterceptTouchEvent return :falseTouchStudy: ChildView dispatchTouchEvent ACTION_DOWNTouchStudy: ChildView onTouchEvent ACTION_DOWNTouchStudy: ChildView onTouchEvent return :trueTouchStudy: ChildView dispatchTouchEvent return :trueTouchStudy: ParentView dispatchTouchEvent return :trueTouchStudy: ParentView dispatchTouchEvent ACTION_MOVETouchStudy: ParentView onInterceptTouchEvent ACTION_MOVETouchStudy: ChildView dispatchTouchEvent ACTION_CANCELTouchStudy: ChildView onTouchEvent ACTION_CANCELTouchStudy: ChildView onTouchEvent return :falseTouchStudy: ChildView dispatchTouchEvent return :falseTouchStudy: ParentView dispatchTouchEvent return :falseTouchStudy: ParentView dispatchTouchEvent ACTION_MOVETouchStudy: ParentView onTouchEvent ACTION_MOVETouchStudy: ParentView dispatchTouchEvent ACTION_CANCELTouchStudy: ParentView onTouchEvent ACTION_CANCELTouchStudy: ParentView onTouchEvent return :falseTouchStudy: ParentView dispatchTouchEvent return :false复制代码
ACTION_DOWN事件的分发消费过程图:
ACTION_MOVE的分发消费过程图:
从③和④两种情况可以看出当parentView拦截ACTION_DOWN事件后,childView将不会收到ACTION_DOWN事和后续的ACTION_MOVE与ACTION_UP事件,而parentView能否收到后续的事件,取决于parentView的onTouchEvent是否消费ACTION_DOWN事件。当childView消费了ACTION_DOWN事件后,如果parentView拦截ACTION_MOVE事件,则后续的一系列事件都将交由parentView的ontouchEvent()来处理,不会再走onInterceptionEvent()方法,childView将不会收到后续的任何事件。
结论:
(1)不管是GroupView还是View,消费事件指的是消费ACTION_DOWN事件,只有消费了ACTION_DOWN事件,才有可能接收分发后续的ACTION_MOVE和ACTION_UP等事件。(2)对于View而言,消耗ACTION_DOWN事件只有一种途径:在onTouchEvent()中接受到ACTION_DOWN时返回true。 而GroupVie消耗ACTION_DOWN事件有两种途径:①其child view消费ACTION_DOWN;②其自身消耗ACTION_DOWN事件,即在其自身的onTouchEvent()中接受到ACTION_DOWN事件时返回true,这两种消耗ACTION_DOWN事件的关系为:parentView会先view是否消耗ACTION_DOWN分发给child view,让child view决定是否消耗ACTION_DOWN,只有当child view不消耗时,才将ACTION_DONW事件传递给其本身的onTouchEvent()方法让其判断是否消费ACTION_DOWN方法。
(3)当GroupView拦截某一事件时,GroupView 不会将这一事件和后续的所有事件分发给child view,拦截的这一事件将会交给GroupView的onTouchEvent()处理。对于后续GroupView接收到的所有事件不会走拦截过程,而是直接交由GroupView的onTouchEvent()处理。
源码分析
以下所有的源码都是基于API26(android 8.0)
View 的dispatchTouchEvent源码:
public boolean dispatchTouchEvent(MotionEvent event) { ... if (onFilterTouchEventForSecurity(event)) { //关注点一:当View为可点击并且在拖动scrollBar时直接消费事件 if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) { result = true; } //关注点二:当View设置为可点击并且并且设置OnTouchListener处理时,直接消费事件,并将所有的一系列事件交由OnTouchListener处理 ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } //关注点三:如果不是拖动scrollBar并且没有设置OnTouchListener则事件交由onTouchEvent()处理 if (!result && onTouchEvent(event)) { result = true; } } if (!result && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } if (actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL || (actionMasked == MotionEvent.ACTION_DOWN && !result)) { stopNestedScroll(); } return result; }复制代码
从“关注点二”和“关注点三”中可以看出如果View设置了OnTouchListener则view直接消费事件,并将所有的事件都交由OnTouchListener处理,就onTouchEvent()什么事了,如果没有设置OnTouchListener,则是否消费事件由onTouchEvent()决定,下面看看View的onTouchEvent()源码。onTouchEvent源码较长,挑重点来看
public boolean onTouchEvent(MotionEvent event) { final float x = event.getX(); final float y = event.getY(); final int viewFlags = mViewFlags; final int action = event.getAction(); final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE; ... if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) { switch (action) { case MotionEvent.ACTION_UP: ... break; case MotionEvent.ACTION_DOWN: ... break; case MotionEvent.ACTION_CANCEL: ... case MotionEvent.ACTION_MOVE: ... break; } return true; } return false; }复制代码
View的onTouchEvent()比较好理解,如果View设置为可点击或设置OnLongClickListener,OnClickListenerView就消费了事件。switch里的代码是具体处理长按监听和点击事件等事情。
GroupView的dispatchTouchEvent()就比较复杂了,下面通过关键源码来看看GroupView是如何分发消费事件的。
public boolean dispatchTouchEvent(MotionEvent ev) { ... boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; if (actionMasked == MotionEvent.ACTION_DOWN) { //关注点一:接收到ACTION_DOWN事件时,将mFirstTouchTarget设置为 null cancelAndClearTouchTargets(ev); resetTouchState(); } final boolean intercepted; //关注点二:当处理ACTION_DOWN或mFirstTouchTarget不为空是走拦截流程 if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget不为空是走拦截流程 != null) { /*关注点三:是否不允许拦截事件,当child View调用 parent.requestDisallowInterceptTouchEvent()时,disallowIntercept = true*/ final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { intercepted = false; } } else { intercepted = true; } if (intercepted || mFirstTouchTarget != null) { ev.setTargetAccessibilityFocus(false); } final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; if (!canceled && !intercepted) { ... /*关注点四:只有当事件为ACTION_DOWN或者为多点触控的ACTION_POINTER_DOWN或者鼠标悬浮事件时, 才会去在child view中寻找消费事件的view*/ if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { final int actionIndex = ev.getActionIndex(); //识别手指的标志位 final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) : TouchTarget.ALL_POINTER_IDS; removePointersFromTouchTargets(idBitsToAssign); final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); /*关注点五:先根据Z轴方法z坐标值大小排序,(Z轴是三维坐标系里的Z方向,cardView设置阴影是就是设置Z轴的值), 再根据child view的绘制顺序排序*/ final ArrayListpreorderedList = buildTouchDispatchChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex); ... //关注点六:如果child view 不可见,并且事件不是发生在child view的范围内直接跳出查找下一个child view if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; } newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { newTouchTarget.pointerIdBits |= idBitsToAssign; break; } resetCancelNextUpFlag(child); //关注点七:如果找到处理这一事件的child view,给mFirstTouchTarget赋值。 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // Child wants to receive touch within its bounds. mLastTouchDownTime = ev.getDownTime(); if (preorderedList != null) { // childIndex points into presorted list, find original index for (int j = 0; j < childrenCount; j++) { if (children[childIndex] == mChildren[j]) { mLastTouchDownIndex = j; break; } } } else { mLastTouchDownIndex = childIndex; } mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); //关注点八:给mFirstTouchTarget赋值 newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } ... } if (preorderedList != null) preorderedList.clear(); } 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.pointerIdBits |= idBitsToAssign; } } } // 关注点九:如果没有找到child view去消费事件,将事件分发给自己,判断是否消费事件 if (mFirstTouchTarget == null) { handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; //遍历mFirstTouchTarget这个链表 while (target != null) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { /* 关注点十:如果child view消耗事件,判断是否拦截事件,如果拦截事件则child view 将不会收到这次事件,而是收到 ACTION_CANCEL事件,而mFirstTouchTarget最终将会赋值为null;如果不拦截事件,则按正常流程将事件分发给child view */ final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; 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事件时,将 mFirstTouchTarget设置为null,重置FLAG_DISALLOW_INTERCEPT位*/ 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; }复制代码
- 重点关注下mFirstTouchTarget这个对象,mFirstTouchTarget是一个链表结构的对象,正常情况下这个链表只有一个元素,当GroupView允许处理多点触控事件时,并且多点触控发生时mFirstTouchTarget可能会有多个元素。多点触控事件的分发消费流程与单点触控的流程相似,下面的说明都是以单点触控为例说明。
- ‘关注点五’注释的代码说明了ACTION_DOWN事件是按照child view绘制顺序来分发事件的,先绘制的child view会优先收到ACTION_DOWN事件。
- ‘关注点四’到‘关注点九’之间的代码就是ACTION_DOWN事件的分发消费过程。如果child view消费了ACTION_DOWN事件,在‘关注点八’的代码就给mFirstTouchTarget赋值,否则mFirstTouchTarget为null。
- ‘关注点九’说明如果child view没有消费ACTION_DOWN事件,其是绝对不会接受到后续的ACTION_MOVE,ACTION_UP等事件,换句话说view消费事件指的是是否消费ACTION_DOWN事件。至于消费了ACTION_DOWN事件之后,能否接受到后续的事件,就要看paren view是否拦截事件了。
- ‘关注点十’代码是mFirstTouchTarget的遍历过程,如果parentView拦截了事件,则child view会收到ACITON_CANCEL事件,同时mFirstTouchTarget会通过链表的遍历会最终赋值为null,这回导致后续的事件不会分发给child view而是执行‘关注点九’的代码。
下面是dispatchTransformedTouchEvent()的代码分析。
//当事件由GroupView自己处理则child = null private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; final int oldAction = event.getAction(); //当GroupView拦截事件时cancel = true if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { //GroupView自己处理事件。 GroupView继承制View,super.dispatchTouchEvent就是调用View.dispatchTouchEvent handled = super.dispatchTouchEvent(event); } else { //将事件分发给child view处理 handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; } // 获取此次事件的所有手指标识位|运算值{@link MotionEvent#getPointerIdBits()} 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); } 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); } transformedEvent.recycle(); return handled; }复制代码
事件从activity到child view的完整分发消费图: