ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
原文参考[这里](http://blog.csdn.net/zizidemenghanxiao/article/details/50184295) 顶级View对点击事件的分发过程: ~~~ /** * {@inheritDoc} */ @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. 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. /* * 至少我知道在这个函数中最终将mFirstTouchTarget设为null。 * mFirstTouchTarget代表的就是一个事件序列中第一个拦截的对象, * 所以这里需要重置。 * */ cancelAndClearTouchTargets(ev); /* * 如果事件是ACTION_DOWN, * ViewGroup就会在resetTouchState中重置下面的FLAG_DISALLOW_INTERCEPT标志位。 * 重置的方式是这样的:mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; * */ resetTouchState(); } // Check for interception. /* * 这个标识很重要,因为它一旦被标志位true,意味着下面的各种if语句都进不去了, * 意味着本ViewGroup拦截了该事件,并且后续的事件序列直接由该ViewGroup处理, * 而不是进入各种if中判断是否需要拦截。 * */ final boolean intercepted;// 拦截标识 /* * 这个if中需要满足两个条件: * (1)actionMasked == MotionEvent.ACTION_DOWN: * 该事件是否为点击下按事件时成立,就是说新的一轮事件到来 * (2)mFirstTouchTarget != null: * 当ViewGroup不拦截事件并将事件交给子元素处理时,成立,mFirstTouchTarget指向这个子元素。 * 而且在ViewGroup中,默认onInterceptTouchEvent返回false,它是不拦截任何事件的, * 但是在LinearLayout中可能就会拦截啊,可以改写啊。 * 而且,当第二个条件成立时,此时发生的事件序列就是ACTION_MOVE或者ACTION_UP,都会进入到这个if语句中。 * */ /* * 所以说呢,当子元素成功拦截了事件或者下按事件发生的时候就会进入if语句。 * 所以说呢,如果子元素没有处理,并且是move和up发生的时候就无法进入该if语句。 * 但为什么这样设定呢,因为如果子元素没有处理的话,事件序列中的其他事件就会直接由ViewGroup来处理了, * 不需要来这里来判断一下到底要不要拦截事件了。那如果是move和up也是同样的,不需要来这里来判断要不要拦截事件。 * */ /* * 也就相当于说,一个事件,第一次因为ACTION_DOWN进入这里,然后ViewGroup判断是否来拦截。 * 之后在子元素成功处理后,因为子元素是可以通过FLAG_DISALLOW_INTERCEPT标志位来干预父元素的事件分发过程,所以又来这里来要看是否拦截。 * */ /* * 为什么总说一旦父元素拦截ACTION_DOWN以后其他的事件序列就只能由父元素来处理呢? * 是因为如果父元素拦截了ACTION_DOWN,那么mFirstTouchTarget == null * 当ACTION_MOVE和ACTION_UP到来的时候,这条if语句就不会进入了, * 然后intercepted = true;表示事件序列由父元素全拦截了。 * */ if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { /* * 通常事件传递过程是由外向内的, * 但是通过 requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素的事件分发过程, * 不过ACTION_DOWN事件除外。 * 干预表现在子元素已经拦截了事件, * 但是可以通过requestDisallowInterceptTouchEvent来控制 * ACTION_MOVE和ACTION_UP能不能够进入到这里来。 * */ /* * FLAG_DISALLOW_INTERCEPT一旦设置后,ViewGroup将无法拦截处理ACTION_DOWN以外的其他点击事件了。 * 因为在事件分发时,ACTION_DOWN会重置FLAG_DISALLOW_INTERCEPT标志位,表示另一次事件开始。 * */ /* * 子View干涉ViewGroup的过程: * 初始化:mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; * 在子View中FLAG_DISALLOW_INTERCEPT被重置,也就是要去干扰, * 然后mGroupFlags & FLAG_DISALLOW_INTERCEPT为1 * 然后(mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0 为true * 然后disallowIntercept为true * 然后导致if (!disallowIntercept)无法进入。 * */ /* * FLAG_DISALLOW_INTERCEPT标志位有什么用呢? * 当面对滑动冲突时,我们可以考虑用这种方法去解决问题。 * */ final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { /* * 所以说onInterceptTouchEvent并不是每次事件都会被调用的。 * 而dispatchTouchEvent却会在每次都调用。 * 对于原始的ViewGroup,onInterceptTouchEvent会返回false, * 但是对于你自己写的LinearLayout,则可以修改这个函数, * 让它对ACTION_DOWN、ACTION_MOVE、ACTION_UP做出不同的选择。 * */ intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { intercepted = false; } } else { // There are no touch targets and this action is not an initial down // so this view group continues to intercept touches. /* * 就是说没有子元素mFirstTouchTarget,而且事件也不是ACTION_DOWN, * 没人管那就只能自己拦截了。 * */ intercepted = true; } // 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; /* * 当ViewGroup不拦截事件的时候,intercepted=false,事件会向下分发由它的子View进行处理 * 所以说一旦ViewGroup拦截了事件,intercepted=true, * 意味着事件序列中的任何事件都不再会传给子元素了,由父元素全权处理。 * 所以intercepted=true一定要谨慎设置。 * */ if (!canceled && !intercepted) { 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 View[] children = mChildren; final boolean customOrder = isChildrenDrawingOrderEnabled(); /* * 遍历ViewGroup的所有子元素,判断子元素是否能够接收到点击事件。 * */ for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; final View child = children[childIndex]; /* * 判断子元素是否能够接收到点击事件: * (1)canViewReceivePointerEvents:子元素是否在播动画。 * (2)isTransformedTouchPointInView:点击事件的坐标是否落在子元素的区域内。 * */ if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { continue; } /* * 如果上面那个if语句没有成立,说明这个子元素是可以拦截事件的, * 所以新的TouchTarget出现了,就是这个子元素。 * */ 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实际上调用的就是子元素的dispatchTouchEvent方法。 * 下面的第三个参数中child一定不为null,所以child的dispatchTouchEvent一定会被调用。 * 子元素的dispatchTouchEvent返回true, * 意味着dispatchTransformedTouchEvent也返回ture, * 表示事件被子元素分发成功,并break跳出循环。 * */ 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(); /* * 分发成功后,在addTouchTarget会对mFirstTouchTarget进行赋值 * */ newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; /* * 分发成功,跳出循环 * */ break; } } } 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; } } } /* * 有两种情况遍历所有的子元素后事件也没有处理: * (1)ViewGroup根本没有子元素 * (2)子元素的dispatchTouchEvent都返回了false。 * 这种情况下只能ViewGroup自己来处理事件了。 * */ // Dispatch to touch targets. if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. /* * 注意第三个参数:null,在上面变量子元素的时候这里放的是child。 * 如果是null,dispatchTransformedTouchEvent内部就会调用: * super.dispatchTouchEvent(event); * 很显然,这里就转到了View的dispatchTouchEvent(event)方法,即点击事件开始交由View来处理。在View中有onTouchEvent。 * 其实父元素ViewGroup的onTouchEvent就是指的是View中的onTouchEvent方法,它自己这里是没有的。因为ViewGroup是继承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) { handled = true; } else { 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; } } // 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; } ~~~ 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 /* * 首先会判断有没有设置OnTouchListener。 * 如果OnTouchListener中的onTouch方法返回true,那么onTouchEvent方法就不会调用, * 这样做的好处是方便外界处理点击事件。 * */ ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { return true; } /* * 优先级低于OnTouchListener * */ if (onTouchEvent(event)) { return true; } } if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } return false; } ~~~ * View源码中的onTouchEvent方法进行分析 ~~~ /** * Implement this method to handle touch screen motion events. * <p> * If this method is used to detect click actions, it is recommended that * the actions be performed by implementing and calling * {@link #performClick()}. This will ensure consistent system behavior, * including: * <ul> * <li>obeying click sound preferences * <li>dispatching OnClickListener calls * <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when * accessibility features are enabled * </ul> * * @param event The motion event. * @return True if the event was handled, false otherwise. */ public boolean onTouchEvent(MotionEvent event) { final int viewFlags = mViewFlags; /* * 当View处于不可用状态下时,View照样会消耗点击事, * 但它并不对事件做出任何的反映 * */ if ((viewFlags & ENABLED_MASK) == DISABLED) { if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); } /* * 如果View设置有代理,那么还会执行mTouchDelegate的onTouchEvent方法, * 这个onTouchEvent的工作机制看起来和OnTouchListener类似,这里我们不做研究 * */ if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } /* * 这里是对点击事件的具体处理。 * 可以发现的是View的CLICKABLE和LONG_CLICKABLE只要有一个为true, * 那么这个View就消耗这个事件,即onTouchEvent返回ture,不管他是不是DISABLE状态。 * 这个证明了前面(8)(9)(10)的结论。 * */ if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { switch (event.getAction()) { /* * 当up事件发生时,就会触发performClick()方法。 * */ case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { // take focus if we don't have it already and we should in // touch mode. boolean focusTaken = false; if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { focusTaken = requestFocus(); } if (prepressed) { // The button is being released before we actually // showed it as pressed. Make it show the pressed // state now (before scheduling the click) to ensure // the user sees it. setPressed(true); } if (!mHasPerformedLongPress) { // This is a tap, so remove the longpress check removeLongPressCallback(); // Only perform take click actions if we were in the pressed state if (!focusTaken) { // Use a Runnable and post this rather than calling // performClick directly. This lets other visual state // of the view update before click actions start. if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { /* * 如果View设置了OnClickListener, * 那么performClick()方法内部会调用它的onClick方法 * */ performClick(); } } } if (mUnsetPressedState == null) { mUnsetPressedState = new UnsetPressedState(); } if (prepressed) { postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (!post(mUnsetPressedState)) { // If the post failed, unpress right now mUnsetPressedState.run(); } removeTapCallback(); } break; case MotionEvent.ACTION_DOWN: mHasPerformedLongPress = false; if (performButtonActionOnTouchDown(event)) { break; } // Walk up the hierarchy to determine if we're inside a scrolling container. boolean isInScrollingContainer = isInScrollingContainer(); // For views inside a scrolling container, delay the pressed feedback for // a short period in case this is a scroll. if (isInScrollingContainer) { mPrivateFlags |= PFLAG_PREPRESSED; if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } else { // Not inside a scrolling container, so show the feedback right away setPressed(true); checkForLongClick(0); } break; case MotionEvent.ACTION_CANCEL: setPressed(false); removeTapCallback(); removeLongPressCallback(); break; case MotionEvent.ACTION_MOVE: final int x = (int) event.getX(); final int y = (int) event.getY(); // Be lenient about moving outside of buttons if (!pointInView(x, y, mTouchSlop)) { // Outside button removeTapCallback(); if ((mPrivateFlags & PFLAG_PRESSED) != 0) { // Remove any future long press/tap checks removeLongPressCallback(); setPressed(false); } } break; } return true; } return false; } ~~~