假設(shè)手機(jī)屏幕上有一個(gè)button。我們?nèi)c(diǎn)擊了一下,然后button做出了相應(yīng)的反應(yīng),那么這個(gè)過程其實(shí)這樣的,當(dāng)手機(jī)點(diǎn)擊到屏幕時(shí),TP(Touch panel)傳感器的數(shù)據(jù)發(fā)生了變化。數(shù)據(jù)經(jīng)過驅(qū)動(dòng)的處理(其實(shí)用示波器來看傳感器數(shù)據(jù),這個(gè)數(shù)據(jù)肯定不可能那么規(guī)整),然后數(shù)據(jù)依次傳遞到內(nèi)核,framwork,然后再傳遞相應(yīng)的app的當(dāng)前Activity中。我們說android中View的事件分發(fā),其實(shí)指的就是在此之后的過程。當(dāng)手指接觸屏幕所產(chǎn)生的反應(yīng),我們稱之為事件(MotionEvent).
當(dāng)然MotionEvent 實(shí)際上也可以細(xì)分為很多種,典型的有以下幾種。

  • ACTION_DOWN: 手指剛剛接觸到屏幕。

  • ACTION_MOVE:手指在屏幕上滑動(dòng)。

  • ACTION_UP:手指從屏幕上離開的一瞬間。

所以正常情況下,手指觸摸屏幕會(huì)觸發(fā)一系列事件。
比如 DOWN -> MOVE -> UP。從手指接觸屏幕那一刻起,到手指離開屏幕那一刻結(jié)束,中間所產(chǎn)生的一系列事件總和,我們稱之為一個(gè)事件序列。它以一個(gè)down事件開始,中間夾雜著0個(gè)或多個(gè)move事件,以一個(gè)up事件結(jié)束。

我們手指接觸到屏幕,怎么判斷是點(diǎn)擊還是滑動(dòng)呢?其實(shí)在手機(jī)framework中,有一個(gè)默認(rèn)值,比如8px(和具體的設(shè)備和手機(jī)廠商有關(guān)),當(dāng)手指移動(dòng)的距離大于該值時(shí),就是滑動(dòng)事件TouchSlop,否則就是點(diǎn)擊事件。

我們所討論的事件分發(fā),指的就是 MotionEvent從 Activity -> Window -> ViewGroup -> View這個(gè)過程。 (畢竟我們都是 做上層app開發(fā)的,像TP數(shù)據(jù)怎么處理之類的那些是驅(qū)動(dòng)工程師們關(guān)心的問題)。

這個(gè)事件分發(fā)的過程,總結(jié)起來其實(shí)很簡(jiǎn)單,因?yàn)樵搱?chǎng)景和我們?nèi)粘I钪械膱?chǎng)景是一致的。

boss(老板)要做某件事情,但是既然身為boss,人家肯定不用自己動(dòng)手嘛,所以這事交給下面的manager(經(jīng)理)就好了,經(jīng)理好歹也是領(lǐng)導(dǎo)嘛,所以也不用自己親自動(dòng)手,交給下面的worker(工人)就好了,所以轉(zhuǎn)了一圈,干活的還是最下面的工人,如果worker把這活處理了,那么這件事情就算完了,可是也有例外情況,如果worker發(fā)現(xiàn)這個(gè)活自己處理不了,那么這事就只能向上反饋給manager 了,如果manager能把這活給處理了,那自然最好,可是如果manager發(fā)現(xiàn)這活自己也處理不了,那就只能再反饋給boos,讓boss去處理了

事件分發(fā)也是如此。點(diǎn)擊屏幕上的一個(gè)點(diǎn),事件那么經(jīng)過activity -> window,然后傳遞到 界面最外層(或者叫最頂級(jí))的ViewGroup容器中(指的是Relativelayout,LinearLayout等)。 然后最后依次傳遞到被包裹在最里面到 View中。(當(dāng)然,如果View包裹的有幾層的ViewGroup,那么肯定是依次先傳遞給它們, 并且需要注意,最里面的控件完全可以是一個(gè)ViewGroup,而它不必包含View。)

事件被傳遞到了最里面的View,所以交給了View來處理,如果View沒處理,那么就依次倒過來向上回溯。分別交給各級(jí)ViewGroup,如果它們也沒處理,那么就繼續(xù)向上回溯,乃至Window,Activity中來處理。

View就是最底層的worker,而boss,manager都是包裹在它外面的ViewGroup,如果層級(jí)比較復(fù)雜的話,還要經(jīng)過多層才能傳到到最底層的View,(比如經(jīng)過老板,總經(jīng)理,部門經(jīng)理,項(xiàng)目經(jīng)理等多層,事情才分配到最底層的工人這里), View處理不了的,那么就再依次向上反饋給各級(jí)領(lǐng)導(dǎo),看看誰去處理。

