ViewGroup/View的事件分發機制(1)(Touch,down,move,up)

desaco發表於2016-02-29

true:攔截;false:不攔截.

    自定義ViewGroup實現各種滑動效果的,不可避免的會出現很多事件的衝突,對ViewGroup事件分發機制的瞭解,也有益於大家瞭解衝突產生的原因,以及對衝突進行處理~

 View體系的繪製流程是從ViewRootImpl的performTraversals方法開始的
View的測量大小流程:performMeasure –> measure –> onMeasure等方法;
View的測量位置流程:performLayout –> layout –> onLayout等方法;
View的繪製流程:performDraw-> draw-> onDraw等方法;

自定義控制元件集合庫- https://github.com/HpWens/MeiWidgetView
   -- View 的測量、佈局、繪製三大流程都是交由 ViewRootImpl 發起,而且還都是在 performTraversals() 方法中發起的,所以這個方法的邏輯很複雜,因為每次都需要根據相應狀態判斷是否需要三個流程都走,有時可能只需要執行 performDraw() 繪製流程,有時可能只執行 performMeasure() 測量和 performLayout() 佈局流程(一般測量和佈局流程是一起執行的)。不管哪個流程都會遍歷一次 View 樹,所以其實介面的繪製是需要遍歷很多次的,如果頁面層次太過複雜,每一幀需要重新整理的 View 又很多時,耗時就會長一點。

1.dispatchTouchEvent()事件分發,當Touch事件發生時,dispatchTouchEvent()方法會以隧道方式(即從根元素依次向內層元素傳遞)將事件向下傳遞。一般來說我們不會去改寫這個方法       
 return true :事件交由當前view進行消費,同時事件停止向下傳遞    
 return false:事件交由上層view或者Activity進行消費      
 return super.dispatchTouchEvent():系統預設的事件分發處理邏輯

2.onInerceptTouchEvent()事件攔截,該方法只有ViewGroup擁有。       
 return true:表示攔截事件,把攔截的事件交由當前的view處理      
 return fasle:表示不攔截事件,事件會被傳遞到子view的dispatchTouchEvent()來進行事件分發onTouchEvent()事件響應       

3.onTouchEvent()方法會以冒泡方式(即從最內層的子元素依次向外傳遞)將事件向上傳遞      
 return true:表示消費了事件,事件停止向上傳遞     
 return false:表示沒有處理事件,事件繼續向上傳遞       
 return super.onTouchEvent():預設處理事件的邏輯,和返回false差不多

-- Android事件分發機制詳解- https://blog.csdn.net/feelinghappy/article/details/54378797
事件在哪些物件之間進行傳遞?一個點選事件產生後,傳遞順序是:Activity(Window) -> ViewGroup -> View。
事件傳遞情況: 
 從Activity A—->ViewGroup B—>View C,從上往下呼叫dispatchTouchEvent()
 再由View C—>ViewGroup B —>Activity A,從下往上呼叫onTouchEvent()

 - Android6.0 ViewGroup/View 事件分發機制詳解-http://www.2cto.com/kf/201606/514943.html
  mDecor是何許人也,其實就是PhoneWindow中的一個內部類DecorView的例項物件,是Activity的Window視窗中最根部的父容器,我們平時在Activity的onCreate()方法中,通過setContentView()給設定的佈局容器,都屬於mDecor的子View mContentView物件的子view,而DecorView又繼承於FrameLayout,FrameLayout又繼承於ViewGroup。

Activity.dispatchTouchEvent是來自Window.Callback.dispatchTouchEvent,Activity.dispatchTouchEvent呼叫的位置是在PhoneWindow.DecorView.
dispatchTouchEvent,此處指的是DecorView它本身重寫的那個,而不是super的,注意兩者的區別.
Android中activity觸控操作dispatchTouchEvent

觸控事件先進行此操作;

float downX = 0, downY = 0;
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = ev.getX();
                downY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

dispatchTouchEvent是處理觸控事件分發,事件(多數情況)是從Activity的dispatchTouchEvent開始的。執行super.dispatchTouchEvent(ev),事件向下分發。
onInterceptTouchEvent是ViewGroup提供的方法,預設返回false,返回true表示攔截。
onTouchEvent是View中提供的方法,ViewGroup也有這個方法,view中不提供onInterceptTouchEvent。view中預設返回true,表示消費了這個事件。

android中的Touch事件都是從ACTION_DOWN開始的:
單手指操作:ACTION_DOWN---ACTION_MOVE----ACTION_UP
多手指操作:ACTION_DOWN---ACTION_POINTER_DOWN---ACTION_MOVE--ACTION_POINTER_UP---ACTION_UP.

