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

desaco發表於2016-01-29

> View的事件傳遞機制的11個結論:
  1. 同一個事件序列是從手指觸控螢幕的那一刻起,到手指離開螢幕那一刻結束,這個過程中所產生的一系列事件。這個事件序列以down事件開始,中間含有數量不定的move事件,最終以up事件結束。
  2. 一個事件序列只能被一個View攔截且消耗,不過通過事件代理TouchDelegate,可以將onTouchEvent強行傳遞給其他View處理。
  3. 某個View一旦決定攔截,那麼這一事件序列就都只能由它來處理
  4. 某個View一旦開始處理事件,如果不消耗ACTION_DOWN事件(onTouchEvent返回了false),那麼事件會重新交給它的父元素處理,即父元素的onTouchEvent會被呼叫。
  5. 如果View不消耗除ACTION_DOWN以外的事件,那麼這個點選事件會消失,此時父元素的onTouchEvent並不會呼叫,並且當前View可以持續收到後續的事件,最終這些消失的事件會傳遞到Activity。
  6. ViewGroup預設不攔截任何事件。Android原始碼中ViewGroup的onInterceptTouchEvent方法預設返回false。
  7. View沒有onIntercepteTouchEvent方法,一旦有點選事件傳遞給它,那麼它的onTouchEvent方法就會被呼叫。
  8. View的onTouchEvent預設都不會消耗事件(返回false),除非它是可點選的(clickable和longClickable有一個為true)。View的longClickable預設都為false,clickable要分情況看,比如Button預設為true,TextView預設為false。
  9. View的enable屬性不影響onTouchEvent的預設返回值。哪怕一個View是disable狀態,只要它的clickable或者longClickable有一個為true,那麼它的onTouchEvent就返回true。
  10. onClick會發生的前提是當前View是可點選的,並且它受到down和up的事件。
  11. 事件傳遞是由外向內的,即事件總是先傳遞給父元素,然後再由父元素分發給子View,通過requestDisallowInterceptTouchEvent方法就可以在子元素中干擾父元素的事件分發過程,但ACTION_DOWN事件除外。

> Activity,View與ViewGroup的事件分發與事件攔截:
  1.事件從Activity.dispatchTouchEvent()開始傳遞,只要沒有被停止或攔截,從最上層的View(ViewGroup)開始一直往下(子View)傳遞。子View 可以通過onTouchEvent()對事件進行處理。
  2.事件由父View(ViewGroup)傳遞給子View,ViewGroup 可以通過onInterceptTouchEvent()對事件做攔截,停止其往下傳遞。
 3.如果事件從上往下傳遞過程中一直沒有被停止,且最底層子View 沒有消費事件,事件會反向往上傳遞,這時父View(ViewGroup)可以進行消費,如果還是沒有被消費的話,最後會到Activity 的onTouchEvent()函式。
  4.如果View 沒有對ACTION_DOWN 進行消費,之後的其他事件不會傳遞過來。
  5.OnTouchListener 優先於onTouchEvent()對事件進行消費。

> 事件分發順序
 View中onTouch,onTouchEvent和onClick的執行順序: onTouch->onTouchEvent->onClick
 -- View的事件的分發機制由三個重要方法來共同完成:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent
  1.事件分發:public boolean dispatchTouchEvent(MotionEvent ev) 
用來進行事件的分發。如果事件能夠傳遞給當前View,那麼此方法一定會被呼叫,返回結果受當前View的onTouchEvent和下級View的DispatchTouchEvent方法的影響,表示是否消耗當前事件。
  2.事件攔截:public boolean onInterceptTouchEvent(MotionEvent event) 
在上述方法內部呼叫,用來判斷是否攔截某個事件,如果當前View攔截了某個事件,那麼在同一個事件序列當中,此方法不會被再次呼叫,返回結果表示是否攔截當前事件。
  3.事件響應:public boolean onTouchEvent(MotionEvent event) 
在dispatchTouchEvent方法中呼叫,用來處理點選事件,返回結果表示是否消耗當前事件,如果不消耗,則在同一個事件序列中,當前View無法再次接收到事件。
-- 三者的關係可以總結為如下虛擬碼:
    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean consume = false;
        if (onInterceptTouchEvent(ev)) {
            consume = onTouchEvent(ev);
        } else {
            consume = child.dispatchTouchEvent(ev);
        }
        return consume;
    }

