Android:30分鐘弄明白Touch事件分發機制

轉載: 原文地址  點擊打開連接 http://www.cnblogs.com/linjzong/p/4191891.htmlhtml

PS: 對照原文, 添加了一些文字, 表達更清晰(對本人來講)函數

---------------------------------------------質樸的分割線---------------------------------------------spa

Touch事件分發中只有兩個主角:ViewGroup和View。Activity的Touch事件事實上是調用它內部的ViewGroup的Touch事件,能夠直接當成ViewGroup處理。code

View在ViewGroup內,ViewGroup也能夠在其餘ViewGroup內,這時候把內部的ViewGroup當成View來分析。htm

ViewGroup的相關事件有三個:onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent。對象

View的相關事件只有兩個:dispatchTouchEvent、onTouchEvent。blog

先分析ViewGroup的處理流程:首先得有個結構模型概念:ViewGroup和View組成了一棵樹形結構,最頂層爲Activity的ViewGroup,下面有若干的ViewGroup節點,每一個節點之下又有若干的ViewGroup節點或者View節點,依次類推。如圖:繼承

 

當一個Touch事件(觸摸事件爲例)到達根節點,即Acitivty的ViewGroup時,它會依次下發,下發的過程是調用子View(ViewGroup)的dispatchTouchEvent方法實現的。簡單來講,就是ViewGroup遍歷它包含着的子View,調用每一個View的dispatchTouchEvent方法,而當子View爲ViewGroup時,又會經過調用ViwGroup的dispatchTouchEvent方法繼續調用其內部的子View的dispatchTouchEvent方法。遞歸

上述例子中的消息下發順序是這樣的:①-②-⑤-⑥-⑦-③-④。事件

dispatchTouchEvent方法只負責事件的分發,它擁有boolean類型的返回值,當返回爲true時,順序下發會中斷。

在上述例子中若是⑤的dispatchTouchEvent返回結果爲true,那麼⑥-⑦-③-④將都接收不到本次Touch事件。

來個簡單版的代碼加深理解:

/** 
  * ViewGroup 
  * @param ev 
  * @return 
  */  
 public boolean dispatchTouchEvent(MotionEvent ev){  
     ....//其餘處理,在此無論  
     View[] views=getChildView();  
     for(int i=0;i<views.length;i++){  
        //判斷下Touch到屏幕上的點在該子View上面   
         if(...){  
         if(views[i].dispatchTouchEvent(ev))  
           return true;  
          }  
     }  
     ...//其餘處理,在此無論  
 }  
 /** 
  * View    (本人理解 2016年12月26日15:50:29)若是該View在點擊座標範圍以內,那麼就是這個View獲取到這個Touch事件 
  *          由於View裏面不可能包含子View了(由於一個普通的View確定是位於最深層的View),  
  *     若是有其餘View重疊在一塊兒的狀況,  
  *          那麼我猜想源碼會在ViewGroup的dispathTouchEvent()方法中作那個View是否在最上面的判斷,       
  *          還有就是View是否隱藏和顯示的判斷-----這是細節, 不影響理解 
  * @param ev 
  * @return 
  */  
 public boolean dispatchTouchEvent(MotionEvent ev){  
     ....//其餘處理,在此無論  
     return false;  
 }  

在此能夠看出,ViewGroup的dispatchTouchEvent是真正在執行「分發」工做,而View的dispatchTouchEvent方法,並不執行分發工做,或者說它分發的對象就是本身,決定是否把touch事件交給本身處理,而處理的方法,即是onTouchEvent事件,事實上子View的dispatchTouchEvent方法真正執行的代碼是這樣的

/** 
  * View 
  * @param ev 
  * @return 
  */  
 public boolean dispatchTouchEvent(MotionEvent ev){  
     ....//其餘處理,在此無論  
     return onTouchEvent(event);  
 } 

通常狀況下,咱們不應在普通View內重寫dispatchTouchEvent方法,由於它並不執行分發邏輯。當Touch事件到達View時,咱們該作的就是是否在onTouchEvent事件中處理它。

那麼,ViewGroup的onTouchEvent事件是何時處理的呢?當ViewGroup全部的子View都返回false時,onTouchEvent事件便會執行。因爲ViewGroup是繼承於View的,它其實也是經過調用View的dispatchTouchEvent方法來執行onTouchEvent事件。

 

在目前的狀況看來,彷佛只要咱們把全部的onTouchEvent都返回false,就能保證全部的子控件都響應本次Touch事件了。

但必需要說明的是,這裏的Touch事件,只限於Acition_Down事件,即觸摸按下事件,而Aciton_UP和Action_MOVE卻不會執行。

事實上,一次完整的Touch事件,應該是由一個Down、一個Up和若干個Move組成的。Down方式經過dispatchTouchEvent分發,分發的目的是爲了找到真正須要處理完整Touch請求的View。當某個View或者ViewGroup的onTouchEvent事件返回true時,便表示它是真正要處理此次請求的View,以後的Aciton_UP和Action_MOVE將由它處理。當全部子View的onTouchEvent都返回false時,此次的Touch請求就由根ViewGroup,即Activity本身處理了。

看看改進後的ViewGroup的dispatchTouchEvent方法

