《Android藝術開發探索》學習筆記之View的事件體系(View的事件分發機制)

鋸齒流沙發表於2017-12-26

點選事件的傳遞規則

所謂點選事件的事件分發,其實就是對MotionEvent事件的分發過程,即當一個MotionEvent產生了以後,系統需要把這個事件傳遞給一個具體的View,而這個傳遞的過程就是分發的過程。點選事件的分發過程由三個很重要的方法來共同完成: dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent。

public boolean dispatchTouchEvent(MotionEvent e)

用來進行事件的分發。如果事件能夠傳遞給當前View,那麼此方法一定會被呼叫,返回結果受當前View的onTouchEvent和下級View的dispatchTouchEvent方法的影響,表示是否消耗當前事件。

public boolean onInterceptTouchEvent(MotionEvent e)

在dispatchTouchEvent方法的內部呼叫,用來判斷是否攔截某個事件,如果當前View攔截了某個事件,那麼在同一個事件序列中,此方法不會再次呼叫,返回結果表示是否攔截當前事件。

public boolean onTouchEvent(MotionEvent e)

在dispatchTouchEvent方法中呼叫,用來處理點選事件,返回結果表示是否消耗當前事件,如果不消耗,則在同一個事件序列中,當前View無法再次接收到事件。

上面三個方法的關係,用虛擬碼表示:

public boolean dispatchTouchEvent(MotionEvent ev){
        boolean consume = false;
        if (onInterceptTouchEvent()){
            consume = onTouchEvent();
        }else{
            consume = child.dispatchTouchEvent(ev);
        }
        return consume;
    }
複製程式碼

通過虛擬碼可以大致瞭解點選事件的傳遞規則:對於一個ViewGroup來說,點選事件產生後,首先傳遞給它,這是它的dispatchTouchEvent就會呼叫,如果這個ViewGroup的onInterceptTouchEvent方法返回true就表示它要攔截當前事件,接著事件就會交給這個ViewGroup處理,即它的onTouchEvent方法就會被呼叫。如果這個ViewGroup的onInterceptTouchEvent返回false就表示不攔截當前事件,這是當前事件就會繼續傳遞給它的子元素,接著子元素的dispatchTouchEvent方法就會被呼叫,如此反覆,直到事件最終被處理。

當一個View要處理事件時,如果它設定了OnTouchListener,如下所示:

this.setOnTouchListener(new OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
         return true;
    }
});
複製程式碼

那麼OnTouchListener的onTouch方法就會被回撥。事件如何處理還要看onTouch的返回值,如果返回false,則當前View的onTouchEvent方法被呼叫,如果返回true,那麼onTouchEvent方法將不會被呼叫。由此可見,給View設定的OnTouchListener,其優先順序比onTouchEvent要高。在onTouchEvent方法中,如果當前設定的有onClickListener,那麼它的onClick方法就會被呼叫。可以看出,我們平時常用的onClickListener,其優先順序最低,即處於事件傳遞的尾端。

當一個事件產生後,它的傳遞順序遵循如下順序:Activity——>Window——>View,即事件總是先傳遞給Activity,Activity再傳遞給Window,最後Window再傳遞給頂級的View,頂級的View接收事件後,就會按照事件分發機制去分發事件。考慮一種情況,如果一個View的onTouchEvent返回false,那麼它的父容器的onTouchEvent將會被呼叫,依次類推。如果所有的元素不處理這個事件,那麼事件最終傳遞給Activity處理,即Activity的onTouchEvent方法被呼叫。

關於事件傳遞的結論:

1)同一個事件序列是從手指觸控螢幕的那一刻起,到手指離開螢幕的那一刻結束,在這個過程中所產生的一系列事件,這份事件序列以down事件開始,中間含有數量不定的move事件,最終以up事件結束。

2)正常情況下,一個事件序列只能被一個View攔截且消耗。

