概念
同一個事件序列指的是從手指觸控螢幕的那一刻開始,到手指離開螢幕的那一刻結束,在這個過程產生的一系列事件。以down事件開始,可能經過n多個move事件,最終以up事件結束。
案例
自定義三個佈局
<com.yolo.myapplication.view.FirstFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_view_demo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/holo_blue_bright"
tools:context="com.yolo.myapplication.view.ViewDemoActivity">
<com.yolo.myapplication.view.SecondRelativeLayout
android:layout_width="300dp"
android:layout_height="300dp"
android:layout_gravity="center"
android:background="@android:color/holo_green_dark">
<com.yolo.myapplication.view.ThirdTextView
android:layout_width="100dp"
android:layout_height="100dp"
android:clickable="true"
android:background="@android:color/holo_red_light"
android:layout_centerInParent="true" />
</com.yolo.myapplication.view.SecondRelativeLayout>
</com.yolo.myapplication.view.FirstFrameLayout>複製程式碼
情況一
預設情況,未設定返回true或false,事件都是return super.onXXX(event);
I/Activity: dispatchTouchEvent>>DOWN
I/FirstFrameLayout: dispatchTouchEvent>>DOWN
I/FirstFrameLayout: onInterceptTouchEvent>>>>DOWN
I/SecondRelativeLayout: dispatchTouchEvent>>DOWN
I/SecondRelativeLayout: onInterceptTouchEvent>>>>DOWN
I/ThirdTextView: dispatchTouchEvent>>DOWN
I/ThirdTextView: onTouchEvent>>>>DOWN
I/SecondRelativeLayout: onTouchEvent>>>>DOWN
I/FirstFrameLayout: onTouchEvent>>>>DOWN
I/Activity: onTouchEvent>>>>DOWN
I/Activity: dispatchTouchEvent>>UP
I/Activity: onTouchEvent>>>>UP複製程式碼
預設情況下,某個View一旦開始處理事件,如果事件序列中DOWN事件未被消耗,將事件重新交給它的父元素去處理,且事件序列中的其他事件則不會交給它來處理。
情況二
ThirdTextView的onTouchEvent返回true。
I/FirstFrameLayout: dispatchTouchEvent>>DOWN
I/FirstFrameLayout: onInterceptTouchEvent>>>>DOWN
I/SecondRelativeLayout: dispatchTouchEvent>>DOWN
I/SecondRelativeLayout: onInterceptTouchEvent>>>>DOWN
I/ThirdTextView: dispatchTouchEvent>>DOWN
I/ThirdTextView: onTouchEvent>>>>DOWN
I/FirstFrameLayout: dispatchTouchEvent>>UP
I/FirstFrameLayout: onInterceptTouchEvent>>>>UP
I/SecondRelativeLayout: dispatchTouchEvent>>UP
I/SecondRelativeLayout: onInterceptTouchEvent>>>>UP
I/ThirdTextView: dispatchTouchEvent>>UP
I/ThirdTextView: onTouchEvent>>>>UP複製程式碼
圖例:
結論:
正常情況下,一個事件序列只能被一個View攔截且消耗。
View的OnTouchEvent的事件預設都會消耗事件(返回true),因此未發生回傳的現象。如果View是不可點選的(clickable和longClickable同時為false),則返回false。
注意:
View的longClickable預設都為false,clickable屬性分情況:如Button的clickable屬性預設為true,TextView的clickable屬性預設為false。
View的enable屬性不影響onTouchEvent預設返回值。clickable和longClickable屬性會影響。
情況三
FristFrameLayout的onTouchEvent返回true
I/Activity: dispatchTouchEvent>>DOWN
I/FirstFrameLayout: dispatchTouchEvent>>DOWN
I/FirstFrameLayout: onInterceptTouchEvent>>>>DOWN
I/SecondRelativeLayout: dispatchTouchEvent>>DOWN
I/SecondRelativeLayout: onInterceptTouchEvent>>>>DOWN
I/ThirdTextView: dispatchTouchEvent>>DOWN
I/ThirdTextView: onTouchEvent>>>>DOWN
I/SecondRelativeLayout: onTouchEvent>>>>DOWN
I/FirstFrameLayout: onTouchEvent>>>>DOWN
I/Activity: dispatchTouchEvent>>UP
I/FirstFrameLayout: dispatchTouchEvent>>UP
I/FirstFrameLayout: onTouchEvent>>>>UP複製程式碼
圖例:
- 結論:
如果View一旦開始處理事件,如果它不消費ACTION_DOWN事件,那麼同一個事件序列的其他事件也不會再交給它,並且重新交給它的父元素去處理。例如紅線的down事件流向。
情況四
SecondRelativeLayout 攔截事件,onInterceptTouchEvent返回true
I/Activity: dispatchTouchEvent>>DOWN
I/FirstFrameLayout: dispatchTouchEvent>>DOWN
I/FirstFrameLayout: onInterceptTouchEvent>>>>DOWN
I/SecondRelativeLayout: dispatchTouchEvent>>DOWN
I/SecondRelativeLayout: onInterceptTouchEvent>>>>DOWN
I/SecondRelativeLayout: onTouchEvent>>>>DOWN
I/FirstFrameLayout: onTouchEvent>>>>DOWN
I/Activity: onTouchEvent>>>>DOWN
I/Activity: dispatchTouchEvent>>UP
I/Activity: onTouchEvent>>>>UP複製程式碼
SecondRelativeLayout 攔截事件,onInterceptTouchEvent返回true,且onTouchEvent返回true
I/FirstFrameLayout: dispatchTouchEvent>>DOWN
I/FirstFrameLayout: onInterceptTouchEvent>>>>DOWN
I/SecondRelativeLayout: dispatchTouchEvent>>DOWN
I/SecondRelativeLayout: onInterceptTouchEvent>>>>DOWN
I/SecondRelativeLayout: onTouchEvent>>>>DOWN
I/FirstFrameLayout: dispatchTouchEvent>>UP
I/FirstFrameLayout: onInterceptTouchEvent>>>>UP
I/SecondRelativeLayout: dispatchTouchEvent>>UP
I/SecondRelativeLayout: onTouchEvent>>>>UP複製程式碼
結論:
View一旦決定攔截,那麼事件序列只能由它來處理,並且它的onInterceptTouchEvent不會被呼叫。比如SecondRelativeLayout攔截了Down事件,那麼事件序列中的Up直接交給它處理,並且不會再去執行onInterceptTouchEvent。
注意:
事件傳遞由外向內,即事件總是縣傳遞到父元素,然後再由父元素分發給子View,通過requestDisallowInterceptTouchEvent
方法可以在子元素中干預父元素的事件分發過程。但是ACTION_DOWN事件除外。
注意
- 當一個View需要處理事件時,如果它設定了OnTouchListener,那麼這個介面的onTouch方法會被回撥。如果onTouch方法返回true,則onTouchEvent不會被呼叫。因此OnTouchListener優先順序高於onTouchEvent。
OnTouchListener > onTouchEvent > OnClickListener
View攔截了事件,那麼事件序列中的其餘事件則直接跳過onInterceptTouchEvent方法去執行onTouchEvent方法。是因為mFirstTouchTarget != null 的標記。
// 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. cancelAndClearTouchTargets(ev); resetTouchState(); } final boolean intercepted; //如果子View處理了事件,mFirstTouchTarget != null 成立,為true。 //且此時傳遞的是事件序列的其餘事件,不是Down事件,因此整個條件不成立。 //不會執行onInterceptTouchEvent 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; }複製程式碼
FLAG_DISALLOW_INTERCEPT標記位。
這個標記位可以通過requestDisallowInterceptTouchEvent方法來設定。一般用於子View中,設定後,ViewGroup將無法攔截除了ACTION_DOWN以外的事件。ViewGroup在分發事件的時候,會重置此標記位。將導致子View設定這個標記位無效。
滑動衝突解決
一般採用相對簡單的外部攔截法進行處理。
虛擬碼:
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercepted = false;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
intercepted = false;
//TODO
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYIntercept;
//攔截條件:如果X軸偏移量大於Y軸,則表示水平滑動,進行攔截處理。
if (Math.abs(deltaX) > Math.abs(deltaY)) {
intercepted = true;
} else {
intercepted = false;
}
break;
}
case MotionEvent.ACTION_UP: {
intercepted = false;
break;
}
default:
break;
}
mLastXIntercept = x;
mLastYIntercept = y;
return intercepted;
}複製程式碼