官术网_书友最值得收藏!

3.4 View的事件分發機制

上面幾節介紹了View的基礎知識以及View的滑動,本節將介紹View的一個核心知識點:事件分發機制。事件分發機制不僅僅是核心知識點更是難點,不少初學者甚至中級開發者面對這個問題時都會覺得困惑。另外,View的另一大難題滑動沖突,它的解決方法的理論基礎就是事件分發機制,因此掌握好View的事件分發機制是十分重要的。本節將深入介紹View的事件分發機制,在3.4.1節會對事件分發機制進行概括性地介紹,而在3.4.2節將結合系統源碼去進一步分析事件分發機制。

3.4.1 點擊事件的傳遞規則

在介紹點擊事件的傳遞規則之前,首先我們要明白這里要分析的對象就是MotionEvent,即點擊事件,關于MotionEvent在3.1節中已經進行了介紹。所謂點擊事件的事件分發,其實就是對MotionEvent事件的分發過程,即當一個MotionEvent產生了以后,系統需要把這個事件傳遞給一個具體的View,而這個傳遞的過程就是分發過程。點擊事件的分發過程由三個很重要的方法來共同完成:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent,下面我們先介紹一下這幾個方法。

public boolean dispatchTouchEvent(MotionEvent ev)

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

public boolean onInterceptTouchEvent(MotionEvent event)

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

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;
        }

上述偽代碼已經將三者的關系表現得淋漓盡致。通過上面的偽代碼,我們也可以大致了解點擊事件的傳遞規則:對于一個根ViewGroup來說,點擊事件產生后,首先會傳遞給它,這時它的dispatchTouchEvent就會被調用,如果這個ViewGroup的onInterceptTouchEvent方法返回true就表示它要攔截當前事件,接著事件就會交給這個ViewGroup處理,即它的onTouchEvent方法就會被調用;如果這個ViewGroup的onInterceptTouchEvent方法返回false就表示它不攔截當前事件,這時當前事件就會繼續傳遞給它的子元素,接著子元素的dispatchTouchEvent方法就會被調用,如此反復直到事件被最終處理。

當一個View需要處理事件時,如果它設置了OnTouchListener,那么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方法會被調用。這個過程其實也很好理解,我們可以換一種思路,假如點擊事件是一個難題,這個難題最終被上級領導分給了一個程序員去處理(這是事件分發過程),結果這個程序員搞不定(onTouchEvent返回了false),現在該怎么辦呢?難題必須要解決,那只能交給水平更高的上級解決(上級的onTouchEvent被調用),如果上級再搞不定,那只能交給上級的上級去解決,就這樣將難題一層層地向上拋,這是公司內部一種很常見的處理問題的過程。從這個角度來看,View的事件傳遞過程還是很貼近現實的,畢竟程序員也生活在現實中。

關于事件傳遞的機制,這里給出一些結論,根據這些結論可以更好地理解整個傳遞機制,如下所示。

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

(2)正常情況下,一個事件序列只能被一個View攔截且消耗。這一條的原因可以參考(3),因為一旦一個元素攔截了某此事件,那么同一個事件序列內的所有事件都會直接交給它處理,因此同一個事件序列中的事件不能分別由兩個View同時處理,但是通過特殊手段可以做到,比如一個View將本該自己處理的事件通過onTouchEvent強行傳遞給其他View處理。

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

(4)某個View一旦開始處理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那么同一事件序列中的其他事件都不會再交給它來處理,并且事件將重新交由它的父元素去處理,即父元素的onTouchEvent會被調用。意思就是事件一旦交給一個View處理,那么它就必須消耗掉,否則同一事件序列中剩下的事件就不再交給它來處理了,這就好比上級交給程序員一件事,如果這件事沒有處理好,短期內上級就不敢再把事情交給這個程序員做了,二者是類似的道理。

(5)如果View不消耗除ACTION_DOWN以外的其他事件,那么這個點擊事件會消失,此時父元素的onTouchEvent并不會被調用,并且當前View可以持續收到后續的事件,最終這些消失的點擊事件會傳遞給Activity處理。

(6)ViewGroup默認不攔截任何事件。Android源碼中ViewGroup的onInterceptTouch-Event方法默認返回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事件除外。

3.4.2 事件分發的源碼解析