該過程其實(shí)就是一個(gè)遞歸的過程。這是android系統(tǒng)中默認(rèn)的事件分發(fā)規(guī)則,如果我們理解了這個(gè)規(guī)則,那么我們就可以解決很多實(shí)際問題,比如 我們可以在某一層ViewGroup中來攔截處理事件,而不用向下傳遞到View中。

Notice:

  1. 因?yàn)?code style="margin: 0px; padding: 0.2em 0px; box-sizing: border-box; line-height: 1.8; vertical-align: middle; display: inline-block; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 12px; background-color: rgba(0, 0, 0, 0.04); border: none !important; border-radius: 3px;">ViewGroup extends View,所以從繼承關(guān)系上來講,ViewGroup也是一個(gè)View,而在本文當(dāng)中說的ViewGroup指的是 RelativeLayoutLinearLayout 等容器類View,而View指的是Button,TextView等不會(huì)再有child的View(因?yàn)樗鼈儾粫?huì)再包裹什么了)。之所以要明確區(qū)分,是因?yàn)樗鼈兊挠行┓椒ǖ膶?shí)現(xiàn)是有區(qū)別的。

  2. 為了方便,在本文當(dāng)中,稱呼控件指的是View或ViewGroup(也可能是View和ViewGroup)。

  3. 在本文當(dāng)中,一個(gè)控件處理了事件,并return了對(duì)應(yīng)的boolean值,我們稱之為 事件被 消費(fèi)了。(如果沒有return值,我們?cè)趺粗肋@個(gè)事件是否被處理了呢?)

既然是事件分發(fā),那么不得不提到到幾個(gè)主要方法。

View中:

     //從名字中就可以看出來,是負(fù)責(zé)分發(fā)事件的方法。
     public boolean dispatchTouchEvent(MotionEvent event)
    
    //該方法中設(shè)置去消費(fèi)事件,比如我們平時(shí)常用的OnClickView,OnLongClick等監(jiān)聽事件都是在這里被調(diào)用的。
    public boolean onTouchEvent(MotionEvent event)

實(shí)際上,View當(dāng)中也有具體負(fù)責(zé)消費(fèi)事件的接口。

    public interface OnTouchListener {        boolean onTouch(View v, MotionEvent event);
    }    
    // 這個(gè)就是我們最常用的 點(diǎn)擊事件方法。
    public interface OnClickListener {        void onClick(View v);
    }

說完了View,那么再來看看ViewGroup中的主要方法。

    //View中也有該方法,但是和ViewGroup中的實(shí)現(xiàn)方式不同
    public boolean dispatchTouchEvent(MotionEvent ev) 
    
    //該方法是ViewGroup特有的,決定是否攔截事件。(默認(rèn)返回false),如果返回true,則事件不會(huì)再向child傳遞了,這個(gè)道理很簡(jiǎn)單,ViewGroup是屬于領(lǐng)導(dǎo), 它才有權(quán)利決定是否攔截事件。而View是最底層的工人,是沒有決定權(quán)的。
    public boolean onInterceptTouchEvent(MotionEvent ev)
    
    
    
    //幾個(gè)相關(guān)方法或接口中,ViewGroup實(shí)現(xiàn)了這兩個(gè),其他都沒重寫,也就是利用View當(dāng)中的實(shí)現(xiàn)

和事件分發(fā)有關(guān)的方法就這幾個(gè)?;镜牡览砥鋵?shí)也不復(fù)雜。(聯(lián)想老板給工人分配任務(wù)的場(chǎng)景就可以了)。
而關(guān)于這個(gè)過程,可以使用偽代碼描述如下:
對(duì)于View:

//@author www.yaoxiaowen.comboolean dispatchTouchEvent(MotionEvent event){    boolean result = false;    if (OnTouchListener.OnTouch(view, event)){
        result = true;
    }    if (!result && onTouchEvent(event)){
        result = true;
    }    return result;
}//實(shí)際上這個(gè)類,處理的比較復(fù)雜,比如分為 ACTION_DOWN, ACTION_UP 之類的,//不過這里是僅僅是作為示例的偽代碼。//@author www.yaoxiaowen.comboolean onTouchEvent(MotionEvent event){    if (clickable || clickable){        if (event.getAction()==ACTION_UP && OnClickListener!=null){
            OnClickListener.onClick();
        }        return true;
    }    return false;
}

而對(duì)于 ViewGroup,偽代碼如下:

//@author www.yaoxiaowen.comboolean dispatchTouchEvent(MotionEvent event){    boolean result = false;    if (onInterceptTouchEvent(event)){        //Notice:這個(gè)地方不是調(diào)用 的this.onTouchEvent。調(diào)用自己那就是死循環(huán)了,既然調(diào)用了父類View的dispatchTouchEvent(),想想View的該方法做什么,所以實(shí)際上就是把事件分發(fā)給自己了。
        //(因?yàn)轭愃苚nTouchEvent, onTouchListener方法在ViewGroup當(dāng)中沒實(shí)現(xiàn),只在父類View當(dāng)中才實(shí)現(xiàn))
        reuslt = super.dispatchTouchEvent();
    }else{         //child可能是 ViewGroup,也可能是 View
        result = child.dispatchTouchEvent(event);
    }    return result;
}

