View的事件分發機制和滑動衝突解決總結
Touch事件處理和傳遞
-
事件分類:
ACTION_DOWN, ACTION_UP, ACTION_MOVE:按下、離開、移動
ACTION_POINTER_DOWN, ACTION_POINTER_UP:多點按下、離開
ACTION_CANCEL:當控制元件收到前驅事件(前驅事件: 一個從DOWN一直到UP的所有事件組合稱為完整的手勢,中間的任意一次事件對於下一個事件而言就是它的前驅事件)之後,後面的事件如果被父控制元件攔截,那麼當前控制元件就會收到一個CANCEL事件,並且把這個事件會傳遞給它的子View,沒有子View就呼叫父容器的dispatchTouchEvent()。
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; }
ViewGroup的dispatchTransformedTouchEvent方法對ACTION_CANCEL的處理
-
事件處理方法:
-
傳遞—-dispatchTouchEvent():
只要事件傳遞到了當前View,那麼dispatchTouchEcent方法就一定會被呼叫。返回結果表示是否消耗當前事件。
-
攔截——onInterceptTouchEvent():
在dispatchTouchEcent方法內部呼叫此方法,用來判斷是否攔截某個事件。如果當前View攔截了某個事件,那麼在這同一個事件序列中,此方法不會再次被呼叫。返回結果表示是否攔截當前事件。
-
消費—-onTouchEvent()函式和OnTouchListener():
在dispatchTouchEcent方法內呼叫此方法,用來處理事件。返回結果表示是否處理當前事件,如果不處理,那麼在同一個事件序列裡面,當前View無法再收到後續的事件。
-
-
Android事件傳遞結論:
- 事件都是從Activity.dispatchTouchEvent()開始傳遞
- 事件由父View傳遞給子View,ViewGroup可以通過onInterceptTouchEvent()方法對事件攔截,停止其向子view傳遞
- 某個View一旦決定攔截事件,那麼這個事件序列之後的事件都會由它來處理,並且不會再呼叫onInterceptTouchEvent。
- 如果View沒有對ACTION_DOWN進行消費,之後的其他事件不會傳遞過來,也就是說ACTION_DOWN必須返回true,之後的事件才會傳遞進來
- 如果一個View處理了down事件,卻沒有處理其他事件,那麼這些事件不會交給父元素處理,並且這個View還能繼續受到後續的事件。而這些未處理的事件,最終會交給Activity來處理。
- ViewGroup的onInterceptToucheEvent預設返回false,也就是預設不攔截事件。
- View沒有InterceptTouchEvent方法,如果有事件傳過來,就會直接呼叫onTouchEvent方法。
- View的enable屬性不會影響onTouchEvent的預設返回值。就算一個View是不可見的,只要他是可點選的(clickable或者longClickable有一個為true),它的onTouchEvent預設返回值也是true。
- 如果事件從上往下傳遞過程中一直沒有被停止,且最底層子View沒有消費事件,事件會反向往上傳遞,這時父View(ViewGroup)可以進行消費,如果還是沒有被消費的話,最後會到Activity的onTouchEvent()函式。
- OnTouchListener優先於onTouchEvent()對事件進行消費
-
View攔截處理事件的流程,偽程式碼表示:
public boolean dispatchTouchEvent(MotionEvent ev){ boolean consum = false; if(onInterceptTouchEvent(ev)){//判斷是否攔截 consum = onTouchEvent(ev);//處理事件 }else{//不攔截,則交給子View的dispatchTouchEvent()方法處理, consum = child.dispatchTouchEvent(ev); } return consum; }
滑動衝突解決
外部攔截法:
-
事件都經過父容器的攔截處理,如果父容器需要則攔截,如果不需要則不攔截。
-
改寫父容器的onInterceptTouchEvent(MotionEvent event)方法,確定是否攔截事件:
-
ACTION_DOWN事件返回false,使事件可以傳遞給子View。
-
ACTION_MOVE事件判斷攔截時返回true,不攔截返回false。
-
ACTION_UP事件返回false,否則當子View處理時,無法處理click事件。
-
-
Example:
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; break; } case MotionEvent.ACTION_MOVE: { if (滿足父容器的攔截要求) { intercepted = true; } else { intercepted = false; } break; } case MotionEvent.ACTION_UP: { intercepted = false; break; } default: break; } return intercepted; }
內部處理法:
-
父容器不攔截任何事件,將所有事件傳遞給子元素,如果子元素需要則消耗掉,如果不需要則通過requestDisallowInterceptTouchEvent方法交給父容器處理。
-
重寫父容器的onInterceptTouchEvent方法
-
重寫子View的dispatchTouchEvent(MotionEvent event)方法
-
Example:
//重寫父容器的onInterceptTouchEvent public boolean onInterceptTouchEvent(MotionEvent event) { int action = event.getAction(); if (action == MotionEvent.ACTION_DOWN) {//不處理DOWN事件,全部交給子View判斷 return false; } else { return true; } }
//重寫子View的dispatchTouchEvent方法 public boolean dispatchTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { //父Viewgroup不處理onInterceptTouchEvent回撥 parent.requestDisallowInterceptTouchEvent(true); break; } case MotionEvent.ACTION_MOVE: { int deltaX = x - mLastX; int deltaY = y - mLastY; if (父容器需要此類點選事件) { //需要父Viewgroup處理onInterceptTouchEvent回撥,攔截事件 parent.requestDisallowInterceptTouchEvent(false); } break; } case MotionEvent.ACTION_UP: { break; } default: break; } mLastX = x; mLastY = y; return super.dispatchTouchEvent(event); }