> 事件分發原理
  事件型別分為ACTION_DOWN, ACTION_UP, ACTION_MOVE, ACTION_POINTER_DOWN, ACTION_POINTER_UP, ACTION_CANCEL,每個事件都是以ACTION_DOWN開始ACTION_UP結束。
  對事件的處理包括三類,分別為傳遞——dispatchTouchEvent()函式;攔截——onInterceptTouchEvent()函式;消費——onTouchEvent()函式和OnTouchListener()。
  ViewGroup的相關事件有三個:onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent。View的相關事件只有兩個:dispatchTouchEvent、onTouchEvent。
  事件序列為:ACTION_DOWN-->ACTION_MOVE-->ACTION-->...->ACTION_UP事件。
1)android對事件分發的順序為:Activity-->PhoneWindow->DecorView->yourView;
2)android控制元件對事件處理的優先順序:onTouch>onTouchEvent>onClick

-- 在分析ViewGroup分發事件之前還得說兩結論:
 1)ViewGroup永遠不會對攔截,因為他的onInterceptTouchEvent(MotionEvent ev)始終返回的是false!這樣DecorView對到來的事件MotionEvent就只有分發到子View並由子View進行攔截和處理此事件了.
 2)View包括直接繼承於View的子類因為其父類View沒有onInterceptTouchEvent方法,所以沒法對事件進行攔截,如果這種View獲取到了事件,那麼就會執行onTouchEvent方法(當然這也是有條件的,這個前提條件在對下面onTouch方法作用的時候會有說明)。
requestDisallowInterceptTouchEvent(boolean disallowIntercept  )
//當前View干預其父View對後續事件的分發  
 mParent.requestDisallowInterceptTouchEvent(disallowIntercept); 

  我們是可以通過讓target呼叫requestDisallowInterceptTouchEvent方法來干預父類關於事件分發過程。或者在在適當的情況下讓target父View的onInterceptEvent返回true或者false,來解決滑動問題事件的衝突問題。當然在down事件之後的後續事件還是會先由父View進行分發攔截,也即是說文章開頭所說的事件序列中把每一個事件單獨來看的話,都會由父View來進行攔截和分發的,只不過到後續事件到傳到target的時候直接進行處理而少了攔截的過程而已,因為在父類查詢target的時候已經攔截過一次,這點很重要,也是解決滑動衝突的關鍵點,比如滑動的時候根據合適的時機來判斷是否讓父View進行事件攔截和處理。
 
-- 五個函式的大致執行順序如下:
1.dispatchTouchEvent()
2.onInterceptTouchEvent()
3.onTouch()
4.onTouchEvent()
5.onClick()
6.onLongClick()

> 事件分發與事件攔截
Android ViewGroup事件分發機制- http://blog.csdn.net/lmj623565791/article/details/39102591/
  ViewGroup的相關事件有三個:onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent。View的相關事件只有兩個:dispatchTouchEvent、onTouchEvent。
事件分發:public boolean dispatchTouchEvent(MotionEvent ev)
事件攔截:public boolean onInterceptTouchEvent(MotionEvent ev)

-- ViewGroup重寫了以下三個方法:

public class CustomViewGroup extends ViewGroup {
    public CustomViewGroup(Context context) {
        super(context);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.d("desaco", "ViewGroupA dispatchTouchEvent");
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.d("desaco", "ViewGroupA onInterceptTouchEvent");
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("desaco", "ViewGroupA onTouchEvent");
        return super.onTouchEvent(event);
    }

    @Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
        Log.e("desaco", "requestDisallowInterceptTouchEvent ");
        super.requestDisallowInterceptTouchEvent(disallowIntercept);
    }

    @Override
    protected void onLayout(boolean b, int i, int i1, int i2, int i3) {

    }
}

-- View重寫了以下兩個方法:

public class CustomView extends View {
    public CustomView(Context context) {
        super(context);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d("desaco", "View onTouchEvent");
        return super.onTouchEvent(event);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.d("desaco", "View dispatchTouchEvent");
        return super.dispatchTouchEvent(event);
    }

}

-- Android ViewGroup事件分發機制- http://blog.csdn.net/lmj623565791/article/details/39102591/
如果ViewGroup的onInterceptTouchEvent(ev) 當ACTION_MOVE時return true ,即攔截了子View的MOVE以及UP事件;
此時子View希望依然能夠響應MOVE和UP時該咋辦呢?getParent().requestDisallowInterceptTouchEvent(true);  這樣即使ViewGroup在MOVE的時候return true,子View依然可以捕獲到MOVE以及UP事件。
@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);  

 

 

    }

 

View結構圖:                                                                View事件分發流程圖:

 

相關文章