源码基于 sdk 30(Android 11.0/R)。
Android 事件机制包含系统启动流程、输入管理(InputManager)、系统服务和 UI 的通信(WindowManagerService + ViewRootImpl + Window)、事件分发等一系列的环节。
Android 系统中将输入事件定义为 InputEvent,根据输入事件的类型又分为了 KeyEvent(键盘事件) 和 MotionEvent(屏幕触摸事件)。这些事件统一由系统输入管理器 InputManager 进行分发。
在系统启动的时候,SystemServer 会启动 WindowManagerService,WMS 在启动的时候通过 InputManager 来负责监控键盘消息。
InputManager 负责从硬件接收输入事件,并将事件通过 ViewRootImpl 分发给当前激活的窗口处理,进而分发给 View。
Window 和 InputManagerService 之间通过 InputChannel 来通信,底层通过 socket 进行通信。
Android Touch 事件的基础知识:
所有的 Touch 事件都封装到 MotionEvent 里面。
事件类型分为 ACTION_DOWN(手指按下屏幕的瞬间), ACTION_MOVE(手指在屏幕上移动),ACTION_UP(手指离开屏幕瞬间), ACTION_CANCEL(取消手势,一般由程序产生,不会由用户产生)等。每个事件传递过程都是由一个 ACTION_DOWN 开始,经过 n 个 ACTION_MOVE,最终以一个 ACTION_UP/ACTION_CANCEL 结束。
事件处理包括三种情况,分别为:
传递 View#dispatchTouchEvent(MotionEvent event)
拦截 ViewGroup#onInterceptTouchEvent(MotionEvent ev)
消费 View#OnTouchListener.onTouch(View v, MotionEvent event) 和 View#onTouchEvent(MotionEvent event)。
所有的触摸事件都会到达 Activity,Activity 内部通过 Window 连接着一个 View 的根布局 DecorView,事件会从 Activity 一层一层传递到最内层的 View。
事件架构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public abstract class InputEvent implements Parcelable {}public class KeyEvent extends InputEvent implements Parcelable {}public final class MotionEvent extends InputEvent implements Parcelable {}
KeyEvent 对应了键盘的输入事件;MotionEvent 就是手势事件,鼠标、笔、手指、轨迹球等相关输入设备的事件都属于 MotionEvent。
InputEvent 统一由 InputManager 进行分发,负责与硬件通信并接收输入事件。
system_server 进程启动时会创建 InputManagerService 服务。
1 2 3 4 5 6 7 8 private void startOtherServices (@NonNull TimingsTraceAndSlog t) { InputManagerService inputManager = new InputManagerService (context); WindowManagerService wm = WindowManagerService.main(context, inputManager, ...); inputManager.setWindowManagerCallbacks(wm.getInputManagerCallback()); inputManager.start(); }
1 2 3 4 5 6 7 8 9 10 public class InputManagerService extends IInputManager .Stub { public InputManagerService (Context context) { this .mContext = context; this .mHandler = new InputManagerHandler (DisplayThread.get().getLooper()); mPtr = nativeInit(this , mContext, mHandler.getLooper().getQueue()); } private static native long nativeInit (InputManagerService service, Context context, MessageQueue messageQueue) ;}
system_server 进程启动时同时会启动 WMS,WMS 在启动的时候就会通过 IMS 启动 InputManager 来监控键盘消息。
App 端与服务端建立了双向通信之后,InputManager 就能够将产生的输入事件从底层硬件分发过来,Android 提供了 InputEventReceiver 类,以接收分发这些消息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public abstract class InputEventReceiver { public InputEventReceiver (InputChannel inputChannel, Looper looper) { mInputChannel = inputChannel; mMessageQueue = looper.getQueue(); mReceiverPtr = nativeInit(new WeakReference <InputEventReceiver>(this ), inputChannel, mMessageQueue); } private void dispatchInputEvent (int seq, InputEvent event) { mSeqMap.put(event.getSequenceNumber(), seq); onInputEvent(event); } public void onInputEvent (InputEvent event) { finishInputEvent(event, false ); } public final void finishInputEvent (InputEvent event, boolean handled) { nativeFinishInputEvent(mReceiverPtr, seq, handled); event.recycleIfNeededAfterDispatch(); } }
1 2 3 4 5 6 7 8 final class WindowInputEventReceiver extends InputEventReceiver { @Override public void onInputEvent (InputEvent event) { enqueueInputEvent(event, this , 0 , true ); } }
Window 和 IMS 之间通过 InputChannel 通信。InputChannel 是一个 pipe,底层通过 socket 进行通信。在 ViewRootImpl.setView() 过程中注册 InputChannel。
1 2 3 4 5 6 public final class InputChannel implements Parcelable {}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public void setView () { requestLayout(); InputChannel inputChannel = new InputChannel (); res = mWindowSession.addToDisplayAsUser(mWindow, inputChannel, ...); if (mInputQueueCallback != null ) { mInputQueue = new InputQueue (); mInputQueueCallback.onInputQueueCreated(mInputQueue); } mInputEventReceiver = new WindowInputEventReceiver (inputChannel, Looper.myLooper()); mSyntheticInputStage = new SyntheticInputStage (); InputStage viewPostImeStage = new ViewPostImeInputStage (mSyntheticInputStage); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 public class ViewRootImpl { abstract class InputStage { protected int onProcess (QueuedInputEvent q) { return FORWARD; } } final class ViewPostImeInputStage extends InputStage { @Override protected int onProcess (QueuedInputEvent q) { if (q.mEvent instanceof KeyEvent) { return processKeyEvent(q); } else { final int source = q.mEvent.getSource(); if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0 ) { return processPointerEvent(q); } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0 ) { return processTrackballEvent(q); } else { return processGenericMotionEvent(q); } } } } private int processPointerEvent (QueuedInputEvent q) { final MotionEvent event = (MotionEvent)q.mEvent; boolean handled = mView.dispatchPointerEvent(event); return handled ? FINISH_HANDLED : FORWARD; } } public class DecorView { @Override public boolean dispatchTouchEvent (MotionEvent ev) { final Window.Callback cb = mWindow.getCallback(); return cb != null && !mWindow.isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super .dispatchTouchEvent(ev); } }
ViewPostImeInputStage 是 Touch 事件传递的起点;
Activity 实现了 Window.Callback 接口,DecorView 会把事件分发到 Activity#dispatchTouchEvent(ev) 方法。
事件传递流程 Android 事件传递机制是先分发再处理,先由外部的 View 接收,然后依次传递给其内层的 View,再从最内层 View 反向依次向外层传递。
事件从 Activity#dispatchTouchEvent() 开始传递,再传递给 DecorView;
DecorView 中先执行 dispatchTouchEvent() 方法,在其内部会先调用 onInterceptTouchEvent() 询问是否拦截事件,若拦截则执行 onTouchEvent() 方法处理这个事件;
若不拦截,则执行子 View 的 dispatchTouchEvent() 方法,进入向下分发的传递,直到事件被处理;
如果事件从外向内传递过程中一直没有被拦截,且最底层子 View 没有消费事件,则事件会反向向上传递。这时父 View 可以进行消费,如果还是没有被消费,最后会传递到 Activity#onTouchEvent() 方法;
如果 View 没有对 ACTION_DOWN 进行消费,之后的其它类型的事件也不会传递过来,也就是说 ACTION_DOWN 必须返回 true,之后的事件才会传递进来。
事件处理优先级是OnTouhListener#onTouch(View v, MotionEvent event) -> onTouchEvent() -> OnClickListener#onClick(View v)。
三个方法的关系如下:
1 2 3 4 5 6 7 8 9 public boolean dispatchTouchEvent (MotionEvent event) { boolean consum = false ; if (onInterceptTouchEvent(event)){ consum = onTouchEvent(event); } else { consum = child.dispatchTouchEvent(event); } return consum; }
源码分析 Activity 的事件处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public class Activity { public boolean dispatchTouchEvent (MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true ; } return onTouchEvent(ev); } public boolean onTouchEvent (MotionEvent event) { if (mWindow.shouldCloseOnTouch(this , event)) { finish(); return true ; } return false ; } } public class PhoneWindow extends Window { @Override public boolean superDispatchTouchEvent (MotionEvent event) { return mDecor.superDispatchTouchEvent(event); } } public class Window { public boolean shouldCloseOnTouch (Context context, MotionEvent event) { final boolean isOutside = event.getAction() == MotionEvent.ACTION_UP && isOutOfBounds(context, event) || event.getAction() == MotionEvent.ACTION_OUTSIDE; if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) { return true ; } return false ; } }
Activity 接收到 ACTION_DOWN 事件后,调用 onUserInteraction() 方法,表示和用户进行了交互;
Activity 接收到事件后经 PhoneWindow 传递给 DecorView;
传递给 DecorView 后如果此事件没有被消费,则事件交给 Activity#onTouchEvent() 处理。
ViewGroup 的事件处理 分发事件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 public boolean dispatchTouchEvent (MotionEvent ev) { boolean handled = false ; if (actionMasked == MotionEvent.ACTION_DOWN) { cancelAndClearTouchTargets(ev); resetTouchState(); } 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); } else { intercepted = false ; } } else { intercepted = true ; } final boolean canceled = resetCancelNextUpFlag(this ) || actionMasked == MotionEvent.ACTION_CANCEL; TouchTarget newTouchTarget = null ; boolean alreadyDispatchedToNewTouchTarget = false ; if (!canceled && !intercepted) { if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0 ) { final ArrayList<View> preorderedList = buildTouchDispatchChildList(); final View[] children = mChildren; for (int i = childrenCount - 1 ; i >= 0 ; i--) { final View child = getAndVerifyPreorderedView(...); if (!child.canReceivePointerEvents() || !isTransformedTouchPointInView(x, y, child, null )) { continue ; } newTouchTarget = getTouchTarget(child); if (dispatchTransformedTouchEvent(ev, false , child, idBitsToAssign)) { newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true ; break ; } } if (newTouchTarget == null && mFirstTouchTarget != null ) { } } } if (mFirstTouchTarget == null ) { handled = dispatchTransformedTouchEvent(ev, canceled, null , TouchTarget.ALL_POINTER_IDS); } else { 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; } } } return handled; } 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) { event.setAction(MotionEvent.ACTION_CANCEL); if (child == null ) { handled = super .dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; } if (child == null ) { handled = super .dispatchTouchEvent(transformedEvent); } else { handled = child.dispatchTouchEvent(transformedEvent); } return handled; }
应用了树的深度优先搜索算法(Depth-First-Search,简称 DFS 算法),每个 ViewGroup 都持有一个 mFirstTouchTarget, 当接收到 ACTION_DOWN 时,通过递归遍历找到 View 树中真正对事件进行消费的 Child,并保存在 mFirstTouchTarget 属性中,依此类推组成一个完整的分发链。在这之后,当接收到同一事件序列的其它事件如 ACTION_MOVE、ACTION_UP 时,则会跳过递归流程,将事件直接分发给下一级的 Child。
ViewGroup 分发事件的主要的任务是找一个 Target,并且用这个 Target 处理事件,主要逻辑如下 :
在 ACTION_DOWN 事件并且当前 ViewGroup 不拦截时会查找 TouchTarget。按 View 添加顺序倒序遍历子 View,判断子 View 的 dispatchTouchEvent() 是否为 true。查找 TouchTarget 的过程只有在 ACTION_DOWN 事件中才会触发。
如果子 View 的 dispatchTouchEvent() 返回 true,则这个子 View 就是当前 ViewGroup 的 Target。
如果 TouchTarget 不存在,则调用当前 ViewGroup#onTouchEvent() 方法,仍不消费,会向上调传递直到 Activity#onTouchEvent()。
子 View 可以调用父 View 的 requestDisallowInterceptTouchEvent(true) 请求父 View 不拦截此事件,交给 子 View 处理。只限于一个 Touch 过程(Down->Up/Cancel)。
处理 Cancel 事件,将 View 从链表中删除。
为什么倒序查找 TouchTarget? 如果按添加顺序遍历,当 View 重叠时(FrameLayout),先添加的 View 总是能消费事件,而后添加的 View 不可能获取到事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public boolean dispatchTouchEvent (MotionEvent ev) { for (int i = childrenCount - 1 ; i >= 0 ; i--) { final int childIndex = ...; final View child = ...; if (!child.canReceivePointerEvents() || !isTransformedTouchPointInView(x, y, child, null )) { continue ; } if (dispatchTransformedTouchEvent(ev, false , child, idBitsToAssign)) { newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true ; break ; } } }
拦截事件:
1 2 3 4 5 6 7 8 9 10 public boolean onInterceptTouchEvent (MotionEvent ev) { if (ev.isFromSource(InputDevice.SOURCE_MOUSE) && ev.getAction() == MotionEvent.ACTION_DOWN && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY) && isOnScrollbarThumb(ev.getX(), ev.getY())) { return true ; } return false ; }
默认返回 false,不拦截事件。
ViewGroup 子类可以重写该方法,决定是否拦截事件。
子 View 通过 parentView.requestDisallowInterceptTouchEvent(true) 请求父 View 不拦截此事件。
View 的事件处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public boolean dispatchTouchEvent (MotionEvent event) { if (event.isTargetAccessibilityFocus()) { if (!isAccessibilityFocusedViewOrHost()) { return false ; } event.setTargetAccessibilityFocus(false ); } boolean result = false ; if (actionMasked == MotionEvent.ACTION_DOWN) { stopNestedScroll(); } if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) { result = true ; } ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this , event)) { result = true ; } if (!result && onTouchEvent(event)) { result = true ; } return result; }
安全监测。如果 View#setFilterTouchesWhenObscured(true) 开启了安全检测,当 View 所在的 Window 被覆盖时不处理 Touch 事件;
停止嵌套滑动(5.0 以后添加);
View 可用时才调用 OnTouchListener#onTouch() 方法;
不管 View 是否可用,只要 OnTouchListener 不消费事件,就会让 onTouchEvent() 处理;
View 不可用时,不处理 OnTouchListener#onTouchEvent(),直接调用 View#onTouchEvent() 方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 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 ); } return clickable; } if (mTouchDelegate != null ) { if (mTouchDelegate.onTouchEvent(event)) { return true ; } } if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) { switch (action) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0 ; if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { boolean focusTaken = false ; if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { focusTaken = requestFocus(); } if (prepressed) { setPressed(true , x, y); } if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) { removeLongPressCallback(); if (!focusTaken) { if (mPerformClick == null ) { mPerformClick = new PerformClick (); } if (!post(mPerformClick)) { performClickInternal(); } } } removeTapCallback(); } break ; case MotionEvent.ACTION_DOWN: if (!clickable) { checkForLongClick(ViewConfiguration.getLongPressTimeout(),...); break ; } if (performButtonActionOnTouchDown(event)) { break ; } boolean isInScrollingContainer = isInScrollingContainer(); if (isInScrollingContainer) { } else { setPressed(true , x, y);k checkForLongClick (ViewConfiguration.getLongPressTimeout() ,...); } break ; case MotionEvent.ACTION_CANCEL: if (clickable) { setPressed(false ); } removeTapCallback(); removeLongPressCallback(); break ; case MotionEvent.ACTION_MOVE: if (clickable) { drawableHotspotChanged(x, y); } final int motionClassification = event.getClassification(); final boolean ambiguousGesture = ...; int touchSlop = mTouchSlop; if (ambiguousGesture && hasPendingLongPressCallback()) { if (!pointInView(x, y, touchSlop)) { removeLongPressCallback(); checkForLongClick(...); } } if (!pointInView(x, y, touchSlop)) { removeTapCallback(); removeLongPressCallback(); if ((mPrivateFlags & PFLAG_PRESSED) != 0 ) { setPressed(false ); } } final boolean deepPress = ; if (deepPress && hasPendingLongPressCallback()) { removeLongPressCallback(); checkForLongClick(...); } break ; } return true ; } return false ; }
如果 View 不可用但是可点击,会直接消费事件,只是不做其他任何操作;
如果 View 可用并设置了 TouchDelegate,则事件交给 TouchDelegate 处理;
不管 View 是否可用,只要 View 可点击,默认都会消费事件;
处理 focus,press,click,longclick 等。
TouchDelegate 1 2 3 4 5 6 7 8 9 public boolean onTouchEvent (MotionEvent event) { if (mTouchDelegate != null ) { if (mTouchDelegate.onTouchEvent(event)) { return true ; } } }
从源码看出,如果 View 设置了事件代理,则事件会首先交给 mTouchDelegate 对象处理,如果消费了事件,当前 View 的事件会被拦截。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 public class TouchDelegate { public TouchDelegate (Rect bounds, View delegateView) { mBounds = bounds; mSlop = ViewConfiguration.get(delegateView.getContext()).getScaledTouchSlop(); mSlopBounds = new Rect (bounds); mSlopBounds.inset(-mSlop, -mSlop); mDelegateView = delegateView; } public boolean onTouchEvent (@NonNull MotionEvent event) { int x = (int )event.getX(); int y = (int )event.getY(); boolean sendToDelegate = false ; boolean hit = true ; boolean handled = false ; switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: mDelegateTargeted = mBounds.contains(x, y); sendToDelegate = mDelegateTargeted; break ; case MotionEvent.ACTION_POINTER_DOWN: case MotionEvent.ACTION_POINTER_UP: case MotionEvent.ACTION_UP: case MotionEvent.ACTION_MOVE: sendToDelegate = mDelegateTargeted; if (sendToDelegate) { Rect slopBounds = mSlopBounds; if (!slopBounds.contains(x, y)) { hit = false ; } } break ; case MotionEvent.ACTION_CANCEL: sendToDelegate = mDelegateTargeted; mDelegateTargeted = false ; break ; } if (sendToDelegate) { if (hit) { event.setLocation(mDelegateView.getWidth() / 2 , mDelegateView.getHeight() / 2 ); } else { int slop = mSlop; event.setLocation(-(slop * 2 ), -(slop * 2 )); } handled = mDelegateView.dispatchTouchEvent(event); } return handled; } }
bounds 是指消费事件的区域;
mDelegateView 是指消费事件后将事件分发给代理 View;
示例一,扩大 View 的点击范围:
1 2 3 4 5 6 7 8 9 10 11 textView.post { with(textView) { val rect = Rect(left, top, left + width, top + height) rect.inset(-100 , -100 ) (parent as View).touchDelegate = TouchDelegate(rect, textView) } }
示例二,点击 View 时响应的是其他 View 的逻辑:
1 2 3 4 5 6 7 8 9 textView.post { with(textView) { val rect = Rect(0 , 0 , right, bottom) touchDelegate = TouchDelegate(rect, anotherView) } }
View 滑动冲突 FAQ
View.GONE 后仍消费事件的原因? 可能该 View 执行了动画,要移除。android View with View.GONE still receives onTouch and onClick View with visibility View.GONE still generates touch events
触摸 button 后滑动到外部抬起会触发点击事件吗?如果再滑动回去会触发吗? 不会触发点击事件。因为按下(ACTION_DOWN)时设置了 PFLAG_PRESSED 标记,移动(ACTION_MOVE)到 View 外部后清除了该标记,即使再滑动到 View 内也不会重新设置。当抬起(ACTION_UP)时,判断设置了 PFLAG_PRESSED 才会触发点击事件。
ACTION_CANCEL 事件怎么产生,作用是什么? 如果子 View 处理了 ACTION_DOWN 事件,那么正常情况下后面的 ACTION_MOVE 和 ACTION_UP 事件也会交给它处理。但是交给它处理之前,父 View 还是可以拦截事件的,如果拦截了事件,系统会生成 ACTION_CANCEL 事件给子 View,并且不会收到后续的事件,用于重置状态。
总结
一个点击事件是从 Activity->Window->DecorView->若干 ViewGroup->View,传递过程如下:Activity#dispatchTouchEvent() -> DecorView#dispatchTouchEvent() -> View#OnTouchListener.onTouch() -> View#onTouchEvent() -> View#OnClickListener.onClick();
如果一个 View 的 onTouchEvent 方法返回 false,那么将会交给父容器的 onTouchEvent 方法进行处理,逐级往上,如果所有的 View 都不处理该事件,则交由 Activity#onTouchEvent() 处理。
ViewGroup 默认不拦截任何事件,可以通过重写 onInterceptTouchEvent() 方法拦截事件,执行自己对应的 onTouchEvent() 方法。
子 View 可以通过调用父 View 的 requestDisallowInterceptTouchEvent(true) 请求 ViewGroup 把事件交给子 View 处理;
如果 ViewGroup 找到了能够处理该事件的 View(TouchTarget),默认情况后续的事件也会直接交给子 View 处理;
如果某一个 View 不消耗 ACTION_DOWN 事件,则同一事件序列中不会再交给该 View 处理;
非容器的 View,一旦接收到事件默认都会消耗事件,除非它是不可点击的(clickable 和 longClickable 都为 false),那么就会由父容器的 onTouchEvent() 处理;
View 的 visible 属性对事件传递没有影响;
如果当前 View 是可点击的,并且它收到了 down 和 up 事件,则它的 click 事件就会触发;对于 onLongClick,则只要当前 View 接收到 down 事件超过了系统默认的时间;
当某个子 View 消费事件时,会中止 Down 事件的分发,同时在 ViewGroup 中记录该子 View。同一序列中的 Move 和 Up 事件将由该子 View 直接进行处理。
当子 View 都不消费 Down 事件时,将调用 ViewGroup#onTouchEvent() 方法。触发的方式是调用 super.dispatchTouchEvent() 函数,即父类 View#dispatchTouchEvent 方法。如果所有子 View 都不处理,则调用 Acitivity#onTouchEvent() 方法。
如果 View 没有对 ACTION_DOWN 进行消费,那后续事件不会传递过来。如果 View 消费了 ACTION_DOWN,在不拦截的情况下,后续事件会直接传递给这个 View;
接收了 ACTION_DOWN 事件的函数不一定能收到后续的 ACTION_MOVE 和 ACTION_UP 事件,有可能事件被父 View 拦截了。
如果当前正在处理的事件被父 View 拦截,子 View 会收到一个 ACTION_CANCEL 事件,并从 TouchTarget 链中移除,所以后续事件不会再传递过来。
子 View 重叠并都是可点击时,最后添加的 View,即离用户最近的 View 最先消费事件。
参考 [1] Android 事件分发机制的设计与实现 [2] Android 事件拦截机制的设计与实现