内容简介:从当一个点击操作发生时,最先传递给 当前 Activity,由 Activity 的 dispatchTouchEvent 方法分发。我们看下 Activity 的 dispatchTouchEvent 的具体源码:继续查看 window 的 superDispatchTouchEvent 源码,window 是一个抽象类,有一个唯一的实现类 PhoneWindow(这一点可以在 window 的类注释中查看到),那么我们就找 PhoneWindow 的 superDispatchTouchEvent 方法
Activity ---> ViewGroup流程分析
从 View的事件分发(一)分发流程 文章得知,从用户点击屏幕到屏幕中的控件响应操作的大致流程是 Activity-> ViewGroup-> View。那么 Activity 是怎么把事件传递给我们在 xml 中写的根视图呢?就是说 Activity-> ViewGroup 具体是一个怎样的流程?
当一个点击操作发生时,最先传递给 当前 Activity,由 Activity 的 dispatchTouchEvent 方法分发。我们看下 Activity 的 dispatchTouchEvent 的具体源码:
//Activity public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } //调用 window 的 superDispatchTouchEvent 方法进行事件的分发 //如果返回 true,就代表事件被消费, Activity 的 dispatchTouchEvent 就直接返回 true,不再往下执行。 //如果返回 false,就代表事件没有被消费,就调用 Activity 的 onTouchEvent 方法 if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); } 复制代码
继续查看 window 的 superDispatchTouchEvent 源码,window 是一个抽象类,有一个唯一的实现类 PhoneWindow(这一点可以在 window 的类注释中查看到),那么我们就找 PhoneWindow 的 superDispatchTouchEvent 方法。
//PhoneWindow @Override public boolean superDispatchTouchEvent(MotionEvent event) { //mDecor 是 DecorView //这里调用 DecorView 的 superDispatchTouchEvent 方法 return mDecor.superDispatchTouchEvent(event); } 复制代码
继续查看 DecorView 的 superDispatchTouchEvent 方法
//DecorView public boolean superDispatchTouchEvent(MotionEvent event) { //调用父类的 dispatchTouchEvent 方法进行分发. return super.dispatchTouchEvent(event); } 复制代码
我们知道可以通过 ((ViewGroup) getWindow().getDecorView().findViewById(android.R.id.content)).getChildAt(0);
来找到 Activity 所设置的 View,PhoneWindow 中调用的 mDecor 就是 getWindow().getDecorView() 获取的 DecorView,我们通过 Activity 所设置的 View 就是它的子 View。DecorView 是继承的 FrameLayout,当然 FrameLayout 是没有重写 dispatchTouchEvent 方法的,所以最终就是通过 FrameLayout 的父类 ViewGroup 的 dispatchTouchEvent 方法来将事件分发给我们开发的页面。
总结
完整的事件分发流程: Activity ---> PhoneWindow ---> DecorView ---> ViewGroup ---> ··· --->View
ViewGroup ---> View流程分析
在分析 ViewGroup 的 dispatchTouchEvent 方法之前,先分析一个关键的类: TouchTarget 。
TouchTarget是 ViewGroup 中的一个静态内部类。记录了响应 Touch 事件 View 的链表。在 ViewGroup 中,有一个成员变量 private TouchTarget mFirstTouchTarget
,这个变量在 dispatchTouchEvent 分发 Touch 事件中起了重要作用。
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. //记录响应 Touch 事件的 View public View child; // The combined bit mask of pointer ids for all pointers captured by the target. // 对目标捕获的所有指针的指针id的组合位掩码,和多点触控有关。 public int pointerIdBits; // The next target in the target list. //链表中的下一个记录触摸 View 的 TouchTarget 对象 public TouchTarget next; private TouchTarget() { } public static TouchTarget obtain(@NonNull View child, int pointerIdBits) { if (child == null) { throw new IllegalArgumentException("child must be non-null"); } final TouchTarget target; synchronized (sRecycleLock) { if (sRecycleBin == null) { //应用中,首次被触摸的时候 sRecycleBin 是为 null 的。创建一个新对象 target = new TouchTarget(); } else { //将链表的表首的对象赋值为 target,sRecycleBin 指向下一个对象等待下一次复用。 target = sRecycleBin; //表头 sRecycleBin 往下移一位,指向 next 对象。 sRecycleBin = target.next; //可复用的实例链表的长度减一 sRecycledCount--; //将 target 的 next 赋值为 null target.next = null; } } //记录响应 Touch 事件的 View。 target.child = child; target.pointerIdBits = pointerIdBits; return target; } //回收 TouchTarget 对象,后面复用 public void recycle() { if (child == null) { throw new IllegalStateException("already recycled once"); } synchronized (sRecycleLock) { //复用链表的长度最大不超过 MAX_RECYCLED if (sRecycledCount < MAX_RECYCLED) { //在链表的最前面加一个可复用的对象作为表头。sRecycleBin 指向这个对象。 next = sRecycleBin; sRecycleBin = this; sRecycledCount += 1; } else { next = null; } child = null; } } } 复制代码
mFirstTouchTarget 是通过 TouchTarget.obtain 方法赋值的(这个后面会说),应用首次被触摸的时候是通过 new 一个对象来赋值的,后面触摸的时候,是复用了链表首部对象。最后通过 recycle() 方法用来回收自己,用来以后复用。
首先看 ViewGroup 的 dispatchTouchEvent 方法:
分析之前,我们先确定两个疑问:
- 问题一:ViewGroup 是怎么分发给子 View 的?
- 问题二:关于 requestDisallowInterceptTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) { if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(ev, 1); } // If the event targets the accessibility focused view and this is it, start // normal event dispatch. Maybe a descendant is what will handle the click. if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) { ev.setTargetAccessibilityFocus(false); } boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; // Handle an initial down. if (actionMasked == MotionEvent.ACTION_DOWN) { //取消并清除触摸目标,里面将 mFirstTouchTarget 置为 null。 // mFirstTouchTarget 是记录响应触摸事件 View 的链表。 cancelAndClearTouchTargets(ev); //如果是 DOWN 事件,就重置触摸状态。 //将 mGroupFlags 重置 FLAG_DISALLOW_INTERCEPT 标志位,这个是和 requestDisallowInterceptTouchEvent 相关的,后面再进行解释。 resetTouchState(); //这两个方法内部都调用了 clearTouchTargets() 方法,回收 mFirstTouchTarget 指向的链表上的对象,将 mFirstTouchTarget 置为null。 } ①------------ //检查是否拦截 // Check for interception. final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { //是否不允许拦截 //(requestDisallowInterceptTouchEvent 就是通过给 mGroupFlags 添加FLAG_DISALLOW_INTERCEPT 标志位来告诉父控件不要拦截的) final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { //如果允许拦截,那就调用 onInterceptTouchEvent 方法查询是否拦截,记录返回值 intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { //如果不允许拦截,直接将 intercepted 赋值为 false intercepted = false; } } else { // There are no touch targets and this action is not an initial down // so this view group continues to intercept touches. //如果不是DOWN 事件,并且 mFirstTouchTarget 为 null,这个 ViewGroup 就继续拦截事件 //如果能够执行到这里,说明 DOWN 后面的事件传递到了这里, intercepted = true; } // If intercepted, start normal event dispatch. Also if there is already // a view that is handling the gesture, do normal event dispatch. if (intercepted || mFirstTouchTarget != null) { ev.setTargetAccessibilityFocus(false); } // Check for cancelation. //是否自己被拦截,或者传递的是 CANCEL 事件 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; if (!canceled && !intercepted) { // If the event is targeting accessiiblity focus we give it to the // view that has accessibility focus and if it does not handle it // we clear the flag and dispatch the event to all children as usual. // We are looking up the accessibility focused host to avoid keeping // state since these events are very rare. View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus() ? findChildWithAccessibilityFocus() : null; //如果是 DOWN 事件。 //(ACTION_POINTER_DOWN 是一个手指按下的后,另一个手指也按下传递的 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 (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 ArrayList<View> preorderedList = buildTouchDispatchChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; i--) { ②------------ //遍历子 View //注意 这里是倒序遍历的,先看最上面的 View 是否响应操作事件,其实这也是符合我们的直觉的. final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex); // If there is a view that has accessibility focus we want it // to get the event first and if not handled we will perform a // normal dispatch. We may do a double iteration but this is // safer given the timeframe. if (childWithAccessibilityFocus != null) { if (childWithAccessibilityFocus != child) { continue; } childWithAccessibilityFocus = null; i = childrenCount - 1; } ③------------ //查看子 View 是否在当前点击区域,如果不在就跳过这次循环 if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; } //判断这个 View 是否已经在相应事件的链表中。如果在,就跳出循环。 //例如:一个手指按在一个按钮上后,另一个手指也按在这个按钮上,这个时候不响应事件。 newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { // 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); ④------------ //这里调用了 dispatchTransformedTouchEvent 方法, //这个方法里面调用子 View 的 dispatchTouchEvent。(后面说明) if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { //如果 dispatchTransformedTouchEvent 返回了 true,就代表这个 child 消费了这个事件 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(); ⑤------------ //调用 addTouchTarget 方法,为 mFirstTouchTarget 进行赋值 newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } ev.setTargetAccessibilityFocus(false); } if (preorderedList != null) preorderedList.clear(); } //没有找到响应控件的 View,但是 mFirstTouchTarget 不为null(之前存在响应事件的 View) //例如:一个手指按在一个按钮上,另个一手指按在其他控件上(没有消费事件) 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; //遍历链表,将 newTouchTarget 指向链表的尾部的对象。 while (newTouchTarget.next != null) { newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |= idBitsToAssign; } } } // Dispatch to touch targets. if (mFirstTouchTarget == null) { ⑥------------ //如果 mFirstTouchTarget 为null,没有子 View 消费事件 //就通过 dispatchTransformedTouchEvent 调用自身的 onTouchEvent. // 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) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { //DOWN 事件传递过来的时候,是走到这里的。这一点在前面的流程中有体现。 handled = true; } else { // target.child 是否被设置了 CANCEL 事件,或者传递过来的是否是 CANCEL 事件。 final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; ⑦------------ //对于非 DOWN 事件,也是在这里调用 dispatchTransformedTouchEvent 方法进行分发。 /* *如果父控件拦截事件 intercepted 为true,则 cancelChild 为 true。 *在调用 dispatchTransformedTouchEvent 进行分发事件的时候,传递了一个 CANCEL 事件给子 View(后面说明) */ if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } if (cancelChild) { if (predecessor == null) { ⑧------------ //如果表头被回收, mFirstTouchTarget 指向下一个对象。 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(); } 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; } --------------------------------------------------------------------- //ViewGroup 类 private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) { //创建一个 TouchTarget 对象 final TouchTarget target = TouchTarget.obtain(child, pointerIdBits); //将 mFirstTouchTarget 赋值给 target 的 next。第一次赋值的话,mFirstTouchTarget肯定是 null了。 target.next = mFirstTouchTarget ; //mFirstTouchTarget 赋值为 target mFirstTouchTarget = target; return target; } --------------------------------------------------------------------- //ViewGroup 类 private TouchTarget getTouchTarget(@NonNull View child) { for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) { //遍历链表,找到该 child 所在的 TouchTarget 对象。 if (target.child == child) { return target; } } return null; } --------------------------------------------------------------------- private void clearTouchTargets() { TouchTarget target = mFirstTouchTarget; //遍历链表,依次调用链表上TouchTarget对象的 recycle 方法。 if (target != null) { do { TouchTarget next = target.next; target.recycle(); target = next; } while (target != null); //将 mFirstTouchTarget 置为null。 mFirstTouchTarget = null; } } 复制代码
dispatchTransformedTouchEvent 方法:
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) { //cancel为 true,或者传递的是一个 CANCEL 事件,设置 cancel 事件 event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { //如果 child 为空,就调用父类(View)的 dispatchTouchEvent 方法来分发 //(里面调用了 onTouchEvent 方法,后面进行分析) handled = super.dispatchTouchEvent(event); } else { //child 不为 null,就调用子类的分发 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 (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); //将事件分发给子 View 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; } 复制代码
-
问题一:ViewGroup 是怎么分发给子 View 的?
DOWN 事件:通过上述源码分析,ViewGroup 在进行事件分发的时候,先在 DOWN 事件的时候重置一些状态,然后在 代码① 处进行询问是否进行拦截,因为首次传入的事件是 ACTION_DOWN 事件,并且 mFirstTouchTarget 被重置为 null,所以就调用 onInterceptTouchEvent 询问是否拦截。如果 不拦截 就执行到 代码② ,进行遍历子 View,寻找在点击区域的子类( 代码③ ) ,然后通过( 代码④ ) dispatchTransformedTouchEvent 方法调用该 child 的 dispatchTouchEvent 方法进行分发。如此事件就分发到了子 View。
此时,如果子 View 消费了 DOWN 事件( child 的 dispatchTouchEvent 返回 true ---> dispatchTransformedTouchEvent 方法返回 true)。就执行到了 代码⑤ ,通过 addTouchTarget 将该 View 绑定到 TouchTarget 链表中,并且是在表头位置,并且将 newTouchTarget 和 mFristTouchTarget 都赋值指向表头。如果不消费,就进行下一次循环,寻找消费该事件的子 View。
那么,如果 ViewGroup 进行事件 拦截 呢?事件分发该怎么执行?分为两种情况:1. 直接在 DOWN 事件的时候进行拦截;2. 拦截 MOVE、UP 事件。
- 如果直接拦截 DOWN 事件,mFirstTouchTarget 为 null,intercepted 为 true。那么直接执行到 代码⑥ ,通过 dispatchTransformedTouchEvent 源码可知,直接执行了super.dispatchTouchEvent(event)。View (ViewGroup 父类是 View)的 dispatchTouchEvent 方法内部调用了 onTouchEvent 方法,从而事件传递到了 ViewGroup 的 onTouchEvent 方法。
- MOVE、UP 事件到来的时候开始拦截,此时会产生 CANCEL 事件。具体分析在 延伸知识一:CANCEL 事件产生的内部实现。 。
MOVE、UP 事件:MOVE、UP 事件能够传递过来,那就说明有子 View 消费了 DOWN 事件。mFristTouchTarget 不为 null。代码执行到 代码⑦ ,通过 dispatchTransformedTouchEvent 方法将事件分发给消费了 DOWN 事件的子 View。
-
延伸知识一:CANCEL 事件产生的内部实现。
CANCEL 事件产生的条件就是,子 View 消费了 DOWN 事件,在 MOVE、UP 事件中,父View 拦截了事件。在 View的事件分发(一)分发流程 中的 ==CANCEL 事件模拟==中看到的日志是: 在第一个 MOVE 事件中,子 View 收到了 CANCEL 事件,第二个 MOVE 事件的时候,ViewGroup 的 onTouchEvent 才接收收到事件。 那么我们分析一下流程:
由于子 View 消费了 DOWN 事件,所以 mFirstTouchTarget 不为 null 。在第一个 MOVE 事件来临的时候,代码执行到了 代码① ,此时 onInterceptTouchEvent 返回 true,所以 intercepted 被赋值为 true。然后代码执行到了 代码⑦ 。intercepted 为true, cancelChild 自然也就为 true。通过 dispatchTransformedTouchEvent 源码可知,cancel 为 true 时,就设置了一个 CANCEL 事件给 child。在 代码⑧ 中,由于此时是单点触摸,mFirstTouchTarget 指向的链表只有绑定这个 View 的TouchTarget对象,next 为 null,所以 mFirstTouchTarget 赋值为 null。如此,child 在父控件拦截的时候就收到了一个 CANCEL 事件。
第二个 MOVE 事件:由于此时 mFirstTouchTarget 为null,就直接调用了 代码⑥ ,通过 dispatchTransformedTouchEvent 调用了自身的 onTouchEvent 方法。
也可以看出,一旦 ViewGroup 拦截之后, mFirstTouchTarget 重置为 null,后续事件来的时候直接将 intercepted 设置为 true,而不再调用 onInterceptTouchEvent 方法。
-
问题二:关于 requestDisallowInterceptTouchEvent
requestDisallowInterceptTouchEvent 本身是 ViewParent 中的方法,ViewParent 是一个接口,ViewGroup 实现了它。我们子类 View 中通过调用
getParent().requestDisallowInterceptTouchEvent(true);
来达到请求父控件不拦截。//ViewGroup 类 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) { // We're already in this state, assume our ancestors are too return; } //给 mGroupFlags 设置标识。 if (disallowIntercept) { mGroupFlags |= FLAG_DISALLOW_INTERCEPT; } else { mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; } // Pass it up to our parent if (mParent != null) { mParent.requestDisallowInterceptTouchEvent(disallowIntercept); } } 复制代码
在 代码块① 中,可以看到先通过
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
来进行判断子类 View 是否设置了不允许拦截标识,然后再对自身是否拦截进行判断。如果不允许拦截将 intercepted 设置为 false。在 DOWN 事件中,ViewGroup 会先重置 mGroupFlags 标识。所以并不是说子 View 在请求一次不拦截后,父控件就永远不拦截。
private void resetTouchState() { clearTouchTargets(); resetCancelNextUpFlag(this); //重置 mGroupFlags mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; mNestedScrollAxes = SCROLL_AXIS_NONE; } 复制代码
View 的 dispatchTouchEvent 方法
//View 类 public boolean dispatchTouchEvent(MotionEvent event) { boolean result = false; if (onFilterTouchEventForSecurity(event)) { if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) { result = true; } //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { //如果设置了 mOnTouchListener ,并且 onTouch 返回 true //就直接 返回结果 true result = true; } //否则,调用 View 的 onTouchEvent 方法 if (!result && onTouchEvent(event)) { result = true; } } return result; } 复制代码
View 的 onTouchEvent 方法。
public boolean onTouchEvent(MotionEvent event) { //------代码省略------- final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE; if ((viewFlags & ENABLED_MASK) == DISABLED) { if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; //一个不可用的 View 仍然是可以消费这个事件的,只是没有响应而已。 //根据 clickable 的值来决定是否消费这个事件。 return clickable; } //是否设置了触摸代理 (如果尝试扩大 View 的 touch 范围,可以使用触摸代理) if (mTouchDelegate != null) { //如果设置了触摸代理,就将事件 event 传递个触摸代理的 onTouchEvent 方法来处理。 if (mTouchDelegate.onTouchEvent(event)) { return true; } } //如果 clickable 为 true ,就执行下面的代码,并返回 true 消费事件。 if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) { switch (action) { case MotionEvent.ACTION_UP: //------代码省略------- break; case MotionEvent.ACTION_DOWN: //------代码省略------- break; case MotionEvent.ACTION_CANCEL: //------代码省略------- break; case MotionEvent.ACTION_MOVE: //------代码省略------- break; } return true; } return false; } 复制代码
可以看出,View 的 dispatchTouchEvent 中,首先进行判断是否设置了 mOnTouchListener ,如果设置了 mOnTouchListener 并且 mOnTouchListener.onTouch 返回 true,则 View 的 dispatchTouchEvent 方法直接返回 true, 不会调用 View 的 onTouchEvent 方法。否则调用 View 的 onTouchEvent 方法。
在 View 的 onTouchEvent 方法中,我们可以看到如果设置了点击事件和长按事件,那么 onTouchEvent 一定会返回为 true了,也就是消费了事件。最终,事件走到这里就算结束了。
由于 OnLongClickListener 和 OnClickListener 的回调在 onTouchEvent 方法中调用,所以 当我们设置了 setOnTouchListener 并且 onTouch 返回 true 的时候,setOnLongClickListener 和 setOnClickListener 是不会被回调的。
View 的 TouchEvent 详细流程: View的事件分发(三)源码分析
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Android事件分发源码归纳
- View的事件分发(三)源码分析
- Android事件分发机制[-View-] 源码级
- 基于源码分析 Android View 事件分发机制
- Laravel Queue——消息队列任务与分发源码剖析
- Android读书笔记--从源码角度剖析View事件分发机制
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
JS 压缩/解压工具
在线压缩/解压 JS 代码
Markdown 在线编辑器
Markdown 在线编辑器