定义:当用户触摸屏幕时,将产生的触摸行为(Touch事件)
事件类型
MotionEvent.ACTION_DOWN 手指刚接触屏幕MotionEvent.ACTION_MOVE 手指在屏幕上滑动MotionEvent.ACTION_UP 手指从屏幕上松开MotionEvent.ACTION_CANCEL 非人为因素取消正常情况下一次手指触摸屏幕的行为会触发一系列事件
点击屏幕后立即松开,事件序列为DOWN -> UP点击屏幕滑动一段距离后松开,事件序列为DOWN -> MOVE -> … -> MOVE -> UP事件序列流程图如下
Activity:控制生命周期&处理事件
ViewGroup:一组View的集合
View:所有UI组件的基类
dispatchTouchEvent(MotionEvent ev):用来进行事件分发
onInterceptTouchEvent(MotionEvent ev):判断是否拦截事件(ViewGroup)
onTouchEvent(MotionEvent ev)::处理触摸事件
按照惯例,为了方便大家理解源码,我们先来看看整个事件分发的流程图
Activity是最先得到用户触摸事件的,首先我们来看Activity#dispatchTouch
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }这里有个判断这个触摸事件是不是DOWN事件,如果是则调用onUserInteraction(),这个方法是一个空方法,我们继续往下看,getWindow()会返回一个PhoneWindow对象,接着调用PhoneWindow#superDispatchTouchEvent,如果返回true则事件结束,否则表示事件没有被消费,则 调用Activity#onTouchEvent来处理事件,继续跟进PhoneWindow#superDispatchTouchEvent
@Override public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event); }mDecor是顶层布局容器DecorView,继续跟进DecorView#superDispatchTouchEvent
public boolean superDispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); }可以看到DecorView中是直接调用了 super.dispatchTouchEvent(event),因为DecorView继承自FrameLayout,所以最后会调用到ViewGroup#sdispatchTouchEvent(event),我们来总结下Activity的事件分发:Activity->PhoneWindow->DecorView
现在事件已经从Activity分发到了ViewGroup,我们接着来看ViewGroup的事件分发 ViewGroup#dispatchTouchEvent
@Override public boolean dispatchTouchEvent(MotionEvent ev) { ... boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; // Handle an initial down. // --------注释1-------- 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(); } // --------注释2-------- // Check for interception. final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { 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 { // There are no touch targets and this action is not an initial down // so this view group continues to intercept touches. intercepted = true; } ... // --------注释3 -------- 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; // 对子View倒叙遍历 for (int i = childrenCount - 1; i >= 0; i--) { 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; } // --------注释4 -------- if (!child.canReceivePointerEvents() || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; } 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); // --------注释5 -------- 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(); // --------注释6 -------- newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } // The accessibility focus didn't handle the event, so clear // the flag and do a normal dispatch to all children. ev.setTargetAccessibilityFocus(false); } if (preorderedList != null) preorderedList.clear(); } ... // --------注释7 -------- // Dispatch to touch targets. if (mFirstTouchTarget == null) { // 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; // --------注释8 -------- 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. ... return handled; }看到注释1处,如果是一个DOWN事件,表明是一个新的事件的开始,会调用两个方法进行一些标记的清除,cancelAndClearTouchTargets(ev)主要的作用是将全局变量mTouchTarget置null,resetTouchState()主要作用是将全局变量mGroupFlags清除,接下来继续往下看注释2处的代码,这块代码主要是用来检测是否需要对事件进行拦截,必须是DOWNS事件或者mTouchTarget不为null才会可能去执行onInterceptTouchEvent(ev),否则拦截标记intercepted直接赋值为true,然后继续根据mGroupFlags是不是设置为FLAG_DISALLOW_INTERCEPT,是的话表明不拦截该事件,拦截标记intercepted赋值为false,否则执行onInterceptTouchEvent(ev),当子View调用requestDisallowInterceptTouchEvent()方法来请求父ViewGroup不拦截事件时会给mGroupFlags赋值为FLAG_DISALLOW_INTERCEPT
@Override 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); } }在DOWN事件时,已经对mTuchTarget置null,mGroupFlags清除,导致在DOWN事件intercepted赋值为true,表示拦截该事件,所以requestDisallowInterceptTouchEvent只能作用DOWN事件之后的事件(MOVE、UP)。接着看注释3处,在DOWN事件时,如果newTouchTarget == null && childrenCount != 0,则直接对子View进行倒叙遍历,继续往下看注释4处child.canReceivePointerEvents()和isTransformedTouchPointInView(),先看View#canReceivePointerEvents
protected boolean canReceivePointerEvents() { return (mViewFlags & VISIBILITY_MASK) == VISIBLE || getAnimation() != null; }这个方法表示View可见或者正在执行动画,接着看 MotionEvent#isTransformedTouchPointInView
protected boolean isTransformedTouchPointInView(float x, float y, View child, PointF outLocalPoint) { final float[] point = getTempPoint(); point[0] = x; point[1] = y; transformPointToViewLocal(point, child); final boolean isInView = child.pointInView(point[0], point[1]); if (isInView && outLocalPoint != null) { outLocalPoint.set(point[0], point[1]); } return isInView; }这个方法主要作用是判断触摸区域是否在View的区域内 ,回到注释4处,这个条件判断表示如果当前View不可见并且没有在执行动画,或者触摸区域不在View的区域内,则直接continue,进行下次遍历。继续看到注释5处,如果找到了触摸的View,调用dispatchTransformedTouchEvent并将这个View传进去,跟进ViewGroup#dispatchTransformedTouchEvent
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; ... 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); } ... // Done. transformedEvent.recycle(); return handled; }如果child不为空,则调用child.dispatchTouchEvent(event)将事件传递给child,如果child为null,则直接调用super.dispatchTouchEvent(event) ,此时如果 child 为View,则直接调用View的dispatchTouchEvent(event)处理事件,如果child 为ViewGroup,则事件最终会来到ViewGroup的onTouchEvent()方法 ,回到注释5处条件判断,如果dispatchTransformedTouchEvent返回true, 表示子View消费了事件,接着调用注释6处的addTouchTarget(child, idBitsToAssign),ViewGroup#addTouchTarget
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) { final TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; mFirstTouchTarget = target; return target; }这里对mFirstTouchTarget赋值为消费了事件的子View的TouchTarget,回到调用该方法的注释6处,将消费了事件的子View的TouchTarget赋值给newTouchTarget,并将alreadyDispatchedToNewTouchTarget设为true
我们先来总结下到目前为止ViewGroup#dispatchTouchEvent所做的事情:在DOWN事件时,遍历所有子View,找到消费事件的子View,并将子View赋值给mFirstTouchTarget,即mFirstTouchTarget指向了消费事件的子View,如果没有子View消费事件,则 mFirstTouchTarget依旧为null
继续看到ViewGroup#dispatchTouchEvent中注释7处,如果mFirstTouchTarget为null,表明没有子View消费事件,则调用dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS),并将第三个参数child传null,上面我们已经分析了,如果child为null,则直接调用super.dispatchTouchEvent(event) ,此时如果 child 为View,则直接调用View的dispatchTouchEvent(event)处理事件,如果child 为ViewGroup,则事件最终会来到ViewGroup的onTouchEvent()方法
继续看注释8处,如果mFirstTouchTarget不为null,表示DOWN事件已经找到了一个子View来消费事件,条件判断(alreadyDispatchedToNewTouchTarget && target == newTouchTarget)当找到消费事件的子View时值为true,然后直接给handles赋值为true,DOWN事件结束,如果上面条件判断为false,则else里面的代码是对MOVE和UP事件的处理,继续调用dispatchTransformedTouchEvent将MOVE和UP事件直接分发给消费了DOWN事件的 子View
我们来简单总结下ViewGroup#dispatchTouchEvent所做的全部事情:ViewGroup中可以通过onInterceptTouchEvent()对事件进行拦截,返回true表示拦截,返回false表示不拦截,子View可以调用getParent().requestDisallowInterceptTouchEvent(true)来请求父ViewGroup不拦截事件(只对MOVE和UP事件生效),接着遍历子View,找到消费了DOWN事件的子View,并将后续MOVE和UP事件直接分发给消费了DOWN事件的子View,若没有子View消费DOWN事件,则会调用View的dispatchTouchEvent,若返回true,则会调用此ViewGroup的onTouchEvent将事件交给自己的onTouchEvent处理,后续的MOVE和UP事件将不再向下分发,直接交给ViewGroup#onTouchEvent处理
现在事件已经从ViewGroup分发到了View,我们接着来看View的事件分发 View#dispatchTouchEvent
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; // --------注释9 -------- if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } if (!result && onTouchEvent(event)) { result = true; } } if (!result && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } // Clean up after nested scrolls if this is the end of a gesture; // also cancel it if we tried an ACTION_DOWN but we didn't want the rest ... return result; }从代码中可以看出,如果View正在被拖拽,则直接消费掉事件,mListenerInfo是ListenerInfo的对象,在给View设置一些监听的时候贵初始化mListenerInfo,看到注释9处的条件判断,View默认就是enable的,所以只要设置了OnTouchListener,会调用OnTouchListener#onTouch方法,返回true,则事件被消费,返回false,则继续执行onTouchEvent(event)方法,onTouchEvent返回true,则事件被消费,继续跟进View#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(); // --------注释10 -------- 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; // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return clickable; } if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) { switch (action) { case MotionEvent.ACTION_UP: mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; if ((viewFlags & TOOLTIP) == TOOLTIP) { handleTooltipUp(); } if (!clickable) { removeTapCallback(); removeLongPressCallback(); mInContextButtonPress = false; mHasPerformedLongPress = false; mIgnoreNextUpEvent = false; break; } 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, x, y); } // --------注释12 -------- if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) { // 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)) { performClickInternal(); } } } 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(); } mIgnoreNextUpEvent = false; break; case MotionEvent.ACTION_DOWN: if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) { mPrivateFlags3 |= PFLAG3_FINGER_DOWN; } mHasPerformedLongPress = false; if (!clickable) { checkForLongClick(0, x, y); break; } 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(); } mPendingCheckForTap.x = event.getX(); mPendingCheckForTap.y = event.getY(); // --------注释11 -------- postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } else { // Not inside a scrolling container, so show the feedback right away setPressed(true, x, y); checkForLongClick(0, x, y); } break; case MotionEvent.ACTION_CANCEL: if (clickable) { setPressed(false); } removeTapCallback(); removeLongPressCallback(); mInContextButtonPress = false; mHasPerformedLongPress = false; mIgnoreNextUpEvent = false; mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; break; case MotionEvent.ACTION_MOVE: if (clickable) { drawableHotspotChanged(x, y); } // Be lenient about moving outside of buttons if (!pointInView(x, y, mTouchSlop)) { // Outside button // Remove any future long press/tap checks removeTapCallback(); removeLongPressCallback(); if ((mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; } break; } // --------注释13 -------- return true; } return false; }看到注释10处,只要View是可点击的或者可长按的clickable就为true,并且View即使设置为DISABLED,也不会对clickable产生影响 ,依旧返回clickable,如果clickable为true,则说明事件被消费了,只不过对这个事件没有响应,看到注释13处直接返回clickable,表明如果View是可以点击的,那么直接消费掉事件,我们继续往下看在DOWN事件中给mHasPerformedLongPress标记位设为false,表示目前还没有处理长按事件,接着继续判断当View是在可滑动容器时,注释11处发送一个延迟100ms的mPendingCheckForTap任务来检查是否是长按事件,ViewConfiguration.getTapTimeout()返回的是100ms,CheckForTap代码如下
private final class CheckForTap implements Runnable { public float x; public float y; @Override public void run() { mPrivateFlags &= ~PFLAG_PREPRESSED; setPressed(true, x, y); checkForLongClick(ViewConfiguration.getTapTimeout(), x, y); } }如果View不是在可滑动容器里面,则直接调用checkForLongClick方法来检查长按事件,跟进View#checkForLongClick
private void checkForLongClick(int delayOffset, float x, float y) { if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) { mHasPerformedLongPress = false; if (mPendingCheckForLongPress == null) { mPendingCheckForLongPress = new CheckForLongPress(); } mPendingCheckForLongPress.setAnchor(x, y); mPendingCheckForLongPress.rememberWindowAttachCount(); mPendingCheckForLongPress.rememberPressedState(); postDelayed(mPendingCheckForLongPress, ViewConfiguration.getLongPressTimeout() - delayOffset); } }注意这里mHasPerformedLongPress也被赋值false,ViewConfiguration.getLongPressTimeout()返回的是500ms,这段代码又发送了一个500ms的延迟任务mPendingCheckForLongPress来表示长按事件,看到UP事件里面的注释12处,大家应该还记得之前在DOWN事件中和checkForLongClick方法中我们将mHasPerformedLongPress赋值为false,如果在UP事件时mHasPerformedLongPress依然为false,则表明没有长按事件,调用removeLongPressCallback()移除长按事件回调,继续回到checkForLongClick,栏看下mPendingCheckForLongPress的run方法
private final class CheckForLongPress implements Runnable { private int mOriginalWindowAttachCount; private float mX; private float mY; private boolean mOriginalPressedState; @Override public void run() { if ((mOriginalPressedState == isPressed()) && (mParent != null) && mOriginalWindowAttachCount == mWindowAttachCount) { if (performLongClick(mX, mY)) { mHasPerformedLongPress = true; } } } public void setAnchor(float x, float y) { mX = x; mY = y; } public void rememberWindowAttachCount() { mOriginalWindowAttachCount = mWindowAttachCount; } public void rememberPressedState() { mOriginalPressedState = isPressed(); } }可以看到run方法里面调用了performLongClick(mX, mY),当返回值为true时,将mHasPerformedLongPress标记设为true,所以若从DOWN事件到UP时间超过了500ms,则认为事件是长按事件,否则是点击事件,继续跟进performLongClick方法发现最终会调用performLongClickInternal(float x, float y),并且返回performLongClickInternal(float x, float y)的返回值,继续跟进 View#performLongClickInternal
private boolean performLongClickInternal(float x, float y) { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); boolean handled = false; final ListenerInfo li = mListenerInfo; if (li != null && li.mOnLongClickListener != null) { handled = li.mOnLongClickListener.onLongClick(View.this); } if (!handled) { final boolean isAnchored = !Float.isNaN(x) && !Float.isNaN(y); handled = isAnchored ? showContextMenu(x, y) : showContextMenu(); } if ((mViewFlags & TOOLTIP) == TOOLTIP) { if (!handled) { handled = showLongClickTooltip((int) x, (int) y); } } if (handled) { performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); } return handled; }可以看到这里调用了mOnLongClickListener.onLongClick回调,就是我们代码中设置长按事件的回调,如果我们在onLongClick回调中返回true,则mHasPerformedLongPress标记设为true,在onTouchEvent方法的UP事件中就会判定该事件为长按事件,来看到这段代码
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) { // 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)) { performClickInternal(); } } }如果mHasPerformedLongPress标记为true,则不会执行条件里面的 post(mPerformClick),跟进PerformClick
private final class PerformClick implements Runnable { @Override public void run() { performClickInternal(); } }run方法中调用了performClickInternal()方法,跟进performClickInternal()发现继续调用了performClick(),跟进View#performClick
public boolean performClick() { // We still need to call this method to handle the cases where performClick() was called // externally, instead of through performClickInternal() notifyAutofillManagerOnClick(); final boolean result; final ListenerInfo li = mListenerInfo; if (li != null && li.mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this); result = true; } else { result = false; } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); notifyEnterOrExitForAutoFillIfNeeded(true); return result; }可以看到其中调用了mOnClickListener.onClick(this),就是我们在代码中设置的点击监听回调,到此事件分发就分析完毕了。
Activity事件分发
Activity#dispatchTouchEventPhoneWindow#superDispatchTouchEventDecorView#superDispatchTouchEventViewGroup#dispatchTouchEvent若事件没有被消费,则最终调用Activity#onTouchEventViewGroup事件分发
ViewGroup#dispatchTouchEvent(对down事件特殊处理:cancelAndClearTouchTargets(),resetTouchState(),清除标记后down事件一定会执行onInterceptTouchEvent(),onInterceptTouchEvent()默认返回false,不拦截)若没有拦截,ViewGroup#dispatchTansformedTouchEvent,for循环遍历子View,调用子view的dispatchTouchEvent,若没有子view消费事件,则直接调用super.dispatchTouchEvent(event) ,此时如果 child 为View,则直接调用View的dispatchTouchEvent(event)处理事件,如果child 为ViewGroup,则事件最终会来到ViewGroup的onTouchEvent()方法若有子view消费事件,则继续将down事件后续的move和up事件分发给子view处理,ViewGroup不做处理若没有view消费事件,则down事件后续的move和up事件不再向下分发,直接交给ViewGroup#onTouchEvent处理子view可以调用getParent().requestDisalowInterceptTouchEvent()请求ViewGroup不拦截事件(只对MOVE和UP事件有效)View事件分发
View#dispatchTouchEvent(若view被设置了监听器,则会先调用监听器的 onTouch()方法,若onTouch()方法返回true,则事件被消费,不会调用onTouchEvent(), 若返回false,则会继续调用onTouchEvent() )onTouch()返回false,View#onTouchEvent(若view是可以被点击的(clickable == true),则直接返回true消费掉事件),在down事件里面会做一个长按事件的检测 ,在up事件中检测没有长按事件则移除长按事件回调并响应点击事件,如果设置了长按事件并且长按事件的onLongClick()返回为true,则不执行onClick(),若onLongClick()返回为false,则会继续执行onClick()事件行先后顺序:dispatchTouchEvent-> onTouch -> onTouchEvent -> onLongClick() -> onClick()