1. 简介
先来看一个表:
Touch事件相关方法 |
功能 |
Activity |
ViewGroup |
View |
public boolean dispatchTouchEvent(MotionEvent ev) |
事件分发 |
Yes |
Yes |
Yes |
public boolean onInterceptTouchEvent(MotionEvent ev) |
事件拦截 |
No |
Yes |
No |
public boolean onTouchEvent(MotionEvent ev) |
事件响应 |
Yes |
Yes |
Yes |
从表中可以看出,Activity、ViewGroup、View都关心Touch事件,其中ViewGroup的关心的事件有三个:onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent。Activity和View关心的事件只有两个:dispatchTouchEvent、onTouchEvent。也就是说只有ViewGroup可以对事件进行拦截。
Android的UI结构是一个树形结构,树的最顶层(根节点)是Activity内包含的一个ViewGroup,下面有若干个ViewGroup节点,每个节点下面又有若干个ViewGroup节点或者View节点,以此类推。Touch事件的传递和处理与这种结构密切相关。一次完整的触摸事件由一个ACTION_DOWN、零个或一个或多个ACTION_MOVE、一个ACTION_UP组成。
在分析源码之前,先简单了解下这三个函数。
(1) 事件分发: public boolean dispatchTouchEvent(MotionEvent ev)
当Touch事件发生时,Activity的dispatchTouchEvent()方法会以隧道方式(从根节点依次往下传递直到最内层子节点,或在中间某一节点中由于某一条件停止传递)将事件传递给最外层View的dispatchTouchEvent()方法,并由该View的dispatchTouchEvent()方法对事件进行分发。
dispatchTouchEvent 的事件分发逻辑如下:
- return true :事件会分发给当前View并由dispatchTouchEvent()方法进行消费,同时事件会停止向下传递。
- return false :将事件返还给当前View的上一级的onTouchEvent()进行消费。(这个上一级可能是Activity,也可能是父View)
- return super.dispatchTouchEvent(ev) :事件会自动的分发给当前View的onInterceptTouchEvent方法。
注意,View响应dispatchTouchEvent()和onInterceptTouchEvent()的前提是可以向该View中添加子View,也就是说该View有子节点才谈得上能分发和拦截。
(2) 事件拦截: public boolean onInterceptTouchEvent(MotionEvent ev)
在外层View的dispatchTouchEvent()方法返回super.dispatchTouchEvent(ev)时,事件会自动的分发给当前View的onInterceptTouchEvent()方法。
onInterceptTouchEvent 的事件拦截逻辑如下:
- return true :将对事件进行拦截,并将拦截到的事件交由当前View的onTouchEvent()进行处理。
- return false :将对事件进行放行,当前View上的事件会被传递到子View 上,再由子View的dispatchTouchEvent()来继续对这个事件进行分发。
- return super.onInterceptTouchEvent(ev) :事件默认会被拦截,并将拦截到的事件交由当前View的onTouchEvent()进行处理。
(3) 事件响应: public boolean onTouchEvent(MotionEvent ev)
由(1)和(2)可以知道onTouchEvent()被调用的条件。
onTouchEvent 的事件响应逻辑如下:
- return false :事件将会从当前View向上传递,并且都是由上层View的onTouchEvent()来接收,如果传递到上层的onTouchEvent()也返回false,那么这 个事件就会"消失",而且接收不到下一次事件。
- return true :接收并消费掉该事件。
- return super.onTouchEvent(ev) :默认处理事件的逻辑和return false相同。
下面看下源码。
1.1 Activity对Touch事件的处理
当Touch事件发生生,最先被触发的是Activity的dispatchTouchEvent()函数,再由这个函数触发根节点的dispatchTouchEvent()。如果想让Activity不响应触摸事件,可以直接重写这个函数。
dispatchTouchEvent()@Activity.java
// 处理触摸屏事件。可以重写这个函数来拦截所有的触摸屏事件,不让它们分发到window。为了应该被正常处理的触摸屏幕事件,一定要确保调用这个实现。 public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { // 从ACTION_DOWN事件开始本次触摸事件 onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { // 会调用window中的根ViewGroup进行事件分发 return true; // 如果子View消费了这个事件,返回true. } return onTouchEvent(ev); // 没有View处理本次事件,调用Activity的onTouchEvent。 }
onTouchEvent()@Activity.java
// 当一个触摸屏事件没有被它下面的任意view处理的时候,调用这个函数。这个处理对于触摸事件超出window范围的时候很有用,这个时候没有view接收到事件。 public boolean onTouchEvent(MotionEvent event) { if (mWindow.shouldCloseOnTouch(this, event)) { finish(); return true; } return false; }
1.2 View对Touch事件的处理
dispatchTouchEvent()@View.java
// 将屏幕触摸事件向下传递给目标view. public boolean dispatchTouchEvent(MotionEvent event) { ...... if (onFilterTouchEventForSecurity(event)) { // ListenerInfo类保存了设给这个view的各种listener,如OnClickListener,OnTouchListener等。 ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { // 当mOnTouchListener不为null,当前控件是enable的,且mOnTouchListener.onTouch返回true, 那么dispatchTouchEvent返回true. // 从这里可以看到onTouch执行要先于onTouchEvent及onClick return true; } // 上面的条件都不成立,才会执行onTouchEvent() if (onTouchEvent(event)) { // 如果一个控件是可点击的,那么onTouchEvent必定返回true, 因此dispatchTouchEvent也返回true. return true; } } ...... return false; }
从View的dispatchTouchEvent()的实现知道,如果给一个view设置了OnTouchListener,那么onTouch函数就在dispatchTouchEvent()函数中被回调,只有onTouch()返回false时,才会继续执行onTouchEvent()。
onTouchEvent()@View.java
// 实现这个方法处理屏幕触摸手势事件 // 如果这个方法被用于检测点击动作,那么推荐通过调用performClick来实现。这样做可以确保系统行为一致,包括服从点击声音喜好,调度OnClickListener调用,当辅助 // 功能启用的时候处理ACTION_CLICK ACTION_CLICK。 public boolean onTouchEvent(MotionEvent event) { final int viewFlags = mViewFlags; if ((viewFlags & ENABLED_MASK) == DISABLED) { if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); // 设置按下UI状态 } // 一个可点击的但是disabled的view仍然消费touch事件,只是不作出回应。 return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); } ...... if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { switch (event.getAction()) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { // 如果我们没有拥有它,我们应该获取焦点,而且应该在Touch模式下。 boolean focusTaken = false; if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { focusTaken = requestFocus(); } if (prepressed) { // 按钮在我们实际显示它按下之前被释放。现在使它显示按下状态(在调度click之前),以确保用户看到它。 setPressed(true); } if (!mHasPerformedLongPress) { // 这是一个阀门,所以移除长按的检查 removeLongPressCallback(); // 只有在按下状态中时才执行click动作。 if (!focusTaken) { // 使用Runnable和post执行performClick,而不是直接执行。这样可以让这个view的其它可见状态在click操作开始前更新。 if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClick(); // 注意,在MotionEvent.ACTION_UP时执行click。 } } } if (mUnsetPressedState == null) { mUnsetPressedState = new UnsetPressedState(); } if (prepressed) { postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (!post(mUnsetPressedState)) { mUnsetPressedState.run(); } removeTapCallback(); } break; case MotionEvent.ACTION_DOWN: mHasPerformedLongPress = false; if (performButtonActionOnTouchDown(event)) { break; } // 沿层次结构走,以确定我们是否在一个滚动的容器内。 boolean isInScrollingContainer = isInScrollingContainer(); // 对于在一个滚动容器内的views,延迟一会按下的反馈,以防这是一个滚动。 if (isInScrollingContainer) { mPrivateFlags |= PFLAG_PREPRESSED; if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } else { // 如果不是在一个正在滚动的容器内,那么马上显示反馈。 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(); // 按钮之外的移动要容忍。 if (!pointInView(x, y, mTouchSlop)) { // Outside button removeTapCallback(); if ((mPrivateFlags & PFLAG_PRESSED) != 0) { // 移除后面长按/阀门的检查 removeLongPressCallback(); setPressed(false); } } break; } return true; // 如果一个控件是可点击的,那么点击该控件时,onTouchEvent的返回值必定是true } return false; }
performClick()@View.java
// 如果定义了OnClickListener,则调用。执行所有与click关联的动作:报告可访问性事件,播放按键声音。 public boolean performClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); ListenerInfo li = mListenerInfo; if (li != null && li.mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this); return true; } return false; }
setOnClickListener()@View.java
public void setOnClickListener(OnClickListener l) { if (!isClickable()) { setClickable(true); // 如果这个view不是clickable,将它设置为clickable。 } getListenerInfo().mOnClickListener = l; }
如果给一个View设置了OnClickListener,那么onClick()事件是在onTouchEvent()函数中被调用的。
1.3 ViewGroup对Touch事件的处理
onInterceptTouchEvent()@ViewGroup.java
// 实现这个方法来拦截所有触摸屏移动事件。这可以让你在事件被分配给孩子的时候看到事件,并在任意时刻获取当前手势的所有权。 // 使用这个功能需要特别注意,因为它与View.onTouchEvent有着相当复杂的相互作用,并且使用它还需要以正确的方式实现这个方法。 // 事件将会以下面顺序被接收: // (1) ViewGroup会在这里接收到DOWN事件,这个DOWN事件会被这个ViewGroup的一个子View处理掉,或者给这个ViewGroup自己的onTouchEvent函数进行处理。那就意味着ViewGroup应该实现onTouchEvent()函数并且返回true, 这样你就会继续看到手势的后续事件(而不是寻找父View来处理)。 // (2) 通过从ViewGroup自己的onTouchEvent()返回true, ViewGroup的onInterceptTouchEvent()中将不会再收到任何后续事件,即后续事件不用再经过onInterceptTouchEvent()了,所有触摸的处理都像正常情况一样发生在onTouchEvent里了。 // (3) 只要ViewGroup从这个函数返回false,接下来的每个事件(包括最后的UP)都会首先传递到这里,然后再传递给目标View的onTouchEvent()。 // (4) 如果ViewGroup从这个函数返回true,说明从子view拦截了事件,并将它们通过onTouchEvent()传递给这个ViewGroup。当前目标View将接收到相同事件,但是Action为ACTION_CANCEL,并且没有进一步消息在这里传递。 public boolean onInterceptTouchEvent(MotionEvent ev) { return false; }
dispatchTouchEvent()@ViewGroup.java
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; // 处理最初的down事件 if (actionMasked == MotionEvent.ACTION_DOWN) { // 开始一个新的触摸手势时,丢掉所有之前的状态。framework可能由于app切换,ANR或者其它状态变化而放弃前一个手势的up或者cancel事件。 cancelAndClearTouchTargets(ev); resetTouchState(); } // 检查拦截 final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; // 是否禁用事件拦截功能,默认为false. if (!disallowIntercept) { intercepted = onInterceptTouchEvent(ev);// 根据onInterceptTouchEvent返回值决定是否拦截,返回false表示不拦截,返回true表示拦截。 ev.setAction(action); // 重新保存action,以防它发生变化。 } else { intercepted = false; } } else { // 没有触摸目标且action不是最初的DOWN事件,那么这个ViewGroup继续拦截触摸事件。 intercepted = true; } // 检查取消 final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; // 如果需要,更新触摸目标列表给向下的指针。 final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; if (!canceled && !intercepted) { // 如果不是CANCEL,而且不拦截事件,则往下走。intercepted的值在前面被赋值。intercepted为true,则事件不再往下传。 if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { final int actionIndex = ev.getActionIndex(); // DOWN事件这个值通常为0 final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) : TouchTarget.ALL_POINTER_IDS; // 为该指针id除早期的触摸目标,以防它们已经不同步了。 removePointersFromTouchTargets(idBitsToAssign); final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); // 从前往后扫描,找一个可以接收事件的子view。 final View[] children = mChildren; final boolean customOrder = isChildrenDrawingOrderEnabled(); for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; final View child = children[childIndex]; if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { continue; } newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { // 子view已经在它的范围内接收触摸事件了。 // 给它新指针,除了它正在处理的指针。 newTouchTarget.pointerIdBits |= idBitsToAssign; break; // 找到了接收事件的target,跳出循环。 } resetCancelNextUpFlag(child); if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // dispatchTransformedTouchEvent函数中调用子view的dispatchTouchEvent。 // 子view在它的范围内接收触摸事件。 mLastTouchDownTime = ev.getDownTime(); mLastTouchDownIndex = childIndex; mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } } } if (newTouchTarget == null && mFirstTouchTarget != null) { // 没有找到一个子view来接收事件,将指针指向最近添加的目标。 newTouchTarget = mFirstTouchTarget; while (newTouchTarget.next != null) { newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |= idBitsToAssign; } } } // 分发给触摸目标 if (mFirstTouchTarget == null) { // 没有触摸目标,那么把它当作一个普通的view。 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; } } // Update list of touch targets for pointer up or cancel, if needed. // 如果需要,为指针UP或者CANCEL更新触摸目标列表。 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; }
dispatchTransformedTouchEvent()@ViewGroup.java
// 将手势事件转换成特定子view的坐标空间,过滤无关的指针ID,如有必要覆盖它的action。如果child为null,则取而代之的是假定手势事件会发送给该ViewGroup。 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; // 取消手势动作是一种特殊情况。不需要执行任何转换或者过滤操作。重点关注的是action,而不是内容。 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); // 调用child的dispatchTouchEvent。 } event.setAction(oldAction); return handled; } // 结算要传递的指针数量。 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); handled = child.dispatchTouchEvent(event); event.offsetLocation(-offsetX, -offsetY); } return handled; } transformedEvent = MotionEvent.obtain(event); } else { transformedEvent = event.split(newPointerIdBits); } // 执行任何需要的转换和分发。 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); //若一个控件是可点击的,则点击该控件时,dispatchTouchEvent的返回值必定是true } // 完成 transformedEvent.recycle(); return handled; }