3)某個View一旦決定攔截,那麼這個事件序列只能由它來處理(如果事件序列能夠傳遞的話),並且並且它的onInterceptTouchEvent不會再被呼叫。這條也很好理解,就是說一個View決定攔截一個事件後,那麼系統會把同一個事件序列內的其他方法都直接交給它來處理,因此就不用再呼叫這個View的onIntetceptTouchEvent去詢問它是否要攔截了。

4)某個View一旦開始處理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那麼同一事件序列中的其他事件都不會再交給它來處理,並且事件將重新交由它的父元素去處理,即父元素的TouchEvent會被呼叫,意思就是事件一旦交給一個View去處理,那麼它就必須消耗掉,否則同一事件序列中剩下的事件就不再交給它來處理了。

5)如果View不消耗除ACTION_DOWN以外的其他事件,那麼這個點選事件會消失,此時父元素的onTouchEvent並不會被呼叫,並且當前View可以持續收到後續的事件,最終這些消失的點選事件會被傳遞給Activity處理。

6)ViewGroup預設不攔截任何事件,Android原始碼中的ViewGroup的OnInterceptTouchEvent方法預設返回false。

7)View沒有onInterceptTouchEvent方法,一旦有點選事件傳遞給它,那麼它的onTouchEvent方法就會呼叫。

8)View的onTouchEvent預設都會消耗事件(返回true),除非它是不可點選的(clickable和longClickable同時為false)。View的 longClickable屬性預設都為false,clickable屬性要分情況,比如Button的clickable屬性預設為true,而TextView的clickable屬性預設為false。

9)View的enable屬性不影響onTouchEvent的預設返回值,哪怕一個View是disable狀態,只要它的clickable或者longClickable有一個為true,那麼它的onTouchEvent就返回true。

10)onClick會發生的前提是當前View是可點選的,並且它收到了down和up的事件。

11)事件傳遞過程是由外向內的,即事件總是先傳遞給父元素的,然後再由父元素分發給子View,通過requestDisallowInterceptTouchEvent方法可以在子元素中干預父元素的事件分發過程,但是ACTION_DOWN事件除外。

Activity對點選事件的分發過程

點選事件用MotionEvent表示,當一個點選操作發生,事件最先傳遞給當前Activity,由Activity的dispatchTouchEvent來進行事件分發,具體的工作由Activity的內部的Window來完成的。Window會將事件傳遞給decor view,decor view一般就是當前介面的底層容器(即setContentView所設定的View的父容器),通過Activity.getWindow.getDecorView()可以獲得。在Activity中通過setContentView所設定的View就是頂級View,也叫根View,頂級View,一般來說都是ViewGroup。

頂級View對點選事件的分發過程

點選事件到達頂級View(一般是一個ViewGroup)以後,會呼叫ViewGroup的dispatchTouchEvent方法,然後的邏輯是這樣:如果頂級ViewGroup攔截事件即onInterceptTouchEvent返回true,則事件由ViewGroup處理,這是如果ViewGroup的mOnTouchListener被設定,則onTouch會被呼叫,否則onTouchEvent被呼叫。也就是說,如果都提供的話,onTouch會遮蔽掉onTouchEvent。在onTouchEvent中,如果設定了mOnclickListener,則onClick會被呼叫。如果頂級ViewGroup不攔截事件,則事件會傳遞給它所在的點選事件鏈上的子View,這時子View的dispatchTouchEvent會被呼叫。到此為止,事件已經從頂級View傳遞給了下一層View,接下來的傳遞過程和頂級View是一致的,如此迴圈,完成整個事件的分發。

View對點選事件的處理過程

首先會判斷有沒有設定OnTouchListener,如果OnTouchListener中的onTouch方法返回true,那麼onTouchEvent就不會被呼叫,可見OnTouchListener的優先順序高於onTouchEvent,這樣做的好處是方便在外界處理點選事件。onTouchEvent的處理是:只要View的CLICKABLE和LONG_CLICKABLE有一個為true,那麼它就會消耗這個事件,即onTouchEvent方法返回true,不管它是不是DISABLE狀態。然後就是當ACTION_UP事件發生時,會觸發performClick方法,如果View設定了OnclickListener,那麼performClick方法內部會呼叫它的onClick方法。

相關文章