上一節分析了View的事件分發機制,本節將會從源碼的角度去進一步分析、證實上面的結論。

1.Activity對點擊事件的分發過程

點擊事件用MotionEvent來表示,當一個點擊操作發生時,事件最先傳遞給當前Activity,由Activity的dispatchTouchEvent來進行事件派發,具體的工作是由Activity內部的Window來完成的。Window會將事件傳遞給decor view, decor view一般就是當前界面的底層容器(即setContentView所設置的View的父容器),通過Activity.getWindow. getDecorView()可以獲得。我們先從Activity的dispatchTouchEvent開始分析。

源碼:Activity#dispatchTouchEvent

        public boolean dispatchTouchEvent(MotionEvent ev) {
            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
                onUserInteraction();
            }
            if (getWindow().superDispatchTouchEvent(ev)) {
                return true;
            }
            return onTouchEvent(ev);
        }

現在分析上面的代碼。首先事件開始交給Activity所附屬的Window進行分發,如果返回true,整個事件循環就結束了,返回false意味著事件沒人處理,所有View的onTouchEvent都返回了false,那么Activity的onTouchEvent就會被調用。

接下來看Window是如何將事件傳遞給ViewGroup的。通過源碼我們知道,Window是個抽象類,而Window的superDispatchTouchEvent方法也是個抽象方法,因此我們必須找到Window的實現類才行。

源碼:Window#superDispatchTouchEvent

        public abstract boolean superDispatchTouchEvent(MotionEvent event);

那么到底Window的實現類是什么呢?其實是PhoneWindow,這一點從Window的源碼中也可以看出來,在Window的說明中,有這么一段話:

Abstract base class for a top-level window look and behavior policy. An instance of this class should be used as the top-level view added to the window manager. It provides standard UI policies such as a background, title area, default key processing, etc.

The only existing implementation of this abstract class is android. policy.PhoneWindow, which you should instantiate when needing a Window. Eventually that class will be refactored and a factory method added for creating Window instances without knowing about a particular implementation.

上面這段話的大概意思是:Window類可以控制頂級View的外觀和行為策略,它的唯一實現位于android.policy.PhoneWindow中,當你要實例化這個Window類的時候,你并不知道它的細節,因為這個類會被重構,只有一個工廠方法可以使用。盡管這看起來有點模糊,不過我們可以看一下android.policy.PhoneWindow這個類,盡管實例化的時候此類會被重構,僅是重構而已,功能是類似的。

由于Window的唯一實現是PhoneWindow,因此接下來看一下PhoneWindow是如何處理點擊事件的,如下所示。

源碼:PhoneWindow#superDispatchTouchEvent

        public boolean superDispatchTouchEvent(MotionEvent event) {
            return mDecor.superDispatchTouchEvent(event);
        }

到這里邏輯就很清晰了,PhoneWindow將事件直接傳遞給了DecorView,這個DecorView是什么呢?請看下面:

        private final class DecorView extends FrameLayout implements RootViewSur-
        faceTaker

        // This is the top-level view of the window, containing the window decor.
        private DecorView mDecor;

        @Override
        public final View getDecorView() {
            if (mDecor == null) {
                installDecor();
            }
            return mDecor;
        }

我們知道,通過((ViewGroup)getWindow().getDecorView().findViewById(android.R.id. content)).getChildAt(0)這種方式就可以獲取Activity所設置的View,這個mDecor顯然就是getWindow().getDecorView()返回的View,而我們通過setContentView設置的View是它的一個子View。目前事件傳遞到了DecorView這里,由于DecorView繼承自FrameLayout且是父View,所以最終事件會傳遞給View。換句話來說,事件肯定會傳遞到View,不然應用如何響應點擊事件呢?不過這不是我們的重點,重點是事件到了View以后應該如何傳遞,這對我們更有用。從這里開始,事件已經傳遞到頂級View了,即在Activity中通過setContentView所設置的View,另外頂級View也叫根View,頂級View一般來說都是ViewGroup。

3.頂級View對點擊事件的分發過程

關于點擊事件如何在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是一致的,如此循環,完成整個事件的分發。

