天天看點

View,ViewGroup的Touch事件的分發機制

原帖位址:http://blog.csdn.net/xiaanming/article/details/21696315

viewgroup的事件分發機制

我們用手指去觸摸android手機螢幕,就會産生一個觸摸事件,但是這個觸摸事件在底層是怎麼分發的呢?這個我還真不知道,這裡涉及到操作硬體(手機螢幕)方面的知識,也就是linux核心方面的知識,我也沒有了解過這方面的東西,是以我們可能就往上層來分析分析,我們知道android中負責與使用者互動,與使用者操作緊密相關的四大元件之一是activity, 是以我們有理由相信activity中存在分發事件的方法,這個方法就是dispatchtouchevent(),我們先看其源碼吧

View,ViewGroup的Touch事件的分發機制

public boolean dispatchtouchevent(motionevent ev) {  

        //如果是按下狀态就調用onuserinteraction()方法,onuserinteraction()方法  

        //是個空的方法, 我們直接跳過這裡看下面的實作  

        if (ev.getaction() == motionevent.action_down) {  

            onuserinteraction();  

        }  

        if (getwindow().superdispatchtouchevent(ev)) {  

            return true;  

        //getwindow().superdispatchtouchevent(ev)傳回false,這個事件就交給activity  

        //來處理, activity的ontouchevent()方法直接傳回了false  

        return ontouchevent(ev);  

    }  

這個方法中我們還是比較關心getwindow()的superdispatchtouchevent()方法,getwindow()傳回目前activity的頂層視窗window對象,我們直接看window api的superdispatchtouchevent()方法

View,ViewGroup的Touch事件的分發機制

/** 

     * used by custom windows, such as dialog, to pass the touch screen event 

     * further down the view hierarchy. application developers should 

     * not need to implement or call this. 

     * 

     */  

    public abstract boolean superdispatchtouchevent(motionevent event);  

這個是個抽象方法,是以我們直接找到其子類來看看superdispatchtouchevent()方法的具體邏輯實作,window的唯一子類是phonewindow,我們就看看phonewindow的superdispatchtouchevent()方法

View,ViewGroup的Touch事件的分發機制

public boolean superdispatchtouchevent(keyevent event) {  

        return mdecor.superdispatctouchevent(event);  

裡面直接調用decorview類的superdispatchtouchevent()方法,或許很多人不了解decorview這個類,decorview是phonewindow的一個final的内部類并且繼承framelayout的,也是window界面的最頂層的view對象,這是什麼意思呢?别着急,我們接着往下看

我們先建立一個項目,取名androidtouchevent,然後直接用模拟器運作項目, mainactivity的布局檔案為

View,ViewGroup的Touch事件的分發機制

<relativelayout xmlns:android="http://schemas.android.com/apk/res/android"  

    xmlns:tools="http://schemas.android.com/tools"  

    android:layout_width="match_parent"  

    android:layout_height="match_parent"  

    tools:context=".mainactivity" >  

    <textview  

        android:layout_width="wrap_content"  

        android:layout_height="wrap_content"  

        android:layout_centerhorizontal="true"  

        android:layout_centervertical="true"  

        android:text="@string/hello_world" />  

</relativelayout>  

利用hierarchyviewer工具來檢視下mainactivity的view的層次結構,如下圖

View,ViewGroup的Touch事件的分發機制

我們看到最頂層就是phonewindow$decorview,接着decorview下面有一個linearlayout, linearlayout下面有兩個framelayout

上面那個framelayout是用來顯示标題欄的,這個demo中是一個textview,當然我們還可以定制我們的标題欄,利用getwindow().setfeatureint(window.feature_custom_title,r.layout.xxx); xxx就是我們自定義标題欄的布局xml檔案

下面的framelayout是用來裝載contentview的,也就是我們在activity中利用setcontentview()方法設定的view,現在我們知道了,原來我們利用setcontentview()設定activity的view的外面還嵌套了這麼多的東西

我們來理清下思路,activity的最頂層窗體是phonewindow,而phonewindow的最頂層view是decorview,接下來我們就看decorview類的superdispatchtouchevent()方法

View,ViewGroup的Touch事件的分發機制

public boolean superdispatchtouchevent(motionevent event) {  

            return super.dispatchtouchevent(event);  

在裡面調用了父類framelayout的dispatchtouchevent()方法,而framelayout中并沒有dispatchtouchevent()方法,是以我們直接看viewgroup的dispatchtouchevent()方法

View,ViewGroup的Touch事件的分發機制

    * {@inheritdoc} 

    */  

   @override  

   public boolean dispatchtouchevent(motionevent ev) {  

       final int action = ev.getaction();  

       final float xf = ev.getx();  

       final float yf = ev.gety();  

       final float scrolledxfloat = xf + mscrollx;  

       final float scrolledyfloat = yf + mscrolly;  

       final rect frame = mtemprect;  

       //這個值預設是false, 然後我們可以通過requestdisallowintercepttouchevent(boolean disallowintercept)方法  

       //來改變disallowintercept的值  

       boolean disallowintercept = (mgroupflags & flag_disallow_intercept) != 0;  

       //這裡是action_down的處理邏輯  

       if (action == motionevent.action_down) {  

        //清除mmotiontarget, 每次action_down都很設定mmotiontarget為null  

           if (mmotiontarget != null) {  

               mmotiontarget = null;  

           }  

           //disallowintercept預設是false, 就看viewgroup的onintercepttouchevent()方法  

           if (disallowintercept || !onintercepttouchevent(ev)) {  

               ev.setaction(motionevent.action_down);  

               final int scrolledxint = (int) scrolledxfloat;  

               final int scrolledyint = (int) scrolledyfloat;  

               final view[] children = mchildren;  

               final int count = mchildrencount;  

               //周遊其子view  

               for (int i = count - 1; i >= 0; i--) {  

                   final view child = children[i];  

                   //如果該子view是visible或者該子view正在執行動畫, 表示該view才  

                   //可以接受到touch事件  

                   if ((child.mviewflags & visibility_mask) == visible  

                           || child.getanimation() != null) {  

                    //擷取子view的位置範圍  

                       child.gethitrect(frame);  

                       //如touch到螢幕上的點在該子view上面  

                       if (frame.contains(scrolledxint, scrolledyint)) {  

                           // offset the event to the view's coordinate system  

                           final float xc = scrolledxfloat - child.mleft;  

                           final float yc = scrolledyfloat - child.mtop;  

                           ev.setlocation(xc, yc);  

                           child.mprivateflags &= ~cancel_next_up_event;  

                           //調用該子view的dispatchtouchevent()方法  

                           if (child.dispatchtouchevent(ev))  {  

                               // 如果child.dispatchtouchevent(ev)傳回true表示  

                            //該事件被消費了,設定mmotiontarget為該子view  

                               mmotiontarget = child;  

                               //直接傳回true  

                               return true;  

                           }  

                           // the event didn't get handled, try the next view.  

                           // don't reset the event's location, it's not  

                           // necessary here.  

                       }  

                   }  

               }  

       }  

       //判斷是否為action_up或者action_cancel  

       boolean isuporcancel = (action == motionevent.action_up) ||  

               (action == motionevent.action_cancel);  

       if (isuporcancel) {  

           //如果是action_up或者action_cancel, 将disallowintercept設定為預設的false  

        //假如我們調用了requestdisallowintercepttouchevent()方法來設定disallowintercept為true  

        //當我們擡起手指或者取消touch事件的時候要将disallowintercept重置為false  

        //是以說上面的disallowintercept預設在我們每次action_down的時候都是false  

           mgroupflags &= ~flag_disallow_intercept;  

       // the event wasn't an action_down, dispatch it to our target if  

       // we have one.  

       final view target = mmotiontarget;  

       //mmotiontarget為null意味着沒有找到消費touch事件的view, 是以我們需要調用viewgroup父類的  

       //dispatchtouchevent()方法,也就是view的dispatchtouchevent()方法  

       if (target == null) {  

           // we don't have a target, this means we're handling the  

           // event as a regular view.  

           ev.setlocation(xf, yf);  

           if ((mprivateflags & cancel_next_up_event) != 0) {  

               ev.setaction(motionevent.action_cancel);  

               mprivateflags &= ~cancel_next_up_event;  

           return super.dispatchtouchevent(ev);  

       //這個if裡面的代碼action_down不會執行,隻有action_move  

       //action_up才會走到這裡, 假如在action_move或者action_up攔截的  

       //touch事件, 将action_cancel派發給target,然後直接傳回true  

       //表示消費了此touch事件  

       if (!disallowintercept && onintercepttouchevent(ev)) {  

           final float xc = scrolledxfloat - (float) target.mleft;  

           final float yc = scrolledyfloat - (float) target.mtop;  

           mprivateflags &= ~cancel_next_up_event;  

           ev.setaction(motionevent.action_cancel);  

           ev.setlocation(xc, yc);  

           if (!target.dispatchtouchevent(ev)) {  

           // clear the target  

           mmotiontarget = null;  

           // don't dispatch this event to our own view, because we already  

           // saw it when intercepting; we just want to give the following  

           // event to the normal ontouchevent().  

           return true;  

       // finally offset the event to the target's coordinate system and  

       // dispatch the event.  

       final float xc = scrolledxfloat - (float) target.mleft;  

       final float yc = scrolledyfloat - (float) target.mtop;  

       ev.setlocation(xc, yc);  

       if ((target.mprivateflags & cancel_next_up_event) != 0) {  

           target.mprivateflags &= ~cancel_next_up_event;  

       //如果沒有攔截action_move, action_down的話,直接将touch事件派發給target  

       return target.dispatchtouchevent(ev);  

   }  

這個方法相對來說還是蠻長,不過所有的邏輯都寫在一起,看起來比較友善,接下來我們就具體來分析一下

我們點選螢幕上面的textview來看看touch是如何分發的,先看看action_down

在decorview這一層會直接調用viewgroup的dispatchtouchevent(), 先看18行,每次action_down都會将mmotiontarget設定為null, mmotiontarget是什麼?我們先不管,繼續看代碼,走到25行,  disallowintercept預設為false,我們再看viewgroup的onintercepttouchevent()方法

View,ViewGroup的Touch事件的分發機制

public boolean onintercepttouchevent(motionevent ev) {  

      return false;  

  }  

直接傳回false, 繼續往下看,循環周遊decorview裡面的child,從上面的mainactivity的層次結構圖我們可以看出,decorview裡面隻有一個child那就是linearlayout, 第43行判斷touch的位置在不在linnearlayout上面,這是毫無疑問的,是以直接跳到51行, 調用linearlayout的dispatchtouchevent()方法,linearlayout也沒有dispatchtouchevent()這個方法,是以也是調用viewgroup的dispatchtouchevent()方法,是以這個方法卡在51行沒有繼續下去,而是去先執行linearlayout的dispatchtouchevent()

linearlayout調用dispatchtouchevent()的邏輯跟decorview是一樣的,是以也是周遊linearlayout的兩個framelayout,判斷touch的是哪個framelayout,很明顯是下面那個,調用下面那個framelayout的dispatchtouchevent(),  是以linearlayout的dispatchtouchevent()卡在51也沒繼續下去

繼續調用framelayout的dispatchtouchevent()方法,和上面一樣的邏輯,下面的framelayout也隻有一個child,就是relativelayout,framelayout的dispatchtouchevent()繼續卡在51行,先執行relativelayout的dispatchtouchevent()方法

執行relativelayout的dispatchtouchevent()方法邏輯還是一樣的,循環周遊 relativelayout裡面的孩子,裡面隻有一個textview, 是以這裡就調用textview的dispatchtouchevent(), textview并沒有dispatchtouchevent()這個方法,于是找textview的父類view,在看view的dispatchtouchevent()的方法之前,我們先理清下上面這些viewgroup執行dispatchtouchevent()的思路,我畫了一張圖幫大家理清下(這裡沒有畫出onintercepttouchevent()方法)

View,ViewGroup的Touch事件的分發機制

上面的viewgroup的touch事件分發就告一段落先,因為這裡要調用textview(也就是view)的dispatchtouchevent()方法,是以我們先分析view的dispatchtouchevent()方法在将上面的繼續下去

view的touch事件分發機制

我們還是先看view的dispatchtouchevent()方法的源碼

View,ViewGroup的Touch事件的分發機制

public boolean dispatchtouchevent(motionevent event) {  

        if (montouchlistener != null && (mviewflags & enabled_mask) == enabled &&  

                montouchlistener.ontouch(this, event)) {  

        return ontouchevent(event);  

在這個方法裡面,先進行了一個判斷

第一個條件montouchlistener就是我們調用view的settouchlistener()方法設定的

第二個條件是判斷view是否為enabled的, view一般都是enabled,除非你手動設定為disabled

第三個條件就是ontouchlistener接口的ontouch()方法的傳回值了,如果調用了settouchlistener()設定ontouchlistener,并且ontouch()方法傳回true,view的dispatchtouchevent()方法就直接傳回true,否則就執行view的ontouchevent() 并傳回view的ontouchevent()的值

現在你了解了view的ontouchevent()方法和ontouch()的關系了吧,為什麼android提供了處理touch事件ontouchevent()方法還要增加一個ontouchlistener接口呢?我覺得ontouchlistener接口是對處理touch事件的屏蔽和擴充作用吧,屏蔽作用我就不舉例介紹了,看上面的源碼就知道了,我就說下擴充吧,比如我們要列印view的touch的點的坐标,我們可以自定義一個view如下

View,ViewGroup的Touch事件的分發機制

public class customview extends view {  

    public customview(context context, attributeset attrs) {  

        super(context, attrs);  

    public customview(context context, attributeset attrs, int defstyle) {  

        super(context, attrs, defstyle);  

    @override  

    public boolean ontouchevent(motionevent event) {  

        log.i("tag", "x的坐标 = " + event.getx() + " y的坐标 = " + event.gety());  

        return super.ontouchevent(event);  

}  

也可以直接對view設定ontouchlistener接口,在return的時候調用下v.ontouchevent()

View,ViewGroup的Touch事件的分發機制

view.setontouchlistener(new ontouchlistener() {  

            @override  

            public boolean ontouch(view v, motionevent event) {  

                log.i("tag", "x的坐标 = " + event.getx() + " y的坐标 = " + event.gety());  

                return v.ontouchevent(event);  

            }  

        });  

這樣子也實作了我們所需要的功能,是以我認為ontouchlistener是對ontouchevent()方法的一個屏蔽和擴充作用,假如你有不一樣的了解,你也可以告訴我下,這裡就不糾結這個了。

我們再看view的ontouchevent()方法

View,ViewGroup的Touch事件的分發機制

public boolean ontouchevent(motionevent event) {  

      final int viewflags = mviewflags;  

      if ((viewflags & enabled_mask) == disabled) {  

          return (((viewflags & clickable) == clickable ||  

                  (viewflags & long_clickable) == long_clickable));  

      }  

      //如果設定了touch代理,就交給代理來處理,mtouchdelegate預設是null  

      if (mtouchdelegate != null) {  

          if (mtouchdelegate.ontouchevent(event)) {  

              return true;  

          }  

      //如果view是clickable或者longclickable的ontouchevent就傳回true, 否則傳回false  

      if (((viewflags & clickable) == clickable ||  

              (viewflags & long_clickable) == long_clickable)) {  

          switch (event.getaction()) {  

              case motionevent.action_up:  

                  boolean prepressed = (mprivateflags & prepressed) != 0;  

                  if ((mprivateflags & pressed) != 0 || prepressed) {  

                      boolean focustaken = false;  

                      if (isfocusable() && isfocusableintouchmode() && !isfocused()) {  

                          focustaken = requestfocus();  

                      }  

                      if (!mhasperformedlongpress) {  

                          removelongpresscallback();  

                          if (!focustaken) {  

                              if (mperformclick == null) {  

                                  mperformclick = new performclick();  

                              }  

                              if (!post(mperformclick)) {  

                                  performclick();  

                          }  

                      if (munsetpressedstate == null) {  

                          munsetpressedstate = new unsetpressedstate();  

                      if (prepressed) {  

                          mprivateflags |= pressed;  

                          refreshdrawablestate();  

                          postdelayed(munsetpressedstate,  

                                  viewconfiguration.getpressedstateduration());  

                      } else if (!post(munsetpressedstate)) {  

                          munsetpressedstate.run();  

                      removetapcallback();  

                  }  

                  break;  

              case motionevent.action_down:  

                  if (mpendingcheckfortap == null) {  

                      mpendingcheckfortap = new checkfortap();  

                  mprivateflags |= prepressed;  

                  mhasperformedlongpress = false;  

                  postdelayed(mpendingcheckfortap, viewconfiguration.gettaptimeout());  

              case motionevent.action_cancel:  

                  mprivateflags &= ~pressed;  

                  refreshdrawablestate();  

                  removetapcallback();  

              case motionevent.action_move:  

                  final int x = (int) event.getx();  

                  final int y = (int) event.gety();  

                  //當手指在view上面滑動超過view的邊界,  

                  int slop = mtouchslop;  

                  if ((x < 0 - slop) || (x >= getwidth() + slop) ||  

                          (y < 0 - slop) || (y >= getheight() + slop)) {  

                      // outside button  

                      if ((mprivateflags & pressed) != 0) {  

                          mprivateflags &= ~pressed;  

          return true;  

這個方法也是比較長的,我們先看第4行,如果一個view是disabled, 并且該view是clickable或者longclickable, ontouchevent()就不執行下面的代碼邏輯直接傳回true, 表示該view就一直消費touch事件,如果一個enabled的view,并且是clickable或者longclickable的,ontouchevent()會執行下面的代碼邏輯并傳回true,綜上,一個clickable或者longclickable的view是一直消費touch事件的,而一般的view既不是clickable也不是longclickable的(即不會消費touch事件,隻會執行action_down而不會執行action_move和action_up) button是clickable的,可以消費touch事件,但是我們可以通過setclickable()和setlongclickable()來設定view是否為clickable和longclickable。當然還可以通過重寫view的ontouchevent()方法來控制touch事件的消費與否

我們在看57行的action_down, 建立一個checkfortap,我們看看checkfortap是什麼

View,ViewGroup的Touch事件的分發機制

private final class checkfortap implements runnable {  

       public void run() {  

           mprivateflags &= ~prepressed;  

           mprivateflags |= pressed;  

           refreshdrawablestate();  

           if ((mviewflags & long_clickable) == long_clickable) {  

               postcheckforlongclick(viewconfiguration.gettaptimeout());  

原來是個runnable對象,然後使用handler的post方法延時viewconfiguration.gettaptimeout()執行checkfortap的run()方法,在run方法中先判斷view是否longclickable的,一般的view都是false, postcheckforlongclick(viewconfiguration.gettaptimeout())這段代碼就是執行長按的邏輯的代碼,隻有當我們設定為longclickble才會去執行postcheckforlongclick(viewconfiguration.gettaptimeout()),這裡我就不介紹了

由于考慮到文章篇幅的問題,我就不繼續分析view的長按事件和點選事件了,在這裡我直接得出結論吧

長按事件是在action_down中執行,點選事件是在action_up中執行,要想執行長按事件,這個view必須是longclickable的, 也許你會納悶,一般的view不是longclickable為什麼也會執行長按事件呢?我們要執行長按事件必須要調用setonlongclicklistener()設定onlongclicklistener接口,我們看看這個方法的源碼

View,ViewGroup的Touch事件的分發機制

public void setonlongclicklistener(onlongclicklistener l) {  

     if (!islongclickable()) {  

         setlongclickable(true);  

     }  

     monlongclicklistener = l;  

 }  

看到沒有,如果這個view不是longclickable的,我們就調用setlongclickable(true)方法設定為longclickable的,是以才會去執行長按方法onlongclick();

要想執行點選事件,這個view就必須要消費action_down和action_move事件,并且沒有設定onlongclicklistener的情況下,如果設定了onlongclicklistener的情況下,需要onlongclick()傳回false才能執行到onclick()方法,也許你又會納悶,一般的view預設是不消費touch事件的,這不是和你上面說的相違背嘛,我們要向執行點選事件必須要調用setonclicklistener()來設定onclicklistener接口,我們看看這個方法的源碼就知道了

View,ViewGroup的Touch事件的分發機制

public void setonclicklistener(onclicklistener l) {  

     if (!isclickable()) {  

         setclickable(true);  

     monclicklistener = l;  

是以說一個enable的并且是clickable的view是一直消費touch事件的,是以才會執行到onclick()方法

對于view的touch事件的分發機制算是告一段落了,從上面我們可以得出textview的dispatchtouchevent()的傳回false的,即不消費touch事件。我們就要往上看relativelayout的dispatchtouchevent()方法的51行,由于textview.dispatchtouchevent()為false, 導緻mmotiontarget沒有被指派,還是null, 繼續往下走執行relativelayout的dispatchtouchevent()方法, 來到第84行,

判斷target是否為null,這個target就是mmotiontarget,滿足條件,執行92行的 super.dispatchtouchevent(ev)代碼并傳回, 這裡調用的是relativelayout父類view的dispatchtouchevent()方法,由于relativelayout沒有設定ontouchlistener, 是以這裡直接調用relativelayout(其實就是view, 因為relativelayout沒有重寫ontouchevent())的ontouchevent()方法

由于relativelayout既不是clickable的也是longclickable的,是以其ontouchevent()方法false, relativelayout的dispatchtouchevent()也是傳回false,這裡就執行完了relativelayout的dispatchtouchevent()方法

繼續執行framelayout的dispatchtouchevent()的第51行,由于relativelayout.dispatchtouchevent()傳回的是false, 跟上面的邏輯是一樣的, 也是執行到92行的super.dispatchtouchevent(ev)代碼并傳回,然後執行framelayout的ontouchevent()方法,而framelayout的ontouchevent()也是傳回false,是以framelayout的dispatchtouchevent()方法傳回false,執行完畢framelayout的dispatchtouchevent()方法

在上面的我就不分析了,大家自行分析一下,跟上面的邏輯是一樣的,我直接畫了個圖來幫大家了解下(這裡沒有畫出onintercepttouchevent()方法)

View,ViewGroup的Touch事件的分發機制

是以我們點選螢幕上面的textview的事件分發流程是上圖那個樣子的,表示activity的view都不消費action_down事件,是以就不能在觸發action_move, action_up等事件了,具體是為什麼?我還不太清楚,畢竟從activity到textview這一層是分析不出來的,估計是在底層實作的。

但如果将textview換成button,流程是不是還是這個樣子呢?答案不是,我們來分析分析一下,如果是button , button是一個clickable的view,ontouchevent()傳回true, 表示他一直消費touch事件,是以button的dispatchtouchevent()方法傳回true, 回到relativelayout的dispatchtouchevent()方法的51行,滿足條件,進入到if方法體,設定mmotiontarget為button,然後直接傳回true, relativelayout的dispatchtouchevent()方法執行完畢, 不會調用到relativelayout的ontouchevent()方法

然後到framelayout的dispatchtouchevent()方法的51行,由于relativelayout.dispatchtouchevent()傳回true, 滿足條件,進入if方法體,設定mmotiontarget為relativelayout,注意下,這裡的mmotiontarget跟relativelayout的dispatchtouchevent()方法的mmotiontarget不是同一個哦,因為他們是不同的方法中的,然後傳回true

同理framelayout的dispatchtouchevent()也是傳回true, decorview的dispatchtouchevent()方法也傳回true, 還是畫一個流程圖(這裡沒有畫出onintercepttouchevent()方法)給大家理清下

View,ViewGroup的Touch事件的分發機制

從上面的流程圖得出一個結論,touch事件是從頂層的view一直往下分發到手指按下的最裡面的view,如果這個view的ontouchevent()傳回false,即不消費touch事件,這個touch事件就會向上找父布局調用其父布局的ontouchevent()處理,如果這個view傳回true,表示消費了touch事件,就不調用父布局的ontouchevent()

接下來我們用一個自定義的viewgroup來替換relativelayout,自定義viewgroup代碼如下

View,ViewGroup的Touch事件的分發機制

package com.example.androidtouchevent;  

import android.content.context;  

import android.util.attributeset;  

import android.view.motionevent;  

import android.widget.relativelayout;  

public class customlayout extends relativelayout {  

    public customlayout(context context, attributeset attrs) {  

        super(context, attrs, 0);  

    public customlayout(context context, attributeset attrs, int defstyle) {  

    public boolean onintercepttouchevent(motionevent ev) {  

        return true;  

我們就重寫了onintercepttouchevent(),傳回true, relativelayout預設是傳回false, 然後再customlayout布局中加一個button ,如下圖

View,ViewGroup的Touch事件的分發機制

我們這次不從decorview的dispatchtouchevent()分析了,直接從customlayout的dispatchtouchevent()分析

我們先看action_down 來到25行,由于我們重寫了onintercepttouchevent()傳回true, 是以不走這個if裡面,直接往下看代碼,來到84行, target為null,是以進入if方法裡面,直接調用super.dispatchtouchevent()方法, 也就是view的dispatchtouchevent()方法,而在view的dispatchtouchevent()方法中是直接調用view的ontouchevent()方法,但是customlayout重寫了ontouchevent(),是以這裡還是調用customlayout的ontouchevent(),

這個方法傳回false, 不消費touch事件,是以不會在觸發action_move,action_up等事件了,這裡我再畫一個流程圖吧(含有onintercepttouchevent()方法的)

View,ViewGroup的Touch事件的分發機制

好了,就分析到這裡吧,差不多分析完了,還有一種情況沒有分析到,例如我将customlayout的代碼改成下面的情形,touch事件又是怎麼分發的呢?我這裡就不帶大家分析了

View,ViewGroup的Touch事件的分發機制

        if(ev.getaction() == motionevent.action_move){  

        return super.onintercepttouchevent(ev);  

這篇文章的篇幅有點長,如果你想了解touch事件的分發機制,你一定要認真看完,下面來總結一下吧

1.activity的最頂層window是phonewindow,phonewindow的最頂層view是decorview

2.一個clickable或者longclickable的view會永遠消費touch事件,不管他是enabled還是disabled的

3.view的長按事件是在action_down中執行,要想執行長按事件該view必須是longclickable的,并且不能産生action_move

4.view的點選事件是在action_up中執行,想要執行點選事件的前提是消費了action_down和action_move,并且沒有設定onlongclicklistener的情況下,如設定了onlongclicklistener的情況,則必須使onlongclick()傳回false

5.如果view設定了ontouchlistener了,并且ontouch()方法傳回true,則不執行view的ontouchevent()方法,也表示view消費了touch事件,傳回false則繼續執行ontouchevent()

6.touch事件是從最頂層的view一直分發到手指touch的最裡層的view,如果最裡層view消費了action_down事件(設定ontouchlistener,并且ontouch()傳回true 或者ontouchevent()方法傳回true)才會觸發action_move,action_up的發生,如果某個viewgroup攔截了touch事件,則touch事件交給viewgroup處理

7.touch事件的分發過程中,如果消費了action_down,而在分發action_move的時候,某個viewgroup攔截了touch事件,就像上面那個自定義customlayout,則會将action_cancel分發給該viewgroup下面的touch到的view,然後将touch事件交給viewgroup處理,并傳回true

繼續閱讀