結(jié)合偽代碼,再想象具體的分發(fā)流程。仔細(xì)琢磨一下,就可以大概的了解這個(gè)分發(fā)流程了。

雖然總結(jié)的內(nèi)容的不復(fù)雜,但是實(shí)際上依舊有很多的細(xì)節(jié)。下面我們就結(jié)合具體的源碼來進(jìn)行分析。(具體的源碼非常復(fù)雜,參考了不少博客,然后自己也讀了幾遍,但是也只是理解了一小部分,畢竟自己也水平有限。不過我們要分析的也就是流程的主干內(nèi)容。)

沿著事件分發(fā)的流程。Activity -> Window -> ViewGroup -> View.

先看Activity:

 //Activity.javapublic boolean dispatchTouchEvent(MotionEvent ev) {    //省略部分代碼

    //從這句代碼中,將事件分分發(fā)到了 Window 
    if (getWindow().superDispatchTouchEvent(ev)) {        return true;
    }    return onTouchEvent(ev);
} public boolean onTouchEvent(MotionEvent event) {    if (mWindow.shouldCloseOnTouch(this, event)) {        finish();        return true;
    }    return false;
}

在Activity中,事件被傳遞給了Window,但是此時(shí)要注意了,如果Window當(dāng)中未消費(fèi)事件。(就是返回了false)。那么此時(shí)就調(diào)用Activity自身的onToucheEvent()了。這在后面的流程中,原理是相同的。就是根據(jù)返回值來判斷是否消費(fèi)了事件,整個(gè)事件分發(fā)機(jī)制從上向下,再?gòu)南碌缴线@套遞歸的回溯機(jī)制,就是如此。

Activity當(dāng)中把事件分發(fā)給了Window,不過Window類是abstract的,Window的唯一實(shí)現(xiàn) 就是PhoneWindow,然后這中間的傳遞過程就比較復(fù)雜(因?yàn)橹虚g很多源碼其實(shí)我也沒看懂),反正最后就傳遞到了Activity布局中最外層的ViewGroup中。
ViewGroup中的dispatchTouchEvent方法的實(shí)現(xiàn)就比較長(zhǎng)了,所以我們這里就先從上到下分片段的來看這個(gè)方法的具體實(shí)現(xiàn)。

//ViewGroup.java當(dāng)中的代碼@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {    //首先,在該方法的開頭,并沒有 super()的代碼,這說明 dispatchTouchEvent方法就是在ViewGroup當(dāng)中實(shí)現(xiàn)的,View和ViewGroup當(dāng)中各有一套實(shí)現(xiàn)規(guī)則。

    //......

    boolean handled = false;   
        //......

        // Check for interception.
        final boolean intercepted;        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;            if (!disallowIntercept) {                //Notice:注意 這個(gè)地方調(diào)用了 onInterceptTouchEvent 方法來判斷在該ViewGroup當(dāng)中是否攔截。
                // onInterceptTouchEvent 默認(rèn)的返回值 是false。
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was changed
            } else {
                intercepted = false;
            }
        } else {            // There are no touch targets and this action is not an initial down
            // so this view group continues to intercept touches.
            intercepted = true;
        }        // If intercepted, start normal event dispatch. Also if there is already
        // a view that is handling the gesture, do normal event dispatch.
        if (intercepted || mFirstTouchTarget != null) {
            ev.setTargetAccessibilityFocus(false);
        }

而如果在ViewGroup中不攔截該事件,或者該事件不該在本ViewGroup當(dāng)中處理,那么就會(huì)遍歷它的child進(jìn)行處理。

    //ViewGroup.java 的 dispatchTouchEvent(MotionEvent event)  方法
  // ...... 

                    final int childrenCount = mChildrenCount;                    if (newTouchTarget == null && childrenCount != 0) {                        final float x = ev.getX(actionIndex);                        final float y = ev.getY(actionIndex);                        // Find a child that can receive the event.
                        // Scan children from front to back.
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();                        final View[] children = mChildren;                        for (int i = childrenCount - 1; i >= 0; i--) {                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);                            // If there is a view that has accessibility focus we want it
                            // to get the event first and if not handled we will perform a
                            // normal dispatch. We may do a double iteration but this is
                            // safer given the timeframe.
                            if (childWithAccessibilityFocus != null) {                                if (childWithAccessibilityFocus != child) {                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }                          //......

                            resetCancelNextUpFlag(child);                            //注意這一句調(diào)用,將事件往下進(jìn)行分發(fā)。
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();                                if (preorderedList != null) {                                    // childIndex points into presorted list, find original index
                                    for (int j = 0; j < childrenCount; j++) {                                        if (children[childIndex] == mChildren[j]) {
                                            mLastTouchDownIndex = j;                                            break;
                                        }
                                    }
                                } else {
                                    mLastTouchDownIndex = childIndex;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                     http://www.cnblogs.com/yaoxiaowen/p/6925702.html