View mTarget=null;//保存捕獲Touch事件處理的View  
    public boolean dispatchTouchEvent(MotionEvent ev) {  
  
  
        //....其餘處理,在此無論  
          
        if(ev.getAction()==KeyEvent.ACTION_DOWN){  
            //每次Down事件,都置爲Null  
  
            if(!onInterceptTouchEvent()){  
            mTarget=null;  
            View[] views=getChildView();  
            for(int i=0;i<views.length;i++){  
                if(views[i].dispatchTouchEvent(ev))  
                    mTarget=views[i];  
                    return true;  
            }  
          }  
        }  
        //當子View沒有捕獲down事件時,ViewGroup自身處理。這裏處理的Touch事件包含Down、Up和Move  
        if(mTarget==null){  
            return super.dispatchTouchEvent(ev);  
        }  
        //...其餘處理,在此無論  
        if(onInterceptTouchEvent()){  
  
  
         //...其餘處理,在此無論      
         }  
         //這一步在Action_Down中是不會執行到的,只有Move和UP纔會執行到。  
        return mTarget.dispatchTouchEvent(ev);  
  
    }  

ViewGroup還有個onInterceptTouchEvent,看名字便知道這是個攔截事件。這個攔截事件須要分兩種狀況來講明:

1.假如咱們在某個ViewGroup的onInterceptTouchEvent中,將Action爲Down的Touch事件返回true,那便表示將該ViewGroup的全部下發操做攔截掉,這種狀況下,mTarget會一直爲null,由於mTarget是在Down事件中賦值的。因爲mTarge爲null,該ViewGroup的onTouchEvent事件被執行。這種狀況下能夠把這個ViewGroup直接當成View來對待。

2.假如咱們在某個ViewGroup的onInterceptTouchEvent中,將Acion爲Down的Touch事件都返回false,其餘的都返回True,

這種狀況下,Down事件能正常分發:

   (1)若子View的 dispatchTouchEvent 都返回false,那mTarget仍是爲空,無影響。

   (2)若某個子View返回了true,mTarget被賦值了,在Action_Move和Aciton_UP分發到該ViewGroup時,便會給mTarget分發一個Action_Delete的MotionEvent,同時清空mTarget的值,使得接下去的Action_Move(若是上一個操做不是UP)將由ViewGroup的onTouchEvent處理。

 

狀況一用到的比較多,狀況二我的還未找到使用場景。

從頭至尾總結一下:

1.Touch事件分發中只有兩個主角:ViewGroup和View。ViewGroup包含onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent三個相關事件。View包含dispatchTouchEvent、onTouchEvent兩個相關事件。其中ViewGroup又繼承於View。

2.ViewGroup和View組成了一個樹狀結構,根節點爲Activity內部包含的一個ViwGroup。

3.觸摸事件由Action_Down、Action_Move、Aciton_UP組成,其中一次完整的觸摸事件中,Down和Up都只有一個,Move有若干個, 能夠爲0個。

4.當Acitivty接收到Touch事件時,將遍歷子View進行Down事件的分發。ViewGroup的遍歷能夠當作是遞歸的。分發的目的是爲了找到真正要處理本次完整觸摸事件的View,這個View會在onTouchuEvent結果返回true。

5.當某個子View返回true時,會停止Down事件的分發,同時在ViewGroup中記錄該子View。接下去的Move和Up事件將由該子View直接進行處理。因爲子View是保存在ViewGroup中的,多層ViewGroup的節點結構時,上級ViewGroup保存的會是真實處理事件的View所在的ViewGroup對象:如ViewGroup0-ViewGroup1-TextView的結構中,TextView返回了true,它將被保存在ViewGroup1中,而ViewGroup1也會返回true,被保存在ViewGroup0中。當Move和UP事件來時,會先從ViewGroup0傳遞至ViewGroup1,再由ViewGroup1傳遞至TextView。

6.當ViewGroup中全部子View都不捕獲Down事件時,將觸發ViewGroup自身的Touch事件(原文爲 onTouch)。觸發的方式是調用super.dispatchTouchEvent函數,即父類View的dispatchTouchEvent方法。在全部子View都不處理的狀況下,觸發Acitivity的onTouchEvent方法。

7.onInterceptTouchEvent有兩個做用:1.攔截Down事件的分發。2.停止Up和Move事件向目標View傳遞,使得目標View所在的ViewGroup捕獲Up和Move事件。

 

另外,上文所列出的代碼並不是真正的源碼,只是歸納了源碼在事件分發處理中的核心處理流程,真正源碼各位能夠本身去看,包含了更豐富的內容。

 補充:

「觸摸事件由Action_Down、Action_Move、Aciton_UP組成,其中一次完整的觸摸事件中,Down和Up都只有一個,Move有若干個,能夠爲0個。」,這裏補充下其實UP事件是可能爲0個的。

 

最近恰好在作一個手勢放大縮小移動圖片的Demo,對此有了更多的理解。對於onInterceptTouchEvent事件,它的應用場景在不少帶scroll效果的ViewGroup中都有體現。設想一下再一個ViewPager中,每一個Item都是個ImageView,咱們須要對這些ImageView作Matrix操做,這不可避免要捕獲掉Touch事件,可是咱們又須要作到不影響ViewPager翻頁效果,這又必須保證ViewPager能捕獲到Move事件,因而,ViewPager的onInterceptTouchEvent會對Move事件作一個過濾,當適當條件的Move事件(持續若干事件或移動若干距離,這裏我沒讀源碼只是猜想)觸發時,並會攔截掉,返回子View一個Action_Cancel事件。這個時候子View就沒有Up事件了,不少須要在Up中處理的事物要轉到Cancel中處理。

相關文章
相關標籤/搜索