https://img-blog.csdn.net/20181018164325234?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1NoYXJlVXM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70

-- Android介面渲染流程線:
UI物件—->CPU處理為多維圖形,紋理 —–通過OpenGL ES介面呼叫GPU—-> GPU對圖進行光柵化(Frame Rate ) —->硬體時鐘(Refresh Rate)—-垂直同步—->投射到螢幕。

-- 在 Android M 版本開始,GPU Profiling 工具把渲染操作拆解成如下8個詳細的步驟進行顯示:
1.swap Buffers;

2.Command Issue;

3.Sync & Upload

4.Draw
5.Measure/Layout

6.Animation

7.Input Handling

8.Misc Time/Vsync Delay

-- Activity是如何將事件分發到相應的View、ViewGroup當中去的:
Activity.dispatchTouchEvent(MotionEvent event) -> PhoneWindow.superDispatchTouchEvent(MotionEvent event) -> DecorView.superDispatchTouchEvent(MotionEvent event) -> FrameLayout.dispatchTouchEvent(MotionEvent event) -> ViewGroup.dispatchTouchEvent(MotionEvent event) -> 再逐級分發到各個ViewGroup/View當中去.

> Activity、ViewGroup與View的Touch事件:

ViewGroupA.dispatchTouchEvent ->
ViewGroupA.onInterceptTouchEvent(return false, 沒有進行攔截) ->
ViewGroupB.dispatchTouchEvent ->
ViewGroupB.onInterceptTouchEvent(return false, 沒有進行攔截) ->
ViewC.dispatchTouchEvent ->ViewC.onTouchEvent(return false, 沒有消費) -> 
ViewC.dispatchTouchEvent(return false, 將onTouchEvent的處理結果回傳給ViewGroupB) ->
ViewGroupB.onTouchEvent(return false, 也沒有消費) ->

ViewB.dispatchTouchEvent(return false, 將onTouchEvent的處理結果回傳給ViewGroupA) ->
ViewGroupA.onTouchEvent(return false, 也沒有消費) ->
ViewA.dispatchTouchEvent(return false, 最終將onTouchEvent的處理結果回傳給Activity) ->
Activity對事件進行最終處理。

  要不要捕獲事件--攔截事件--分發事件--處理事件??

> 自定義ViewGroup及ViewGroup的事件分發
-- Android ViewGroup事件分發機制- http://blog.csdn.net/lmj623565791/article/details/39102591
1.定義控制元件
public class CustomLinearLayout extends LinearLayout {
    private static final String TAG = CustomLinearLayout.class.getSimpleName();
    public CustomLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    //分發事件
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "dispatchTouchEvent ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG, "dispatchTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.e(TAG, "dispatchTouchEvent ACTION_UP");
                break;
 
            default:
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    //點選、觸控事件處理
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "onTouchEvent ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG, "onTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.e(TAG, "onTouchEvent ACTION_UP");
                break;
            default:
                break;
        }
        return super.onTouchEvent(event);
    }
 
    //攔截觸控事件
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                Log.e(TAG, "onInterceptTouchEvent ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.e(TAG, "onInterceptTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                Log.e(TAG, "onInterceptTouchEvent ACTION_UP");
                break;
 
            default:
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }
 
    @Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
        Log.e(TAG, "requestDisallowInterceptTouchEvent ");
        super.requestDisallowInterceptTouchEvent(disallowIntercept);
    }
}
2.XML佈局:
<?xml version="1.0" encoding="utf-8"?>
<com.desaco.practiceknowing.view_event_dispatch.CustomLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
 
    <com.desaco.practiceknowing.view_event_dispatch.CustomButton
        android:id="@+id/id_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="5dp"
        android:layout_marginTop="5dp"
        android:text="click me" />
</com.desaco.practiceknowing.view_event_dispatch.CustomLinearLayout>
3.MOVE一下,不然不會觸發MOVE事件,看一下日誌的輸出:
09-06 09:57:27.287: E/MyLinearLayout(959): dispatchTouchEvent ACTION_DOWN  
09-06 09:57:27.287: E/MyLinearLayout(959): onInterceptTouchEvent ACTION_DOWN  
09-06 09:57:27.287: E/MyButton(959):           dispatchTouchEvent ACTION_DOWN  
09-06 09:57:27.297: E/MyButton(959):           onTouchEvent ACTION_DOWN


