http://blog.csdn.net/lvxiangan/article/details/9309927
老實說,這兩個小東東實在是太麻煩了,很不好懂,我自己那api文檔都頭暈,在網上找到很多資料,才知道是怎么回事,這里總結一下,記住這個原則就會很清楚了:
理解:
1.在viewGroup里含有view時候,一個事件的序列: down-->move....move..... up 事件。
2. 在一個事件序列中,如果一個子view的onTouchEvent返回true, 下一個move事件會不會走其父view的onIterceptTouchEvent()方法?
答案:會的,move事件走onInterceptTounchEvent 是否返回--返回true,搶奪子view的move事件......。
--返回false,走子view的onTouchEvent();
本帖最后由 sun.shine 於 2013-3-22 18:17 編輯 1.首先明白一個常識:View 沒有onInterceptTouchEvent事件,而ViewGroup這三個事件都有,是viewgroup繼承View之后才加了一個方法叫onIntercepTouchEvent。 從字面意思可以看出,onInterceptTouchEvent是攔截器,用來攔截事件用的,dispatchTouchEvent是用來分發事件的,onTouchEvent是用來處理事件的。 大家不難看出,應該是先走dispatchTouchEvent然后走onTouchEvent。那OnInterceptTouchEven的調用時機是什么時候呢?
為了更好的理解這三個事件,我們從簡單到復雜,先從一個子view,一個viewgroup,然后viewgroup里有子view。 2.針對一個View來講,事件是先走該View的dispatchTouchEvent,然后再走onTouchEvent(也有可能不走)。 什么時候不會走onTouchEvent呢? 當重寫dispatchTouchEvent,不走super.dispatchTouchEvent直接返回false,它就不會走onTouchEvent。 當然這樣做是違反android架構常理的,一般的dispatchTouchEvent是不建議重寫的。不過通過這個案例我們可以總結出這么一個結論. 在事件到達view的時候,先走dispatchTouchEvent,在系統的dispatchTouchEvent中它會調用該view的Ontouch方法,
如果此onTouch方法的down事件里返回true,則dispatchTouchEvent方法也返回true,且把以后的move事件,up事件都傳給onTouch。
之后的move事件及up事件的返回值,onTouch返回什么dispatchTouchEvent也返回什么。 相反如果傳第一個down事件給ontouch的時候,ontouch返回的是false,從此事件不再會傳過來,也就是不會走dispatchTouchEvent。更不會走ontouchevent。
3.針對一個ViewGroup來講(沒有子view的時候): 事件的走向是dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent 我們會發現它們的邏輯跟view 的沒什么兩樣,只是在走down事件的時候onInterceptTouchEvent會在中間,而這里不管onInterceptTouchEvent返回什么
都不會干擾它像2.形容的那樣運行,難道onInterceptTouchEvent這個方法沒用?
4.當Viewgroup里有子view的時候 down事件走向:viewgroup.dispatchTouchEvent->viewgroup.onInterceptTouchEvent
->如果返回true->viewgroup.onTouch---------------------- -分支1
|->如果返回false->view.dispatchTouchEvent----------------分支2 分支1:之后的move或up事件的走向是:viewgroup.dispatchTouchEvent->viewgroup.ontouch 這里不管ontouch返回的是什么都是這個走向 分支2:down事件到了view.dispatchTouchEvent->view.onTouch->返回true->-----分支3 |->返回false->viewgroup.ontouch->返回true->move,up等事件viewgroup.dispatchTouchEvent-> viewgroup.ontouch |->返回false,則該viewgroup不會再收到后續事件了 分支3:子view的onTouch返回true了,表示子view能接受該事件,今后的事件走向是 Move: viewgroup.dispatchTouchEvent->viewgroup.onInterceptTouchEvent返回? 如果返回的是false,以后的move,up都這么走viewgroup.dispatchTouchEvent->viewgroup.onInterceptTouchEvent->view.dispatchTouchEvent->view.ontouch; 如果返回的是true,搶奪子view的move事件。 接下來的走向是:強制傳Cancel事件和UP事件給view,view.dispatchTouchEvent->view.ontouch(無視它返回什么)
->然后把Move事件留給viewgroup:viewgroup.dispatchTouchEvent->viewgroup.ontouch。
這個現象大家應該在listview或是scrollview里見過,就是當用戶在scrollview里按住一個按鈕,發現按鈕做了相應反應(按鈕高亮了),
但當按住不放拖它時,發現界面在滾動,這就是因為onInterceptTouchEvent搶事件了!
ViewGroup里的onInterceptTouchEvent默認值是false,這樣才能把事件傳給View里的onTouchEvent.
ViewGroup里的onTouchEvent默認值是false。
View里的onTouchEvent返回默認值是true.這樣才能執行多次touch事件。
概念介紹
1、onInterceptTouchEvent()是用於處理事件(類似於預處理,當然也可以不處理)並改變事件的傳遞方向,也就是決定是否允許Touch事件繼續向下(子控件)傳遞,一但返回True(代表事件在當前的viewGroup中會被處理),則向下傳遞之路被截斷(所有子控件將沒有機會參與Touch事件),同時把事件傳遞給當前的控件的onTouchEvent()處理;
2、onTouchEvent() 用於處理事件,返回值決定當前控件是否消費了這個事件,也就是說在當前控件在處理完Touch事件后,是否還允許Touch事件繼續向上(父控件)傳遞,一但返回True,則父控件不用操心自己來處理Touch事件。
返回false,則向上傳遞給父控件
1、onInterceptTouchEvent()是用於處理事件(重點onInterceptTouchEvent這個事件是從父控件開始往子控件傳的,直到有攔截 或者到沒有這個事件的view,然后就往回從子到父控件,這次是onTouch的)(類似於預處理,當然也可以不處理)並改變事件的傳遞方向,
也就是決定是否允許Touch事件繼續向下(子控件)傳遞,一但返回True(代表事件在當前的viewGroup中會被處理),則向下傳遞之路被截斷(所有子控件將沒有機會參與Touch事件),同時把事件傳遞給當前的控件的onTouchEvent()處理;
返回false,則把事件交給子控件的dispatchTouchEvent()
2、onTouchEvent()用於處理事件(重點onTouch這個事件是從子控件回傳到父控件的,一層層向下傳),返回值決定當前控件是否消費(consume)了這個事件,也就是說在當前控件在處理完Touch事件后,是否還允許Touch事件繼續向上(父控件)傳遞。
返回false,則向上傳遞給父控件,詳細一點就是這個touch事件就給了父控件,那么后面的up事件就是到這里touch觸發,不會在傳給它的子控件。
如果父控件依然是false,那touch的處理就給到父控件的父控件,那么up的事件處理都在父控件的父控件,不會觸發下面的。
返回true,如果是子控件返回true,那么它的touch事件都在這里處理,父控件是處理不了,因為它收不到子控件傳給他的touch,被子控件給攔截了。(這里啰嗦了這么多就是為了加深記憶,這個兩個事件理解起來都這么麻煩了,更何況去記, )
(注:可能你會覺得是否消費了有關系嗎,反正我已經針對事件編寫了處理代碼?答案是有區別!
比如ACTION_MOVE或者ACTION_UP發生的前提是一定曾經發生了ACTION_DOWN,如果你沒有消費ACTION_DOWN,那么系統會認為ACTION_DOWN沒有發生過,所以ACTION_MOVE或者ACTION_UP就不能被捕獲。)
詳細介紹
onInterceptTouchEvent()是ViewGroup的一個方法,目的是在系統向該ViewGroup及其各個childView觸發onTouchEvent()之前對相關事件進行一次攔截,
Android這么設計的想法也很好理解,由於ViewGroup會包含若干childView,因此需要能夠統一監控各種touch事件的機會,因此純粹的不能包含子view的控件是沒有這個方法的,如LinearLayout就有,TextView就沒有。
如果在ViewGroup里覆寫了該方法,那么就可以對各種touch事件加以攔截。但是如何攔截,是否所有的touch事件都需要攔截則是比較復雜的,touch事件在onInterceptTouchEvent()和onTouchEvent以及各個childView間的傳遞機制完全取決於onInterceptTouchEvent()和onTouchEvent()的返回值。
並且,針對down事件處理的返回值, 直接影響到后續move和up事件的接收和傳遞。
關於返回值的問題,基本規則很清楚,如果return true,那么表示該方法消費了此次事件,如果return false,那么表示該方法並未處理完全,該事件仍然需要以某種方式傳遞下去繼續等待處理。
onInterceptTouchEvent()是ViewGroup的一個方法,目的是在系統向該ViewGroup及其各個childView觸發onTouchEvent()之前對相關事件進行一次攔截.
-
down事件首先會傳遞到onInterceptTouchEvent()方法
-
如果該ViewGroup的onInterceptTouchEvent()在接收到down事件處理完成之后return false,那么后續的move, up等事件將繼續會先傳遞給該ViewGroup,之后才和down事件一樣傳遞給最終的目標view的 onTouchEvent()處理。
-
如果該ViewGroup的onInterceptTouchEvent()在接收到down事件處理完成之后return true,那么后續的move, up等事件將不再傳遞給onInterceptTouchEvent(),而是和down事件一樣傳遞給該ViewGroup的onTouchEvent()處理,注意,目標view將接收不到任何事件。
-
如果最終需要處理事件的view的onTouchEvent()返回了false,那么該事件將被傳遞至其上一層次的view的onTouchEvent()處理。
-
如果最終需要處理事件的view 的onTouchEvent()返回了true,那么后續事件將可以繼續傳遞給該view的onTouchEvent()處理。
僅僅看這個官方文檔解釋,就能理解清楚這兩個函數關系以及用途的絕對是富有經驗的framework高手。
否則,一定需要一個案例來闡釋。假設我們有這樣一個layout,非常典型的- <com.test.LayoutView1 xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical" android:layout_width="fill_parent"
- android:layout_height="fill_parent">
- <com.test.LayoutView2
- android:orientation="vertical" android:layout_width="fill_parent"
- android:layout_height="fill_parent" android:gravity="center">
- <com.test.MyTextView
- android:layout_width="wrap_content" android:layout_height="wrap_content"
- />
- </com.test.LayoutView2>
- </com.test.LayoutView1>
用一個示例圖來解釋這個layout:
通常外圍的layoutview1,layoutview2,只是布局的容器不需要響應觸屏的點擊事件,僅僅Mytextview需要相應點擊。但這只是一般情況,一些特殊的布局可能外圍容器也要響應,甚至不讓里面的mytextview去響應。更有特殊的情況是,動態更換響應對象。
那么首先看一下默認的觸屏事件的在兩個函數之間的傳遞流程。如下圖:如果僅僅想讓MyTextView來響應觸屏事件,讓MyTextView的OnTouchEvent返回true,那么事件流就變成如下圖,可以看到layoutview1,layoutview2已經不能進入OnTouchEvent:
另外一種情況,就是外圍容器想獨自處理觸屏事件,那么就應該在相應的onInterceptTouchEvent函數中返回true,表示要截獲觸屏事件,比如layoutview1作截獲處理,處理流變成如下圖:
以此類推,我們可以得到各種具體的情況,整個layout的view類層次中都有機會截獲,而且能看出來外圍的容器view具有優先截獲權。
當我們去做一些相對來講具有更復雜的觸屏交互效果的應用時候,經常需要動態變更touch event的處理對象,比如launcher待機桌面和主菜單(見下圖),從滑動屏幕開始到停止滑動過程當中,只有外圍的容器view才可以處理touch event,否則就會誤點擊上面的應用圖標或者widget.
-
反之在靜止不動的狀態下則需要能夠響應圖標(子view)的touch事件。摘取framework中abslistview代碼如下
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
if (touchMode == TOUCH_MODE_FLING) {
return true; //fling狀態,截獲touch,因為在滑動狀態,不讓子view處理
}
break;
}
case MotionEvent.ACTION_MOVE: {
switch (mTouchMode) {
case TOUCH_MODE_DOWN:
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
final int y = (int) ev.getY(pointerIndex);
if (startScrollIfNeeded(y - mMotionY)) {
return true;//開始滑動狀態,截獲touch事件,不讓子view處理
}
break;
}
break;
}
}總結:
僅僅通過概覽性的官方文檔是很難理解onInterceptTouchEvent函數的用途的,只有通過演繹這個抽象的規則,配以圖文才能獲取這個重要的知識。很顯然,默認是返回false,不做截獲。返回true之后,事件流的后端控件就沒有機會處理touch事件了,把默認的事件流中每個處理函數看作一個節點,這個節點只要返回true, 后續的事件就被截止了。
onInterceptTouchEvent是在ViewGroup里面定義的。Android中的layout布局類一般都是繼承此類的。onInterceptTouchEvent是用於攔截手勢事件的,每個手勢事件都會先調用onInterceptTouchEvent。
onInterceptTouchEvent()用於處理事件並改變事件的傳遞方向。
返回值為false時事件會傳遞給子控件的onInterceptTouchEvent();
返回值為true時事件會傳遞給當前控件的onTouchEvent(),而不在傳遞給子控件,這就是所謂的Intercept(截斷)。
onTouchEvent() 用於處理事件,返回值決定當前控件是否消費(consume)了這個事件。可能你要問是否消費了又區別嗎,反正我已經針對事件編寫了處理代碼?答案是有區別!比如ACTION_MOVE或者ACTION_UP發生的前提是一定曾經發生了ACTION_DOWN,如果你沒有消費ACTION_DOWN,那么系統會認為ACTION_DOWN沒有發生過,所以ACTION_MOVE或者ACTION_UP就不能被捕獲。
阻止事件和分發事件:
public class MyLinearLayout extends LinearLayout { public MyLinearLayout(Context context, AttributeSet attrs) { super(context, attrs); } /** * 中斷事件 */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return true; } /** * 分發事件 */ @Override public boolean dispatchTouchEvent(MotionEvent ev) { return super.dispatchTouchEvent(ev); } /** * 實現多個ListView控件同時觸發事件 */ @Override public boolean onTouchEvent(MotionEvent event) { int width=getWidth()/getChildCount(); int height = getHeight(); int count=getChildCount(); float eventX = event.getX(); if (eventX<width){ // 滑動左邊的 listView event.setLocation(width/2, event.getY()); getChildAt(0).dispatchTouchEvent(event);//移動位置后,分發事件 return true; } else if (eventX > width && eventX < 2 * width) { //滑動中間的 listView float eventY = event.getY(); if (eventY < height / 2) { event.setLocation(width / 2, event.getY()); for (int i = 0; i < count; i++) { View child = getChildAt(i); try { child.dispatchTouchEvent(event); } catch (Exception e) { e.printStackTrace(); } } return true; } else if (eventY > height / 2) { event.setLocation(width / 2, event.getY()); try { getChildAt(1).dispatchTouchEvent(event); } catch (Exception e) { e.printStackTrace(); } return true; } }else if (eventX>2*width){ event.setLocation(width/2, event.getY()); getChildAt(2).dispatchTouchEvent(event); return true; } return true; } }