- Android開發藝術探索
- 任玉剛
- 6594字
- 2019-01-10 17:37:40
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節將介紹滑動沖突相關的知識,具體情況請看下面的分析。