09-06 09:57:27.297: E/MyButton(959):           onTouchEvent ACTION_MOVE 
09-06 09:57:27.327: E/MyLinearLayout(959): dispatchTouchEvent ACTION_MOVE  
09-06 09:57:27.327: E/MyLinearLayout(959): onInterceptTouchEvent ACTION_MOVE  
09-06 09:57:27.337: E/MyButton(959):           dispatchTouchEvent ACTION_MOVE  
09-06 09:57:27.337: E/MyButton(959):           onTouchEvent ACTION_MOVE 
 
09-06 09:57:27.457: E/MyLinearLayout(959): dispatchTouchEvent ACTION_UP  
09-06 09:57:27.457: E/MyLinearLayout(959): onInterceptTouchEvent ACTION_UP  
09-06 09:57:27.457: E/MyButton(959):           dispatchTouchEvent ACTION_UP  
09-06 09:57:27.457: E/MyButton(959):           onTouchEvent ACTION_UP 
 
可以看到大體的事件流程為:
 MyLinearLayout的dispatchTouchEvent -> MyLinearLayout的onInterceptTouchEvent -> MyButton的dispatchTouchEvent  ->Mybutton的onTouchEvent ,最先捕獲到事件的為View所在的ViewGroup..
 可以看出,在View上觸發事件,最先捕獲到事件的為View所在的ViewGroup,然後才會到View自身~

> 下面我們按照日誌的輸出,進入原始碼,原始碼分析
ViewGroup - dispatchTouchEvent
1、ViewGroup - dispatchTouchEvent - ACTION_DOWN
首先是ViewGroup的dispatchTouchEvent方法:
@Override  
   public boolean dispatchTouchEvent(MotionEvent ev) {  
       if (!onFilterTouchEventForSecurity(ev)) {  
           return false;  
       }  
       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;  
  
       boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  
  
       if (action == MotionEvent.ACTION_DOWN) {  
           if (mMotionTarget != null) {  
               // this is weird, we got a pen down, but we thought it was  
               // already down!  
               // XXX: We should probably send an ACTION_UP to the current  
               // target.  
               mMotionTarget = null;  
           }  
           // If we're disallowing intercept or if we're allowing and we didn't  
           // intercept  
           if (disallowIntercept || !onInterceptTouchEvent(ev)) {  
               // reset this event's action (just to protect ourselves)  
               ev.setAction(MotionEvent.ACTION_DOWN);  
               // We know we want to dispatch the event down, find a child  
               // who can handle it, start with the front-most child.  
               final int scrolledXInt = (int) scrolledXFloat;  
               final int scrolledYInt = (int) scrolledYFloat;  
               final View[] children = mChildren;  
               final int count = mChildrenCount;  
  
               for (int i = count - 1; i >= 0; i--) {  
                   final View child = children[i];  
                   if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
                           || child.getAnimation() != null) {  
                       child.getHitRect(frame);  
                       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;  
                           if (child.dispatchTouchEvent(ev))  {  
                               // Event handled, we have a target now.  
                               mMotionTarget = child;  
                               return true;  
                           }  
                           // The event didn't get handled, try the next view.  
                           // Don't reset the event's location, it's not  
                           // necessary here.  
                       }  
                   }  
               }  
           }  
       }        ....//other code omitted  
程式碼比較長,決定分段貼出,首先貼出的是ACTION_DOWN事件相關的程式碼。
16行:進入ACTION_DOWN的處理
17-23行:將mMotionTarget置為null
26行:進行判斷:if(disallowIntercept || !onInterceptTouchEvent(ev))
兩種可能會進入if程式碼段
 1、當前不允許攔截,即disallowIntercept =true,
 2、當前允許攔截但是不攔截,即disallowIntercept =false,但是onInterceptTouchEvent(ev)返回false ;
注:disallowIntercept 可以通過viewGroup.requestDisallowInterceptTouchEvent(boolean);進行設定,後面會詳細說;而onInterceptTouchEvent(ev)可以進行復寫。

36-57行:開始遍歷所有的子View
41行:進行判斷當前的x,y座標是否落在子View身上,如果在,47行,執行child.dispatchTouchEvent(ev),就進入了View的dispatchTouchEvent程式碼中了,如果不瞭解請參考:Android View的事件分發機制,當child.dispatchTouchEvent(ev)返回true,則為mMotionTarget=child;然後return true;

ViewGroup的ACTION_DOWN分析結束,總結一下:
ViewGroup實現捕獲到DOWN事件,如果程式碼中不做TOUCH事件攔截,則開始查詢當前x,y是否在某個子View的區域內,如果在,則把事件分發下去
按照日誌,接下來到達ACTION_MOVE