首先看ViewGroup對點擊事件的分發過程,其主要實現在ViewGroup的dispatchTouch-Event方法中,這個方法比較長,這里分段說明。先看下面一段,很顯然,它描述的是當前View是否攔截點擊事情這個邏輯。

        // Check for interception.
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget ! = null) {
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_
            INTERCEPT) ! = 0;
            if (! disallowIntercept) {
                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;
        }

從上面代碼我們可以看出,ViewGroup在如下兩種情況下會判斷是否要攔截當前事件:事件類型為ACTION_DOWN或者mFirstTouchTarget ! = null。ACTION_DOWN事件好理解,那么mFirstTouchTarget ! = null是什么意思呢?這個從后面的代碼邏輯可以看出來,當事件由ViewGroup的子元素成功處理時,mFirstTouchTarget會被賦值并指向子元素,換種方式來說,當ViewGroup不攔截事件并將事件交由子元素處理時mFirstTouchTarget ! = null。反過來,一旦事件由當前ViewGroup攔截時,mFirstTouchTarget ! = null就不成立。那么當ACTION_MOVE和ACTION_UP事件到來時,由于(actionMasked == MotionEvent. ACTION_DOWN || mFirstTouchTarget ! = null)這個條件為false,將導致ViewGroup的onInterceptTouchEvent不會再被調用,并且同一序列中的其他事件都會默認交給它處理。

當然,這里有一種特殊情況,那就是FLAG_DISALLOW_INTERCEPT標記位,這個標記位是通過requestDisallowInterceptTouchEvent方法來設置的,一般用于子View中。FLAG_DISALLOW_INTERCEPT一旦設置后,ViewGroup將無法攔截除了ACTION_DOWN以外的其他點擊事件。為什么說是除了ACTION_DOWN以外的其他事件呢?這是因為ViewGroup在分發事件時,如果是ACTION_DOWN就會重置FLAG_DISALLOW_INTERCEPT這個標記位,將導致子View中設置的這個標記位無效。因此,當面對ACTION_DOWN事件時,ViewGroup總是會調用自己的onInterceptTouchEvent方法來詢問自己是否要攔截事件,這一點從源碼中也可以看出來。在下面的代碼中,ViewGroup會在ACTION_DOWN事件到來時做重置狀態的操作,而在resetTouchState方法中會對FLAG_DISALLOW_INTERCEPT進行重置,因此子View調用request- DisallowInterceptTouchEvent方法并不能影響ViewGroup對ACTION_DOWN事件的處理。

        // Handle an initial down.
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Throw away all previous state when starting a new touch gesture.
            // The framework may have dropped the up or cancel event for the previous
              gesture
            // due to an app switch, ANR, or some other state change.
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }

從上面的源碼分析,我們可以得出結論:當ViewGroup決定攔截事件后,那么后續的點擊事件將會默認交給它處理并且不再調用它的onInterceptTouchEvent方法,這證實了3.4.1節末尾處的第3條結論。FLAG_DISALLOW_INTERCEPT這個標志的作用是讓ViewGroup不再攔截事件,當然前提是ViewGroup不攔截ACTION_DOWN事件,這證實了3.4.1節末尾處的第11條結論。那么這段分析對我們有什么價值呢?總結起來有兩點:第一點,onInterceptTouchEvent不是每次事件都會被調用的,如果我們想提前處理所有的點擊事件,要選擇dispatchTouchEvent方法,只有這個方法能確保每次都會調用,當然前提是事件能夠傳遞到當前的ViewGroup;另外一點,FLAG_DISALLOW_INTERCEPT標記位的作用給我們提供了一個思路,當面對滑動沖突時,我們可以是不是考慮用這種方法去解決問題?關于滑動沖突,將在3.5節進行詳細分析。

接著再看當ViewGroup不攔截事件的時候,事件會向下分發交由它的子View進行處理,這段源碼如下所示。

        final View[] children = mChildren;
        for (int i = childrenCount -1; i >= 0; i--) {
            final int childIndex = customOrder
                    ? getChildDrawingOrder(childrenCount, i) : i;
            final View child = (preorderedList == null)
                    ? children[childIndex] : preorderedList.get(childIndex);
            if (! canViewReceivePointerEvents(child)
                    || ! isTransformedTouchPointInView(x, y, child, null)) {
                continue;
              }

              newTouchTarget = getTouchTarget(child);
              if (newTouchTarget ! = null) {
                  // Child is already receiving touch within its bounds.
                  // Give it the new pointer in addition to the ones it is handling.
                  newTouchTarget.pointerIdBits |= idBitsToAssign;
                  break;
              }

              resetCancelNextUpFlag(child);
              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();
                  newTouchTarget = addTouchTarget(child, idBitsToAssign);
                  alreadyDispatchedToNewTouchTarget = true;
                  break;
              }
          }

