源码基于 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。
事件架构
Android 事件分发体系InputEvent:输入事件抽象
/**
* Common base class for input events.
*/
public abstract class InputEvent implements Parcelable {}
/**
* Object used to report key and button events.
*/
public class KeyEvent extends InputEvent implements Parcelable {}
/**
* Object used to report movement (mouse, pen, finger, trackball) events.
*/
public final class MotionEvent extends InputEvent implements Parcelable {}
KeyEvent 对应了键盘的输入事件;MotionEvent 就是手势事件,鼠标、笔、手指、轨迹球等相关输入设备的事件都属于 MotionEvent。
InputManager:输入管理器
InputEvent 统一由 InputManager 进行分发,负责与硬件通信并接收输入事件。
system_server 进程启动时会创建 InputManagerService 服务。
// SystemServer.java
private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
InputManagerService inputManager = new InputManagerService(context);
// 实例化 InputManagerCallback
WindowManagerService wm = WindowManagerService.main(context, inputManager, ...);
inputManager.setWindowManagerCallbacks(wm.getInputManagerCallback());
inputManager.start();
}
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 来监控键盘消息。
InputEventReceiver:事件接收器
App 端与服务端建立了双向通信之后,InputManager 就能够将产生的输入事件从底层硬件分发过来,Android 提供了 InputEventReceiver 类,以接收分发这些消息:
/**
* Provides a low-level mechanism for an application to receive input events.
* @hide
*/
public abstract class InputEventReceiver {
public InputEventReceiver(InputChannel inputChannel, Looper looper) {
mInputChannel = inputChannel;
mMessageQueue = looper.getQueue();
mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
inputChannel, mMessageQueue);
}
// Called from native code.
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();
}
}
// ViewRootImpl.java
final class WindowInputEventReceiver extends InputEventReceiver {
@Override
public void onInputEvent(InputEvent event) {
// 将输入事件加入队列,开始事件分发
enqueueInputEvent(event, this, 0, true);
}
}
InputChannel
Window 和 IMS 之间通过 InputChannel 通信。InputChannel 是一个 pipe,底层通过 socket 进行通信。在 ViewRootImpl.setView() 过程中注册 InputChannel。
/**
* An input channel specifies the file descriptors used to send input events to a window in another process.
* It is Parcelable so that it can be sent to the process that is to receive events.
* Only one thread should be reading from an InputChannel at a time.
**/
public final class InputChannel implements Parcelable {}
// ViewRootImpl.java
public void setView(){
requestLayout();
InputChannel inputChannel = new InputChannel();
// 通过 Binder 在 system_server 进程中完成 InputChannel 的注册
res = mWindowSession.addToDisplayAsUser(mWindow, inputChannel, ...);
if (mInputQueueCallback != null) {
mInputQueue = new InputQueue();
mInputQueueCallback.onInputQueueCreated(mInputQueue);
}
mInputEventReceiver = new WindowInputEventReceiver(inputChannel, Looper.myLooper());
// 设置 InputStage
mSyntheticInputStage = new SyntheticInputStage();
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
// nativePostImeStage earlyPostImeStage imeStage viewPreImeStage nativePreImeStage
}
InputStage
public class ViewRootImpl{
/**
* Base class for implementing a stage in the chain of responsibility
* for processing input events.
*/
abstract class InputStage {
protected int onProcess(QueuedInputEvent q) {
return FORWARD;
}
}
/**
* Delivers post-ime input events to the view hierarchy.
*/
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;
// mView 是 DecorView 类型
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()
方法处理这个事件; - 若不拦截,则执行子元素的 dispatchTouchEvent() 方法,进入向下分发的传递,直到事件被处理;
- 如果事件从外向内传递过程中一直没有被拦截,且最底层子 View 没有消费事件,这时父 View 可以进行消费,如果还是没有被消费的话,最后会到 Activity#onTouchEvent() 函数。
- 如果 View 没有对 ACTION_DOWN 进行消费,之后的其他类型的事件也不会传递过来,也就是说 ACTION_DOWN 必须返回 true,之后的事件才会传递进来。
- 事件处理优先级是
OnTouhListener#onTouch(View v, MotionEvent event) -> onTouchEvent() -> OnClickListener#onClick(View v)
。
三个方法的关系如下:
public boolean dispatchTouchEvent(MotionEvent event){
boolean consum = false;
if(onInterceptTouchEvent(event)){
consum = onTouchEvent(event);
} else {
consum = child.dispatchTouchEvent(event);
}
return consum;
}
思维导图
源码分析
Activity 的事件处理
public class Activity {
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
// 调用 mDecor.superDispatchTouchEvent(event);
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 的事件处理
分发事件:
// ViewGroup.java
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
// ACTION_DOWN 说明是新事件,重置状态
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 清空 TouchTarget 链表
cancelAndClearTouchTargets(ev);
// 重置事件状态
resetTouchState();
}
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// 判断是否拦截事件
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
// 如果拦截,事件由当前 ViewGroup 处理
intercepted = onInterceptTouchEvent(ev);
// 防止onInterceptTouchEvent()的时候改变Action
ev.setAction(action);
} else {
intercepted = false;
}
} else {
//没有 TouchTarget 而且非 ACTION_DOWN 类型的事件,由 ViewGroup 处理
intercepted = true;
}
// 如果给子View发送cancel事件后mFirstTouchTarget会变null,
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
// 事件没有取消也没有拦截,向子 View 传递
if (!canceled && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int childrenCount = mChildrenCount;
// 遍历查找第一个消费这个事件的子View,并设置为 Target
if (newTouchTarget == null && childrenCount != 0) {
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final View[] children = mChildren;
// 按 View 添加顺序倒序查找
for (int i = childrenCount - 1; i >= 0; i--) {
final View child = getAndVerifyPreorderedView(...);
// 判断事件坐标是否在 View 内
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
// 更新 target
newTouchTarget = getTouchTarget(child);
// 分发事件到点击的 View
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// View 处理该事件,添加到 TouchTarget 调用链,赋值 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.
// ...
}
}
}
if (mFirstTouchTarget == null) {
// 没有找到处理此事件的子 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 {
// 给子 View 发送 cancel 事件
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
}
predecessor = target;
target = next;
}
}
}
return handled;
}
// 第二个参数cancel包含两种含义,一种是外部收到了取消事件,另一种是事件被拦截
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
final int oldAction = event.getAction();
// cancel 事件处理
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)。
为什么倒序查找 TouchTarget?
如果按添加顺序遍历,当 View 重叠时(FrameLayout),先添加的 View 总是能消费事件,而后添加的 View 不可能获取到事件。
// ViewGroup.java
public boolean dispatchTouchEvent(MotionEvent ev) {
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = ...;
final View child = ...;
// 重叠的子 View 都包含点击区域
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
// 非容器 View 默认都会消费事件,从而跳出循环
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
拦截事件:
// ViewGroup.java
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 的事件处理
// View.java
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;
}
// 如果 View 可用并设置了 OnTouchListener,如果回调方法 onTouch() 返回 true 则消费事件
// 并且不会调用 onTouchEvent() 方法
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() 方法。
// View.java
public boolean onTouchEvent(MotionEvent event) {
// 判断是否可以点击
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
// View 不可用时可以消费事件,只是没有响应
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;
// 只有在 press 的情况下,才 click,longClick
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// 如果我们在当前View还没获取焦点,并且能在touch下foucus
// 那么第一次点击只会将这个View的状态改成focus,而不会触发click
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
// 设置 Pressed 状态,更新 View 背景
if (prepressed) {
setPressed(true, x, y);
}
// 检查 longClick 是否已执行
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();
}
}
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
if (!clickable) {
// 400 ms
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(...);
}
}
// Be lenient about moving outside of buttons
if (!pointInView(x, y, touchSlop)) {
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
}
final boolean deepPress = ;
if (deepPress && hasPendingLongPressCallback()) {
// process the long click action immediately
removeLongPressCallback();
checkForLongClick(...);
}
break;
}
return true;
}
return false;
}
- 如果 View 不可用但是可点击,会直接消费事件,只是不做其他任何操作;
- 如果 View 可用并设置了 TouchDelegate,则事件交给 TouchDelegate 处理;
- 不管 View 是否可用,只要 View 可点击,默认都会消费事件;
- 处理 focus,press,click,longclick 等。
View 滑动冲突
问题
- View.GONE 后仍消费事件;
可能该 View 执行了动画,要移除。
android View with View.GONE still receives onTouch and onClick
View with visibility View.GONE still generates touch events
总结
- 一个点击事件产生后,它的传递过程如下:
Activity->Window->DecorView->View。如果一个 View 的 onTouchEvent 方法返回 false,那么将会交给父容器的 onTouchEvent 方法进行处理,逐级往上,如果所有的 View 都不处理该事件,则交由 Activity 的 onTouchEvent 进行处理。 - ViewGroup 默认不拦截任何事件,可以通过重写 onInterceptTouchEvent() 方法拦截事件,执行自己对应的 onTouchEvent() 方法。
- 子 View 可以通过调用父 View 的 requestDisallowInterceptTouchEvent(true) 阻止 ViewGroup 对事件进行拦截;
- 如果 ViewGroup 找到了能够处理该事件的 View(TouchTarget),则直接交给子 View 处理,自己的 onTouchEvent() 方法不会执行;
- 如果某一个 View 不消耗 ACTION_DOWN 事件,则同一事件序列中不会再交给该 View 处理;
- 非容器的 View,一旦接收到事件默认都会消耗事件,除非它是不可点击的(clickable 和 longClickable 都为 false),那么就会由父容器的 onTouchEvent() 处理;
- View 的 visible 属性对事件传递没有影响;
- 点击事件分发过程是
Activity#dispatchTouchEvent() -> DecorView#dispatchTouchEvent() -> View#OnTouchListener.onTouch() -> View#onTouchEvent() -> View#OnClickListener.onClick()
; - 如果当前 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 拦截,会收到一个 ACTION_CANCEL,后续事件不会再传递过来。
- 子 View 重叠并都是可点击时,最后添加的 View,即离用户最近的 View 最先消费事件。
网友评论