2、ViewGroup - dispatchTouchEvent - ACTION_MOVE
首先我們原始碼進行刪減,只留下MOVE相關的程式碼:
@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;  
       boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;    
      //...ACTION_DOWN    
      //...ACTIN_UP or ACTION_CANCEL  
       // The event wasn't an ACTION_DOWN, dispatch it to our target if  
       // we have one.  
       final View target = mMotionTarget;  
       // if have a target, see if we're allowed to and want to intercept its  
       // events  
       if (!disallowIntercept && onInterceptTouchEvent(ev)) {  
           //....  
       }  
       // 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);  
       return target.dispatchTouchEvent(ev);  
   }  
18行:把ACTION_DOWN時賦值的mMotionTarget,付給target ; 
23行:if (!disallowIntercept && onInterceptTouchEvent(ev)) 當前允許攔截且攔截了,才進入IF體,當然了預設是不會攔截的~這裡執行了onInterceptTouchEvent(ev)
28-30行:把座標系統轉化為子View的座標系統
32行:直接return target.dispatchTouchEvent(ev); 
可以看到,正常流程下,ACTION_MOVE在檢測完是否攔截以後,直接呼叫了子View.dispatchTouchEvent,事件分發下去;
最後就是ACTION_UP了
3、ViewGroup - dispatchTouchEvent - ACTION_UP
public boolean dispatchTouchEvent(MotionEvent ev) {  
       if (!onFilterTouchEventForSecurity(ev)) {  
           return false;  
       }    
       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;  
       boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  
       if (action == MotionEvent.ACTION_DOWN) {...}  
      boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||  
         (action == MotionEvent.ACTION_CANCEL);  
  
if (isUpOrCancel) {  
           mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;  
       }  
final View target = mMotionTarget;  
if(target ==null ){...}  
if (!disallowIntercept && onInterceptTouchEvent(ev)) {...}  
  
       if (isUpOrCancel) {  
           mMotionTarget = null;  
       }  
  
       // 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);  
  
       return target.dispatchTouchEvent(ev);  
   }  
17行:判斷當前是否是ACTION_UP
21,28行:分別重置攔截標誌位以及將DOWN賦值的mMotionTarget置為null,都UP了,當然置為null,下一次DOWN還會再賦值的~
最後,修改座標系統,然後呼叫target.dispatchTouchEvent(ev);
正常情況下,即我們上例整個程式碼的流程我們已經走完了:

  1、ACTION_DOWN中,ViewGroup捕獲到事件,然後判斷是否攔截,如果沒有攔截,則找到包含當前x,y座標的子View,賦值給mMotionTarget,然後呼叫mMotionTarget.dispatchTouchEvent
  2、ACTION_MOVE中,ViewGroup捕獲到事件,然後判斷是否攔截,如果沒有攔截,則直接呼叫mMotionTarget.dispatchTouchEvent(ev)
  3、ACTION_UP中,ViewGroup捕獲到事件,然後判斷是否攔截,如果沒有攔截,則直接呼叫mMotionTarget.dispatchTouchEvent(ev)
當然了在分發之前都會修改下座標系統,把當前的x,y分別減去child.left 和 child.top ,然後傳給child;

3、關於攔截
  1、如何攔截

上面的總結都是基於:如果沒有攔截;那麼如何攔截呢?
複寫ViewGroup的onInterceptTouchEvent方法:
@Override  
    public boolean onInterceptTouchEvent(MotionEvent ev)  
    {  
        int action = ev.getAction();  
        switch (action)  
        {  
        case MotionEvent.ACTION_DOWN:  
            //如果你覺得需要攔截  
            return true ;   
        case MotionEvent.ACTION_MOVE:  
            //如果你覺得需要攔截  
            return true ;   
        case MotionEvent.ACTION_UP:  
            //如果你覺得需要攔截  
            return true ;   
        }            
        return false;  
    }  
  預設是不攔截的,即返回false;如果你需要攔截,只要return true就行了,這要該事件就不會往子View傳遞了,並且如果你在DOWN retrun true ,則DOWN,MOVE,UP子View都不會捕獲事件;如果你在MOVE return true , 則子View在MOVE和UP都不會捕獲事件。
  原因很簡單,當onInterceptTouchEvent(ev) return true的時候,會把mMotionTarget 置為null ; 

 2、如何不被攔截
  如果ViewGroup的onInterceptTouchEvent(ev) 當ACTION_MOVE時return true ,即攔截了子View的MOVE以及UP事件;