上面這段代碼邏輯也很清晰,首先遍歷ViewGroup的所有子元素,然后判斷子元素是否能夠接收到點擊事件。是否能夠接收點擊事件主要由兩點來衡量:子元素是否在播動畫和點擊事件的坐標是否落在子元素的區域內。如果某個子元素滿足這兩個條件,那么事件就會傳遞給它來處理??梢钥吹剑琩ispatchTransformedTouchEvent實際上調用的就是子元素的dispatchTouchEvent方法,在它的內部有如下一段內容,而在上面的代碼中child傳遞的不是null,因此它會直接調用子元素的dispatchTouchEvent方法,這樣事件就交由子元素處理了,從而完成了一輪事件分發。

        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else {
            handled = child.dispatchTouchEvent(event);
        }

如果子元素的dispatchTouchEvent返回true,這時我們暫時不用考慮事件在子元素內部是怎么分發的,那么mFirstTouchTarget就會被賦值同時跳出for循環,如下所示。

        newTouchTarget = addTouchTarget(child, idBitsToAssign);
        alreadyDispatchedToNewTouchTarget = true;
        break;

這幾行代碼完成了mFirstTouchTarget的賦值并終止對子元素的遍歷。如果子元素的dispatchTouchEvent返回false, ViewGroup就會把事件分發給下一個子元素(如果還有下一個子元素的話)。

其實mFirstTouchTarget真正的賦值過程是在addTouchTarget內部完成的,從下面的addTouchTarget方法的內部結構可以看出,mFirstTouchTarget其實是一種單鏈表結構。mFirstTouchTarget是否被賦值,將直接影響到ViewGroup對事件的攔截策略,如果mFirstTouchTarget為null,那么ViewGroup就默認攔截接下來同一序列中所有的點擊事件,這一點在前面已經做了分析。

        private TouchTarget addTouchTarget(View child, int pointerIdBits) {
            TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
            target.next = mFirstTouchTarget;
            mFirstTouchTarget = target;
            return target;
        }

如果遍歷所有的子元素后事件都沒有被合適地處理,這包含兩種情況:第一種是ViewGroup沒有子元素;第二種是子元素處理了點擊事件,但是在dispatchTouchEvent中返回了false,這一般是因為子元素在onTouchEvent中返回了false。在這兩種情況下,ViewGroup會自己處理點擊事件,這里就證實了3.4.1節中的第4條結論,代碼如下所示。

        // Dispatch to touch targets.
        if (mFirstTouchTarget == null) {
              // No touch targets so treat this as an ordinary view.
              handled = dispatchTransformedTouchEvent(ev, canceled, null,
                      TouchTarget.ALL_POINTER_IDS);
          }

注意上面這段代碼,這里第三個參數child為null,從前面的分析可以知道,它會調用super.dispatchTouchEvent(event),很顯然,這里就轉到了View的dispatchTouchEvent方法,即點擊事件開始交由View來處理,請看下面的分析。

4.View對點擊事件的處理過程

View對點擊事件的處理過程稍微簡單一些,注意這里的View不包含ViewGroup。先看它的dispatchTouchEvent方法,如下所示。

        public boolean dispatchTouchEvent(MotionEvent event) {
            boolean result = false;
            ...

            if (onFilterTouchEventForSecurity(event)) {
                //noinspection SimplifiableIfStatement
                ListenerInfo li = mListenerInfo;
                if (li ! = null && li.mOnTouchListener ! = null
                        && (mViewFlags & ENABLED_MASK) == ENABLED
                        && li.mOnTouchListener.onTouch(this, event)) {
                    result = true;
                }

                if (! result && onTouchEvent(event)) {
                    result = true;
                }
            }
            ...

            return result;
        }

