一、什么是事件?
在我们通过屏幕与手机交互的时候,每一次手指的按下、移动、抬起就是一个个事件。按照面向对象的思想,这些一个个事件都被封装成了 MotionEvent 对象。
二、什么是事件序列?
同一个事件序列是指:从手指触摸屏幕的那一刻开始,到手指离开屏幕那一刻结束,在这个过程中产生的一系列事件。这一系列事件以 ACTION_DOWN
事件开始,以 ACTION_UP
事件结束,中间可能还有一些 ACTION_MOVE
事件。
千万别把一个事件序列当成一个事件,不然会对理解整个事件分发机制感到迷惑。比如事件处理是以事件为单位,而不是以事件序列为单位。即有可能只处理 ACTION_DOWN
事件,而不处理后面的 ACTION_MOVE、ACTION_UP
事件。
三、事件的传递顺序是怎样的?
事件传递的方向是由外向内的,即事件总是先传递给父视图,然后由父视图分发给子视图。
四、三个重要的方法
其实要理解事件分发机制,不外乎掌握以下三个方法。这三个方法有一个共同点,就是它们是否执行自己的功能(分发、拦截、消耗)完全由自己的返回值来确定,返回 true 就表示自己完成了自己的功能(分发、拦截、消耗)。
- public boolean dispatchTouchEvent (MotionEvent event)
在 View 类以及 ViewGroup 类中均有定义。
注意:ViewGroup 类继承 View 类,因此该方法在 ViewGroup 类中是重写。
作用:
用来进行事件的分发,传递触摸屏幕(Touch)事件至目标 View,除非当前视图就是目标 View。
参数:
event,被分发的动作事件。Touch 事件的相关细节(发生触摸的位置、时间等)被封装成 MotionEvent (动作事件对象)。
返回值:
返回 true 表示该事件将在当前 view 中处理,消耗了该事件,因此不再往下传递事件;返回 false 则表示继续向子视图传递该事件。默认返回 false。
如果事件能够传递到当前视图,此方法一定会被调用。因为该方法在 View 类和 ViewGroup 类中均有定义,所以逻辑表达上会有不同的意思。
ViewGroup 类 dispatchTouchEvent() 的返回值受 ViewGroup 视图 onTouchEvent() 方法和子视图 dispatchTouchEvent() 方法返回值的影响。
View 类中 dispatchTouchEvent() 源码:
public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
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;
}
}
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
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
可以看到,在 View 中的 dispatchTouchEvent() 方法内有一个判断,里面有三个条件,如果三个条件都满足,就返回 true,否则返回 onTouchEvent() 方法执行的结果。
第一个条件是判断 mOnTouchListener 变量是否为空,这个变量是在 View 中的 setOnTouchListener() 方法中赋值的,也就是说只要我们给控件注册了 onTouch() 事件,mOnTouchListener 就一定被赋值。
第二个条件是判断控件是否 enable。
第三个条件是 onTouch() 方法的返回值。如果我们在 onTouch() 方法中返回 true,基本上,dispatchTouchEvent() 就会返回 true,不再继续传递事件,也不会执行 onTouchEvent() 方法。如果我们在 onTouch() 方法里返回 false,则会执行 onTouchEvent() 方法。
因此,如果 onTouch() 返回 true,onClick() 就不会再执行。
注意:这里引申出三个问题。
1. 什么时候调用 onClick() 方法?
View 类中 onTouchEvent() 部分代码:
case MotionEvent.ACTION_UP:
......
//处理 click 事件
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
.....
break;
当手指抬起,ACTION_UP
事件发生时,会触发 performClick() 方法,performClick() 方法又会调用 onClick() 方法。对于 performClick() 方法,只要 mOnClickListener 不为空,就会去调用它的 onClick() 方法。又因为 onTouch() 方法默认返回 false,因此,通常执行完 onTouch() 方法后,还会执行 onClick() 方法就是这个原因。
onClick() 方法会调用的前提是:当前 View 是可点击的,并且它收到了 ACTION_DOWN
和 ACTION_UP
的事件。
2. 什么时候调用 onLongClick() 方法?
View 类中 onTouchEvent() 部分代码:
case MotionEvent.ACTION_DOWN:
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
......
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(0, x, y);
}
break;
当手指按下并持续一段时间,ACTION_DOWN
事件发生时,会触发 checkForLongClick() 方法,如果该 View 设置了 onLongClickListener,那么 checkForLongClick() 方法又会调用 onLongClick() 方法。
3. onTouch() 和 onTouchEvent() 有什么区别,两者又该如何使用?
这两个方法都是在 View 类的 dispatchTouchEvent() 中调用的。
......
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;
}
......
从源码中可以看出,onTouch() 先于 onTouchEvent() 执行。如果在 onTouch() 方法中通过返回 true 将事件消费掉,则 onTouchEvent() 将不会再执行。
另外需要注意的是,onTouch() 能够执行需要两个前提条件:
mOnTouchListener 的值不能为空;
当前处理事件的 View 必须是 enable 的;
所以,如果我们有一个控件是 disable 的,那么给它设置的 onTouch() 方法将永远得不到执行。对于这一类控件,如果我们想要监听它的 Touch 事件,就必须在该控件中重写 onTouchEvent() 方法来实现。
一个不可用(disable)的 View 仍然可以消耗事件,只是不做任何响应并返回 false。View 的 enable 属性不影响 onTouchEvent() 的默认返回值。如果一个 View 是 disable 状态,只要它的 clickable 和 longClickable 属性有一个为 true,那么它的 onTouchEvent() 就返回 true。
看完 View 类中的 dispatchTouchEvent() 代码,下面来看看
ViewGroup 类中 dispatchTouchEvent() 代码:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
// If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
// handled 是用作返回值的变量,表示事件是否被分发,默认是 false
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
// 处理一个新的 ACTION_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.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
// intercepted 是用来记录是否被拦截的结果
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.
// mFirstTouchTarget 为空,同时事件为非
// ACTION_DOWN 事件,那么在这里进行拦截
// mFirstTouchTarget 是触摸目标列表的第一个触摸目标
// (First touch target in the linked list of touch targets.)
intercepted = true;
}
// If intercepted, start normal event dispatch. Also if there is already
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
// 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;
// 如果事件没有取消或被拦截
if (!canceled && !intercepted) {
......
// 这里开始对事件类型进行区分,如果是
// ACTION_DOWN,就是一个新的事件序列开始
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);
// mChildrenCount 记录子 View 个数
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
// 获取到点击的坐标,用来从所有子 View 中筛选出目标 View
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.
// 接下来从前往后检索能够收到事件的子 View
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
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;
}
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
// 找到了合适的子View,这里将子View封装为一个target
// 要是newTouchTarget不为空就跳出循环
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;
}
// 就算返回结果为空也没关系,在这里继续递归调用
// 子View的dispatchTransformedTouchEvent()
resetCancelNextUpFlag(child);
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();
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();
}
// 没有找到可接受事件的View
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;
}
}
}
// Dispatch to touch targets.
// 接下来是对于非 ACTION_DOWN 事件的分发了,这里有两种情况
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
// 1. 如果 mFirstTouchTarget 为 null,调用
// dispatchTransformedTouchEvent() 且传一个 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.
// 2. 之前有 View 接受了 ACTION_DOWN 事件,那么
// 这个 View 也将接受其余的事件
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
// alreadyDispatchedToNewTouchTarget这个变量在之前
// View接受ACTION_DOWN事件时设置为true
// 同时这个mFirstTouchTarget也就是之前那个
// View封装好的target
// 那么返回值handled就为设置为true,表示已经分发
handled = true;
} else {
// 如果mFirstTouchTarget不为空,对于非ACTION_DOWN事件,
// 递归调用dispatchTransformedTouchEvent()
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;
}
可以看到,上面方法中多次调用了
dispatchTransformedTouchEvent() 方法,我们来看一下它的源码:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// 处理 ACTION_CANCEL 事件
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;
}
......
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
// 如果传来的参数child为空的话,则调用ViewGroup的dispatchTouchEvent()
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
// 不为空,则调用child的dispatchTouchEvent()
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
// Perform any necessary transformations and dispatch.
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);
}
// Done.
transformedEvent.recycle();
return handled;
}
它的作用是:将事件传给为指定的子视图。如果子视图为 null,则事件将交回给此视图组。
- public boolean onInterceptTouchEvent(MotionEvent ev)
仅在 ViewGroup 类中有定义,View 类中没有该方法。
注意:ViewGroup 类继承 View 类。
作用:
用于拦截所有触摸屏幕动作事件。
参数:
正在被分发到下层的动作事件。
返回值:
返回 true 表示拦截了事件,即窃取子视图的动作事件并通过 onTouchEvent() 方法,将它们分发给该 ViewGroup。而目标视图则会收到一个 ACTION_CANCEL 的事件,并且不会有更多的信息发送给它。
ViewGroup 默认不拦截事件,即 onInterceptTouchEvent() 方法默认返回 false。
如果当前 View 拦截了某个动作事件,那么在同一个事件序列当中,此方法不会被再次调用。
ViewGroup 中 onInterceptTouchEvent() 源码:
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;
}
- public boolean onTouchEvent (MotionEvent event)
仅在 View 类中有定义,ViewGroup 类中无重写。
注意:ViewGroup 类继承 View 类。
作用:
处理屏幕动作事件。
参数:
event,动作事件。
返回值:
返回 true 表示该事件已经被处理,否则返回 false。
View 的 onTouchEvent() 方法默认返回 true,即默认消耗事件,除非它是不可点击的(clickable 和 longClickable 同时为 false)。
如果不处理事件(即返回 false),则当前 View 无法再次接收同一个事件序列中的其他事件。
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();
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);
}
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)) {
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();
}
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();
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;
}
return true;
}
return false;
}
三个方法间的联系
三个方法间的联系可以用如下代码形象地表示:
public boolean dispatchTouchEvent(MotionEvent event) {
boolean consume = false;
if(onInterceptTouchEvent(ev)) {
consume = onTouchEvent(ev);
} else {
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
通过上面的伪代码,可以看出,事件的分发机制大致如下:
对于一个父 ViewGroup 来说,屏幕触摸事件产生后,该事件首先会传递给它,这时它的 dispatchTouchEvent() 方法就会被调用,如果这个 ViewGroup 的 onInterceptTouchEvent() 方法返回 true 就表示它要拦截当前事件,接着事件就会交给该 ViewGroup 处理,即执行 onTouchEvent() 方法。如果 onInterceptTouchEvent()
方法返回 false 就表示它不拦截该事件,这时事件就会继续传递给它的子视图,接着子视图的 dispatchTouchEvent() 就会被调用,如此反复,直到事件被处理。
五、总结:
1. ViewGroup 的 dispatchTouchEvent() 是真正在执行 “分发” 工作,而 View 的 dispatchTouchEvent() 方法,并不执行分发工作,或者说它分发的对象就是自己,决定是否把 Touch 事件交给自己处理,而处理的方法,就是 onTouchEvent()。
2. 当面对 ACTION_DOWN
事件时,ViewGroup 总是会调用自己的 onInterceptTouchEvent()
方法来询问自己是否要拦截该事件。
3. 正常情况下,一个事件序列只能被一个 ViewGroup 拦截并消耗(处理)。
4. 某个 ViewGroup 一旦决定拦截,那么该事件序列都只能由它来处理(如果事件序列能够传递给它的话),并且它的 onInterceptTouchEvent()
方法不会再被调用。
5. View 没有 onInterceptTouchEvent() 方法,一旦有事件传递给它,那么它的 onTouchEvent() 方法就会被调用。
6. 某个 View 一旦开始处理事件,如果它不消耗 ACTION_DOWN
事件,即 onTouchEvent() 返回了 false,那么同一事件序列中的其他事件都不会再交给它处理,并且事件将重新交由它的父元素去处理,即父视图的 onTouchEvent() 会被调用。
7. 如果 View 不消耗 ACTON_DOWN
以外的其他事件,那么这个点击事件会消失,此时父视图的 onTouchEvent() 并不会被调用,并且当前 View 可以持续收到后续的事件。最终,这些消失的屏幕触摸事件会传递给 Activity 处理。