(轉)Android ViewGroup事件分發機制

三、關於攔截

一、如何攔截

上面的總結都是基於:若是沒有攔截;那麼如何攔截呢?java

複寫ViewGroup的onInterceptTouchEvent方法:ide

 

[java]  view plain  copy
 
  1. @Override  
  2.     public boolean onInterceptTouchEvent(MotionEvent ev)  
  3.     {  
  4.         int action = ev.getAction();  
  5.         switch (action)  
  6.         {  
  7.         case MotionEvent.ACTION_DOWN:  
  8.             //若是你以爲須要攔截  
  9.             return true ;   
  10.         case MotionEvent.ACTION_MOVE:  
  11.             //若是你以爲須要攔截  
  12.             return true ;   
  13.         case MotionEvent.ACTION_UP:  
  14.             //若是你以爲須要攔截  
  15.             return true ;   
  16.         }  
  17.           
  18.         return false;  
  19.     }  


默認是不攔截的,即返回false;若是你須要攔截,只要return true就好了,這要該事件就不會往子View傳遞了,而且若是你在DOWN retrun true ,則DOWN,MOVE,UP子View都不會捕獲事件;若是你在MOVE return true , 則子View在MOVE和UP都不會捕獲事件。this

 

緣由很簡單,當onInterceptTouchEvent(ev) return true的時候,會把mMotionTarget 置爲null ; spa

二、如何不被攔截

若是ViewGroup的onInterceptTouchEvent(ev) 當ACTION_MOVE時return true ,即攔截了子View的MOVE以及UP事件;.net

此時子View但願依然可以響應MOVE和UP時該咋辦呢?orm

Android給咱們提供了一個方法:requestDisallowInterceptTouchEvent(boolean) 用於設置是否容許攔截,咱們在子View的dispatchTouchEvent中直接這麼寫:blog

 

[java]  view plain  copy
 
  1. @Override  
  2.     public boolean dispatchTouchEvent(MotionEvent event)  
  3.     {  
  4.         getParent().requestDisallowInterceptTouchEvent(true);    
  5.         int action = event.getAction();  
  6.   
  7.         switch (action)  
  8.         {  
  9.         case MotionEvent.ACTION_DOWN:  
  10.             Log.e(TAG, "dispatchTouchEvent ACTION_DOWN");  
  11.             break;  
  12.         case MotionEvent.ACTION_MOVE:  
  13.             Log.e(TAG, "dispatchTouchEvent ACTION_MOVE");  
  14.             break;  
  15.         case MotionEvent.ACTION_UP:  
  16.             Log.e(TAG, "dispatchTouchEvent ACTION_UP");  
  17.             break;  
  18.   
  19.         default:  
  20.             break;  
  21.         }  
  22.         return super.dispatchTouchEvent(event);  
  23.     }  


getParent().requestDisallowInterceptTouchEvent(true);  這樣即便ViewGroup在MOVE的時候return true,子View依然能夠捕獲到MOVE以及UP事件。事件

 

 

從源碼也能夠解釋:ip

ViewGroup MOVE和UP攔截的源碼是這樣的:get

 

[java]  view plain  copy
 
  1. if (!disallowIntercept && onInterceptTouchEvent(ev)) {  
  2.             final float xc = scrolledXFloat - (float) target.mLeft;  
  3.             final float yc = scrolledYFloat - (float) target.mTop;  
  4.             mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
  5.             ev.setAction(MotionEvent.ACTION_CANCEL);  
  6.             ev.setLocation(xc, yc);  
  7.             if (!target.dispatchTouchEvent(ev)) {  
  8.                 // target didn't handle ACTION_CANCEL. not much we can do  
  9.                 // but they should have.  
  10.             }  
  11.             // clear the target  
  12.             mMotionTarget = null;  
  13.             // Don't dispatch this event to our own view, because we already  
  14.             // saw it when intercepting; we just want to give the following  
  15.             // event to the normal onTouchEvent().  
  16.             return true;  
  17.         }  


當咱們把disallowIntercept設置爲true時,!disallowIntercept直接爲false,因而攔截的方法體就被跳過了~

 

注:若是ViewGroup在onInterceptTouchEvent(ev)  ACTION_DOWN裏面直接return true了,那麼子View是木有辦法的捕獲事件的~~~

 

四、若是沒有找到合適的子View

咱們的實例,直接點擊ViewGroup內的按鈕,固然直接很順利的走完整個流程;

可是有兩種特殊狀況

一、ACTION_DOWN的時候,子View.dispatchTouchEvent(ev)返回的爲false ; 

若是你仔細看了,你會注意到ViewGroup的dispatchTouchEvent(ev)的ACTION_DOWN代碼是這樣的

 

[java]  view plain  copy
 
  1. if (child.dispatchTouchEvent(ev))  {  
  2.                               // Event handled, we have a target now.  
  3.                               mMotionTarget = child;  
  4.                               return true;  
  5.                           }  


只有在child.dispatchTouchEvent(ev)返回true了,纔會認爲找到了可以處理當前事件的View,即mMotionTarget = child;

 

可是若是返回false,那麼mMotionTarget 依然是null

mMotionTarget 爲null會咋樣呢?

其實ViewGroup也是View的子類,若是沒有找到可以處理該事件的子View,或者乾脆就沒有子View;

那麼,它做爲一個View,就至關於View的事件轉發了~~直接super.dispatchTouchEvent(ev);

源碼是這樣的:

 

[java]  view plain  copy
 
  1. final View target = mMotionTarget;  
  2.        if (target == null) {  
  3.            // We don't have a target, this means we're handling the  
  4.            // event as a regular view.  
  5.            ev.setLocation(xf, yf);  
  6.            if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
  7.                ev.setAction(MotionEvent.ACTION_CANCEL);  
  8.                mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
  9.            }  
  10.            return super.dispatchTouchEvent(ev);  
  11.        }  


咱們沒有一個可以處理該事件的目標元素,意味着咱們須要本身處理~~~就至關於傳統的View~

 

二、那麼何時子View.dispatchTouchEvent(ev)返回的爲true

若是你仔細看了上篇博客,你會發現只要子View支持點擊或者長按事件必定返回true~~

源碼是這樣的:

 

[java]  view plain  copy
 
  1.    
  2. if (((viewFlags & CLICKABLE) == CLICKABLE ||  
  3.                 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {     
  4.              return true ;                                                                                                                                                                                                                                                                                                   }  

 

五、總結

 

關於代碼流程上面已經總結過了~

一、若是ViewGroup找到了可以處理該事件的View,則直接交給子View處理,本身的onTouchEvent不會被觸發;

二、能夠經過複寫onInterceptTouchEvent(ev)方法,攔截子View的事件(即return true),把事件交給本身處理,則會執行本身對應的onTouchEvent方法

三、子View能夠經過調用getParent().requestDisallowInterceptTouchEvent(true);  阻止ViewGroup對其MOVE或者UP事件進行攔截;

 

好了,那麼實際應用中能解決哪些問題呢?

好比你須要寫一個相似slidingmenu的左側隱藏menu,主Activity上有個Button、ListView或者任何能夠響應點擊的View,你在當前View上死命的滑動,菜單欄也出不來;由於MOVE事件被子View處理了~ 你須要這麼作:在ViewGroup的dispatchTouchEvent中判斷用戶是否是想顯示菜單,若是是,則在onInterceptTouchEvent(ev)攔截子View的事件;本身進行處理,這樣本身的onTouchEvent就能夠順利展示出菜單

相關文章
相關標籤/搜索