此時子View希望依然能夠響應MOVE和UP時該咋辦呢?
  Android給我們提供了一個方法:requestDisallowInterceptTouchEvent(boolean) 用於設定是否允許攔截,我們在子View的dispatchTouchEvent中直接這麼寫:
@Override  
    public boolean dispatchTouchEvent(MotionEvent event)  
    {  
        getParent().requestDisallowInterceptTouchEvent(true);    
        int action = event.getAction();  
        switch (action)    {  
        case MotionEvent.ACTION_DOWN:  
            Log.e(TAG, "dispatchTouchEvent ACTION_DOWN");  
            break;  
        case MotionEvent.ACTION_MOVE:  
            Log.e(TAG, "dispatchTouchEvent ACTION_MOVE");  
            break;  
        case MotionEvent.ACTION_UP:  
            Log.e(TAG, "dispatchTouchEvent ACTION_UP");  
            break;  
        default:  
            break;  
        }  
        return super.dispatchTouchEvent(event);  
    }  
  getParent().requestDisallowInterceptTouchEvent(true);  這樣即使ViewGroup在MOVE的時候return true,子View依然可以捕獲到MOVE以及UP事件。
從原始碼也可以解釋:
ViewGroup MOVE和UP攔截的原始碼是這樣的:
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)) {  
                // target didn't handle ACTION_CANCEL. not much we can do  
                // but they should have.  
            }  
            // 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;  
        }  
當我們把disallowIntercept設定為true時,!disallowIntercept直接為false,於是攔截的方法體就被跳過了~
注:如果ViewGroup在onInterceptTouchEvent(ev)  ACTION_DOWN裡面直接return true了,那麼子View是木有辦法的捕獲事件的~~~

4、如果沒有找到合適的子View
我們的例項,直接點選ViewGroup內的按鈕,當然直接很順利的走完整個流程;
但是有兩種特殊情況
1、ACTION_DOWN的時候,子View.dispatchTouchEvent(ev)返回的為false ; 
如果你仔細看了,你會注意到ViewGroup的dispatchTouchEvent(ev)的ACTION_DOWN程式碼是這樣的

if (child.dispatchTouchEvent(ev))  {  
                              // Event handled, we have a target now.  
                              mMotionTarget = child;  
                              return true;  
                          }  
只有在child.dispatchTouchEvent(ev)返回true了,才會認為找到了能夠處理當前事件的View,即mMotionTarget = child;
但是如果返回false,那麼mMotionTarget 依然是null
mMotionTarget 為null會咋樣呢?
其實ViewGroup也是View的子類,如果沒有找到能夠處理該事件的子View,或者乾脆就沒有子View;
那麼,它作為一個View,就相當於View的事件轉發了~~直接super.dispatchTouchEvent(ev);

原始碼是這樣的:
final View target = mMotionTarget;  
       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);  
       }  
我們沒有一個能夠處理該事件的目標元素,意味著我們需要自己處理~~~就相當於傳統的View~

2、那麼什麼時候子View.dispatchTouchEvent(ev)返回的為true
如果你仔細看了上篇部落格,你會發現只要子View支援點選或者長按事件一定返回true~~
原始碼是這樣的:  
if (((viewFlags & CLICKABLE) == CLICKABLE ||  
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {     
             return true ;                                                                                                                                                                                                                                      }  
5、總結
關於程式碼流程上面已經總結過了~
1、如果ViewGroup找到了能夠處理該事件的View,則直接交給子View處理,自己的onTouchEvent不會被觸發;
2、可以通過複寫onInterceptTouchEvent(ev)方法,攔截子View的事件(即return true),把事件交給自己處理,則會執行自己對應的   onTouchEvent方法
3、子View可以通過呼叫getParent().requestDisallowInterceptTouchEvent(true);  阻止ViewGroup對其MOVE或者UP事件進行攔     截;

> 好了,那麼實際應用中能解決哪些問題呢?
  比如你需要寫一個類似slidingmenu的左側隱藏menu,主Activity上有個Button、ListView或者任何可以響應點選的View,你在當前View上死命的滑動,選單欄也出不來;因為MOVE事件被子View處理了~ 你需要這麼做:在ViewGroup的dispatchTouchEvent中判斷使用者是不是想顯示選單,如果是,則在onInterceptTouchEvent(ev)攔截子View的事件;自己進行處理,這樣自己的onTouchEvent就可以順利展現出選單欄了~~
 

相關文章