View對點擊事件的處理過程就比較簡單了,因為View(這里不包含ViewGroup)是一個單獨的元素,它沒有子元素因此無法向下傳遞事件,所以它只能自己處理事件。從上面的源碼可以看出View對點擊事件的處理過程,首先會判斷有沒有設置OnTouchListener,如果OnTouchListener中的onTouch方法返回true,那么onTouchEvent就不會被調用,可見OnTouchListener的優先級高于onTouchEvent,這樣做的好處是方便在外界處理點擊事件。

接著再分析onTouchEvent的實現。先看當View處于不可用狀態下點擊事件的處理過程,如下所示。很顯然,不可用狀態下的View照樣會消耗點擊事件,盡管它看起來不可用。

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags &
            PFLAG_PRESSED) ! = 0) {
                setPressed(false);
            }
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return (((viewFlags & CLICKABLE) == CLICKABLE ||
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
        }

接著,如果View設置有代理,那么還會執行TouchDelegate的onTouchEvent方法,這個onTouchEvent的工作機制看起來和OnTouchListener類似,這里不深入研究了。

        if (mTouchDelegate ! = null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

下面再看一下onTouchEvent中對點擊事件的具體處理,如下所示。

        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_UP:
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) ! = 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) ! = 0 || prepressed) {
                        ...
                        if (! mHasPerformedLongPress) {
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback();

                            //  Only  perform  take  click  actions  if  we  were  in  the
    pressed state
                            if (! focusTaken) {
                                // Use a Runnable and post this rather than calling
                                // performClick directly. This lets other visual
                                    state
                                // of the view update before click actions start.
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (! post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }
                        ...
                    }
                    break;
            }
            ...
            return true;
        }

從上面的代碼來看,只要View的CLICKABLE和LONG_CLICKABLE有一個為true,那么它就會消耗這個事件,即onTouchEvent方法返回true,不管它是不是DISABLE狀態,這就證實了3.4.1節末尾處的第8、第9和第10條結論。然后就是當ACTION_UP事件發生時,會觸發performClick方法,如果View設置了OnClickListener,那么performClick方法內部會調用它的onClick方法,如下所示。

        public boolean performClick() {
            final boolean result;
            final ListenerInfo li = mListenerInfo;
            if (li ! = null && li.mOnClickListener ! = null) {
                playSoundEffect(SoundEffectConstants.CLICK);
                li.mOnClickListener.onClick(this);
                result = true;
            } else {
                result = false;
            }
              sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
              return result;
          }

View的LONG_CLICKABLE屬性默認為false,而CLICKABLE屬性是否為false和具體的View有關,確切來說是可點擊的View其CLICKABLE為true,不可點擊的View其CLICKABLE為false,比如Button是可點擊的,TextView是不可點擊的。通過setClickable和setLongClickable可以分別改變View的CLICKABLE和LONG_CLICKABLE屬性。另外,setOnClickListener會自動將View的CLICKABLE設為true, setOnLongClickListener則會自動將View的LONG_CLICKABLE設為true,這一點從源碼中可以看出來,如下所示。

        public void setOnClickListener(OnClickListener l) {
            if (! isClickable()) {
                setClickable(true);
            }
            getListenerInfo().mOnClickListener = l;
        }
        public void setOnLongClickListener(OnLongClickListener l) {
            if (! isLongClickable()) {
                setLongClickable(true);
            }
            getListenerInfo().mOnLongClickListener = l;
        }

到這里,點擊事件的分發機制的源碼實現已經分析完了,結合3.4.1節中的理論分析和相關結論,讀者就可以更好地理解事件分發了。在3.5節將介紹滑動沖突相關的知識,具體情況請看下面的分析。

主站蜘蛛池模板: 行唐县| 天柱县| 临泉县| 丹巴县| 遂溪县| 紫金县| 文成县| 梅河口市| 山西省| 遵义县| 繁昌县| 波密县| 安仁县| 如东县| 林西县| 外汇| 义乌市| 区。| 古田县| 彝良县| 庆元县| 海口市| 顺义区| 余江县| 眉山市| 邯郸市| 铅山县| 昆明市| 大庆市| 德兴市| 柳州市| 三门峡市| 九龙坡区| 留坝县| 康乐县| 建湖县| 龙江县| 通榆县| 东宁县| 武乡县| 榆树市|