view之onInterceptTouchEvent和onTouchEvent調用關系詳解


 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()之前對相關事件進行一次攔截.

  1. down事件首先會傳遞到onInterceptTouchEvent()方法

  2. 如果該ViewGroup的onInterceptTouchEvent()在接收到down事件處理完成之后return false,那么后續的move, up等事件將繼續會先傳遞給該ViewGroup,之后才和down事件一樣傳遞給最終的目標view的 onTouchEvent()處理。

  3. 如果該ViewGroup的onInterceptTouchEvent()在接收到down事件處理完成之后return true,那么后續的move, up等事件將不再傳遞給onInterceptTouchEvent(),而是和down事件一樣傳遞給該ViewGroup的onTouchEvent()處理,注意,目標view將接收不到任何事件。

  4. 如果最終需要處理事件的view的onTouchEvent()返回了false,那么該事件將被傳遞至其上一層次的view的onTouchEvent()處理。

  5. 如果最終需要處理事件的view 的onTouchEvent()返回了true,那么后續事件將可以繼續傳遞給該view的onTouchEvent()處理。 

    僅僅看這個官方文檔解釋,就能理解清楚這兩個函數關系以及用途的絕對是富有經驗的framework高手。
    否則,一定需要一個案例來闡釋。假設我們有這樣一個layout,非常典型的

    1. <com.test.LayoutView1 xmlns:android="http://schemas.android.com/apk/res/android"  
    2.     android:orientation="vertical" android:layout_width="fill_parent"  
    3.     android:layout_height="fill_parent">  
    4.     <com.test.LayoutView2  
    5.         android:orientation="vertical" android:layout_width="fill_parent"  
    6.         android:layout_height="fill_parent" android:gravity="center">  
    7.         <com.test.MyTextView  
    8.             android:layout_width="wrap_content"   android:layout_height="wrap_content"  
    9.       />  
    10.     </com.test.LayoutView2>  
    11. </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.

  6. 反之在靜止不動的狀態下則需要能夠響應圖標(子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;
    }
    
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM