一、ACTION_CANCEL在這些時(shí)候會(huì)觸發(fā)
1、父view攔截事件
首先要了解ViewGroup什么情況下會(huì)攔截事件,請(qǐng)看下面一段代碼:
@Overridepublic 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; ... ...
可以看出有兩個(gè)條件:
MotionEvent.ACTION_DOWN事件或者mFirstTouchTarget非空也就是有子view在處理事件子view沒有做攔截,也就是沒有調(diào)用ViewParent#requestDisallowInterceptTouchEvent(true)如果滿足上面的兩個(gè)條件才會(huì)執(zhí)行onInterceptTouchEvent(ev)。如果ViewGroup攔截了事件,則intercepted變量為true,接著往下看:
@Overridepublic 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) // 當(dāng)mFirstTouchTarget != null,也就是子view處理了事件 // 此時(shí)如果父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 // 判斷一:此時(shí)cancelChild == true final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;// 判斷二:給child發(fā)送cancel事件 if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) handled = true; ... ... return handled;
以上判斷一處cancelChild為true,然后進(jìn)入判斷二中一看究竟:
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設(shè)置成ACTION_CANCEL event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) ... else // 分發(fā)給child handled = child.dispatchTouchEvent(event); event.setAction(oldAction); return handled;
當(dāng)參數(shù)cancel為ture時(shí)會(huì)將event設(shè)置為MotionEvent.ACTION_CANCEL,然后分發(fā)給child。
2、ACTION_DOWN初始化操作
首先請(qǐng)看一段代碼:
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目標(biāo) cancelAndClearTouchTargets(ev); resetTouchState(); ... ...
系統(tǒng)可能會(huì)由于App切換、ANR等原因丟失了up,cancel事件。因此需要在ACTION_DOWN時(shí)丟棄掉所有前面的狀態(tài),具體代碼如下:
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); // 分發(fā)事件同情況一 dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits); ...
PS:在dispatchDetachedFromWindow()中也會(huì)調(diào)用cancelAndClearTouchTargets()
3、在子View處理事件的過程中被從父View中移除時(shí)
請(qǐng)看下面這段代碼:
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) ... // 創(chuàng)建ACTION_CANCEL事件 MotionEvent event = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); event.setSource(InputDevice.SOURCE_TOUCHSCREEN); 分發(fā)給目標(biāo)view view.dispatchTouchEvent(event); event.recycle(); return; predecessor = target; target = next;
4、子View被設(shè)置了PFLAG_CANCEL_NEXT_UP_EVENT標(biāo)記時(shí)
請(qǐng)看下面這段代碼,在情況一種的兩個(gè)判斷處:
// 判斷一:此時(shí)cancelChild == truefinal boolean cancelChild = resetCancelNextUpFlag(target.child)|| intercepted;// 判斷二:給child發(fā)送cancel事件if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) handled = true;
當(dāng)?resetCancelNextUpFlag(target.child)?為true時(shí)同樣也會(huì)導(dǎo)致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范圍會(huì)發(fā)生什么
通常來說,滑出子View范圍什么也不會(huì)發(fā)生。如果手指移出了子View之外,從而導(dǎo)致事件序列被取消,那么通常不會(huì)有太多事情發(fā)生。您的應(yīng)用程序?qū)?huì)收到一個(gè)ACTION_CANCEL事件,但是由于事件已經(jīng)被取消,您無法執(zhí)行任何進(jìn)一步的操作。如果您希望避免這種情況發(fā)生,您可以嘗試使用requestDisallowInterceptTouchEvent()方法來防止觸摸事件序列被攔截,或者重新設(shè)計(jì)您的UI以確保用戶不會(huì)意外地移動(dòng)手指到View的范圍外。
延伸閱讀1:ACTION_CANCEL作用
我們知道如果某一個(gè)子View處理了Down事件,那么隨之而來的Move和Up事件也會(huì)交給它處理。但是交給它處理之前,父View還是可以攔截事件的,如果攔截了事件,那么子View就會(huì)收到一個(gè)Cancel事件,并且不會(huì)收到后續(xù)的Move和Up事件。