ACTION_CANCEL在这些时候会触发:1、父view拦截事件;2、ACTION_DOWN初始化操作;3、在子View处理事件的过程中被从父View中移除时等。父view拦截事件是指在子View处理事件的过程中,父View对事件拦截。
一、ACTION_CANCEL在这些时候会触发
1、父view拦截事件
首先要了解ViewGroup什么情况下会拦截事件,请看下面一段代码:
@Override
public boolean dispatchTouchEvent(MotionEvent ev)
...
boolean handled = false;
if (onFilterTouchEventForSecurity(ev))
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
...
// 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;
...
...
可以看出有两个条件:
- MotionEvent.ACTION_DOWN事件或者mFirstTouchTarget非空也就是有子view在处理事件
- 子view没有做拦截,也就是没有调用ViewParent#requestDisallowInterceptTouchEvent(true)
如果满足上面的两个条件才会执行onInterceptTouchEvent(ev)。如果ViewGroup拦截了事件,则intercepted变量为true,接着往下看:
@Override
public boolean dispatchTouchEvent(MotionEvent ev)
boolean handled = false;
if (onFilterTouchEventForSecurity(ev))
...
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null)
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept)
// 当mFirstTouchTarget != null,也就是子view处理了事件
// 此时如果父ViewGroup拦截了事件,intercepted==true
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;
...
// Dispatch to touch targets.
if (mFirstTouchTarget == null)
...
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)
...
else
// 判断一:此时cancelChild == true
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
// 判断二:给child发送cancel事件
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits))
handled = true;
...
...
return handled;
以上判断一处cancelChild为true,然后进入判断二中一看究竟:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits)
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL)
// 将event设置成ACTION_CANCEL
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null)
...
else
// 分发给child
handled = child.dispatchTouchEvent(event);
event.setAction(oldAction);
return handled;
当参数cancel为ture时会将event设置为MotionEvent.ACTION_CANCEL,然后分发给child。
2、ACTION_DOWN初始化操作
首先请看一段代码:
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.
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.
// 取消并清除所有的Touch目标
cancelAndClearTouchTargets(ev);
resetTouchState();
...
...
系统可能会由于App切换、ANR等原因丢失了up,cancel事件。因此需要在ACTION_DOWN时丢弃掉所有前面的状态,具体代码如下:
private void cancelAndClearTouchTargets(MotionEvent event)
if (mFirstTouchTarget != null)
boolean syntheticEvent = false;
if (event == null)
final long now = SystemClock.uptimeMillis();
event = MotionEvent.obtain(now, now,
MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
syntheticEvent = true;
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next)
resetCancelNextUpFlag(target.child);
// 分发事件同情况一
dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
...
PS:在dispatchDetachedFromWindow()中也会调用cancelAndClearTouchTargets()
3、在子View处理事件的过程中被从父View中移除时
请看下面这段代码:
public void removeView(View view)
if (removeViewInternal(view))
requestLayout();
invalidate(true);
private boolean removeViewInternal(View view)
final int index = indexOfChild(view);
if (index >= 0)
removeViewInternal(index, view);
return true;
return false;
private void removeViewInternal(int index, View view)
...
cancelTouchTarget(view);
...
private void cancelTouchTarget(View view)
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null)
final TouchTarget next = target.next;
if (target.child == view)
...
// 创建ACTION_CANCEL事件
MotionEvent event = MotionEvent.obtain(now, now,
MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
分发给目标view
view.dispatchTouchEvent(event);
event.recycle();
return;
predecessor = target;
target = next;
4、子View被设置了PFLAG_CANCEL_NEXT_UP_EVENT标记时
请看下面这段代码,在情况一种的两个判断处:
// 判断一:此时cancelChild == true
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
// 判断二:给child发送cancel事件
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits))
handled = true;
当 resetCancelNextUpFlag(target.child) 为true时同样也会导致cancel,查看代码:
/**
* Indicates whether the view is temporarily detached.
*
* @hide
*/
static final int PFLAG_CANCEL_NEXT_UP_EVENT = 0x04000000;
private static boolean resetCancelNextUpFlag(View view)
if ((view.mPrivateFlags & PFLAG_CANCEL_NEXT_UP_EVENT) != 0)
view.mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
return true;
return false;
二、滑出子View范围会发生什么
通常来说,滑出子View范围什么也不会发生。如果手指移出了子View之外,从而导致事件序列被取消,那么通常不会有太多事情发生。您的应用程序将会收到一个ACTION_CANCEL事件,但是由于事件已经被取消,您无法执行任何进一步的操作。如果您希望避免这种情况发生,您可以尝试使用requestDisallowInterceptTouchEvent()方法来防止触摸事件序列被拦截,或者重新设计您的UI以确保用户不会意外地移动手指到View的范围外。
延伸阅读1:ACTION_CANCEL作用
我们知道如果某一个子View处理了Down事件,那么随之而来的Move和Up事件也会交给它处理。但是交给它处理之前,父View还是可以拦截事件的,如果拦截了事件,那么子View就会收到一个Cancel事件,并且不会收到后续的Move和Up事件。