1. 被分發(fā)的對象
被分發(fā)的對象是那些?被分發(fā)的對象是用戶觸摸屏幕而產生的點擊事件权纤,事件主要包括:按下、滑動乌妒、抬起與取消汹想。這些事件被封裝成MotionEvent對象。該對象中的主要事件如下表所示:
事件觸發(fā)場景單次事件流中觸發(fā)的次數
MotionEvent.ACTION_DOWN在屏幕按下時1次
MotionEvent.ACTION_MOVE在屏幕上滑動時0次或多次
MotionEvent.ACTION_UP在屏幕抬起時0次或1次
MotionEvent.ACTION_CANCLE滑動超出控件邊界時0次或1次
按下撤蚊、滑動古掏、抬起、取消這幾種事件組成了一個事件流侦啸。事件流以按下為開始槽唾,中間可能有若干次滑動,以抬起或取消作為結束光涂。
在安卓對事件分發(fā)的處理過程中庞萍,主要是對按下事件作分發(fā),進而找到能夠處理按下事件的組件忘闻。對于事件流中后續(xù)的事件(如滑動钝计、抬起等),則直接分發(fā)給能夠處理按下事件的組件。故本文討論的內容則是主要針對按下事件的私恬。
2. 分發(fā)事件的組件
分發(fā)事件的組件交播,也稱為分發(fā)事件者,包括Activity践付、View和ViewGroup。它們三者的一般結構為:
從上圖中可以看出缺厉,Activity包括了ViewGroup永高,ViewGroup又可以包含多個View。
組件特點舉例
Activity安卓視圖類如MainActivity
ViewGroupView的容器提针,可以包含若干View各種布局類
ViewUI類組件的基類如按鈕命爬、文本框
3. 分發(fā)的核心方法
負責對事件進行分發(fā)的方法主要有三個,分別是:
dispatchTouchEvent(
onTouchEvent()
onInterceptTouchEvent()辐脖。
它們并不存在于所有負責分發(fā)的組件中饲宛,其具體情況總結于下面的表格中:
組件dispatchTouchEventonTouchEventonInterceptTouchEvent
Activity存在存在不存在
ViewGroup存在存在存在
View存在存在不存在
從表格中看,dispatchTouchEvent,onTouchEvent方法存在于上文的三個組件中嗜价。而onInterceptTouchEvent為ViewGroup獨有艇抠。這些方法的具體作用在下文作介紹。
ViewGroup類中久锥,實際是沒有onTouchEvent方法的家淤,但是由于ViewGroup繼承自View,而View擁有onTouchEvent方法瑟由,故ViewGroup的對象也是可以調用onTouchEvent方法的絮重。故在表格中表明ViewGroup中存在onTouchEvent方法的。
4. 事件分發(fā)過程
這一小節(jié)是本文的核心內容歹苦,會從整體上對事件的分發(fā)過程作介紹青伤。
對于事件分發(fā)過程從,筆者認為網上的一些教程中的觀點是有誤的殴瘦。
網上部分教程認為事件是從內部(如Button)開始分發(fā)的狠角,這是有誤的。
網上部分教程常使用’向上‘、’向下‘傳播等描述,但又未對‘何為上’叮盘、‘何為下’作解釋送膳。
網上部分教程將Java的子類對象調用父類方法(向上轉型)的過程也稱為‘向上’傳播,即將事件在組件之間的傳播與程序語言多態(tài)特性混為一談咪鲜,讓初學者費解。
子類在覆寫的方法中調用父類的同名方法,被稱為’向上傳播‘厘惦,這也是不對的。
為此在介紹分發(fā)過程之前,先對一些概念作定義:
向下傳播:Activity包括Layout宵蕉,事件從Activity向Layout傳播被稱作’向下傳播‘酝静。Layout包含若干View,事件從Layout向其子View傳播羡玛,也被稱為’向下傳播‘别智。
向上傳播:與’向下傳播‘相反。
’向上轉型‘不能稱為傳播稼稿,即子類對象調用父類方法薄榛,或在覆寫的方法中調用父類方法,都不能稱為傳播让歼。不能將面向對象程序語言中的概念與布局層次中的上下傳播混為一談敞恋。
分發(fā)方法dispatchTouchEvent
從方法的名稱中可以看出該方法主要是負責分發(fā),是安卓事件分發(fā)過程中的核心谋右。事件是如何傳遞的硬猫,主要就是看該方法,理解了這個方法改执,也就理解了安卓事件分發(fā)機制啸蜜。
在了解該方法的核心機制之前,需要知道一個結論:
如果某個組件的該方法返回TRUE,則表示該組件已經對事件進行了處理天梧,不用繼續(xù)調用其余組件的分發(fā)方法盔性,即停止分發(fā)。
如果某個組件的該方法返回FALSE,則表示該組件不能對該事件進行處理呢岗,需要按照規(guī)則繼續(xù)分發(fā)事件冕香。在不復寫該方法的情況下,除了一些特殊的組件后豫,其余組件都是默認返回False的悉尾。后續(xù)有例子說明。
為何返回TRUE就不用繼續(xù)分發(fā)挫酿,而返回FALSE就停止分發(fā)呢构眯?為了解決這個疑問,需要看一看該方法的具體分發(fā)邏輯早龟。為了便于理解惫霸,下面對dispatchTouchEvent方法進行簡化,只保留最核心的邏輯葱弟。
Activity的dispatchTouchEvent方法
// Activity中該方法的核心部分偽代碼publicbooleandispatchTouchEvent(MotionEvent ev){if(child.dispatchTouchEvent(ev)) {returntrue;//如果子View消費了該事件,則返回TRUE壹店,讓調用者知道該事件已被消費}else{returnonTouchEvent(ev);//如果子View沒有消費該事件,則調用自身的onTouchEvent嘗試處理芝加。}}
首先硅卢,從核心邏輯中看出,當事件傳遞給Activity后,它先將事件分發(fā)給子View處理将塑。
如果經過子View層層傳遞或處理后脉顿,該事件被消費了(即返回了TRUE),則Activity的分發(fā)方法也返回TRUE点寥,同樣也表示該事件已經被消費了艾疟。
如果經過子View層層傳遞或處理后,該事件沒有被消費(即返回了FALSE)敢辩,則Activity的分發(fā)方法就不會返回TRUE了汉柒,而是調用onTouchEvent()去處理,看其實際的處理情況责鳍。
如果onTouchEvent消費了事件,那依然能返回TRUE(表示已消費事件)兽间,這個TRUE作為dispatchTouchEvent的返回值历葛,讓調用它的對象知道該Activity已經消費了事件。
如果onTouchEvent沒有消費該事件嘀略,那就返回FALSE(表示未消費事件)恤溶,這個FALSE作為dispatchTouchEvent的返回值,讓調用它的對象知道該Activity沒有消費事件帜羊,需要繼續(xù)處理咒程。
ViewGroup的dispatchTouchEvent方法
// ViewGroup中該方法的核心部分偽代碼publicbooleandispatchTouchEvent(MotionEvent ev){if(!onInterceptTouchEvent(ev)) {returnchild.dispatchTouchEvent(ev);//不攔截,則傳給子View進行分發(fā)處理}else{returnonTouchEvent(ev);//攔截事件讼育,交由自身對象的onTouchEvent方法處理}}
ViewGroup的該方法與Activity的類似帐姻,只是新添了一個onInterceptTouchEvent方法。當事件傳入時奶段,首先會調用onInterceptTouchEvent饥瓷。
如果該方法返回了FALSE(表示不攔截),則交給子View去調用dispatchTouchEvent()方法
如果該方法返回了TRUE(表示攔截)痹籍,則直接交給該ViewGroup對象的onTouchEvent(ev)方法處理呢铆,具體是否能處理以onTouchEvent()的實際情況為準。
實際上蹲缠,在onInterceptTouchEvent返回TURE表示攔截時棺克,實際調用的是super.dispatchTouchEvent方法,即View的該方法线定,進而由該方法調用onTouchEvent.
View的dispatchTouchEvent方法
// View中該方法的核心部分偽代碼publicbooleandispatchTouchEvent(MotionEvent ev){//如果該對象的監(jiān)聽成員變量不為空娜谊,則會調用其onTouch方法,if(mOnTouchListener !=null&& mOnTouchListener.onTouch(this,event)) {returntrue;//若onTouch方法返回TRUE渔肩,則表示消費了該事件因俐,則dispachtouTouchEvent返回TRUE,讓其調用者知道該事件已被消費。}returnonTouchEvent(ev);//若監(jiān)聽成員為空或onTouch沒有消費該事件抹剩,則調用對象自身的onTouchEvent方法處理撑帖。}
從該方法的核心邏輯中可以看到,事件傳遞進來后澳眷,首先會對mOnTouchListener判空胡嘿,如果之前Set了Listener,則會調用其onTouch方法钳踊。
若onTouch方法返回TRUE衷敌,則dispatchTouchEvent也會返回TRUE,表示消費該事件拓瞪。
若onTouch方法返回FALSE缴罗,或者mOnTouchListener本來就是空,則調用自身的onTouchEvent()來處理祭埂,是否消費事件面氓,可以由其返回值判斷。
實際上蛆橡,在View的onTouchEvent方法中舌界,如果設置了onClickListener監(jiān)聽對象,則會調用其onClick方法泰演。
在同時設置了onTouchListener與onClickListener對象的情況下呻拌,正是由于View的dispacthTouchEvent方法會先調用mOnTouchListener的onTouch,才會調用onTouchEvent方法,所以onTouchListener對象的onTouch方法是優(yōu)先于onClickListener對象的onClick方法調用的睦焕。這里只簡單描述結論藐握,具體源碼請查看本文對應的高級篇內容。
小節(jié):dispatchTouchEvent方法
回顧上面Activity垃喊、ViewGroup和View中的dispatchTouchEvent方法趾娃,它們大體都可以分為兩部分,前一部分是交由子View的dispatchTouchEvent方法或onTouch方法進行處理缔御,后一部分是交給自身的onTouchEvent方法處理抬闷。這樣理解的話,就非常便于記憶了耕突。
為了便于記憶和理解笤成,可以將各組件的dispatchTouchEvent方法分為兩部分:
子View的dispatchTouchEvent 或 onTouch方法
自身的onTouchEvent方法
這個結構有點類似于遞歸的過程,就是組件的dispatchTouchEvent會自用子組件的同名方法眷茁,子組件一樣會調用子子組件的同名方法炕泳,直到遞歸到底,然后在從遞歸底部返回上層上祈,直到返回到最上層培遵,整個過程結束浙芙。或者在這個過程中籽腕,事件傳遞到某個子View嗡呼,該子View決定處理該事件,則事件交給其自身的onTouchEvent方法處理皇耗,如果onTouchEvent方法處理不了南窗,再交由父組件的同名方法處理,直到向上傳遞到頂層結束郎楼。
于是万伤,就有了很多教程里的U型圖。
從U型圖中可以發(fā)現呜袁,其實安卓事件分發(fā)的主體思路非常簡單敌买,即由父組件不斷向子組件分發(fā),若子組件能夠處理阶界,則立刻返回放妈。若子組件都不處理,那傳遞到底層的子組件荐操,再返回回來。這個過程類似上面說的遞歸的過程珍策。
這里對這個U型圖做一下說明托启,先看圖中左上角,事件傳到Activity攘宙,首先調用其dispatchTouchEvent方法屯耸,其會傳遞給子View處理,該子View(在圖中是ViewGroup)會調用其dispatchTouchEvent方法蹭劈,如果該方法被覆寫直接返回TRUE,則立即返回Activity疗绣,表示已經消費事件。如果該方法沒有被覆寫或調用了super的同名方法铺韧,則會調用onInterceptTouchEvent方法多矮,如果該方法返回TRUE攔截事件,則交給自身的onTouchEvent處理哈打,如果該方法返回FALSE不攔截塔逃,則繼續(xù)傳給子子View(圖中是View)的dispatchTouchEvent方法處理。此時料仗,再看看這個U型圖湾盗,該遞歸調用已經到底了,若在該方法中的onTouchListener方法不處理立轧,則調用自身的onTouchEvent處理格粪。若還是處理不了躏吊,則從遞歸底部向上返回,依次調用ViewGroup的帐萎、Activity的onTouchEvent方法比伏。
實際上,用這個U型圖來描述安卓的事件分發(fā)機制并不一定準確吓肋,因為同一對象的dispatchTouchEvent方法實際是包含了另外幾個方法的(Activity與View只包含onTouchEvent),但是在這個圖中凳怨,卻是將幾個方法分別畫在不同的框中。所以通過該U型圖來理解事件分發(fā)機智是不準確的是鬼。但是對于部分讀者可能會有所幫助肤舞。要準確理解事件調用機制,還是應該回到上面均蜜,查看三個核心方法的核心邏輯李剖,就能夠準確理解。
強調說明囤耳,安卓事件分發(fā)的‘向上’與‘向下‘傳播篙顺,不要與面向對象程序語言中基類與子類關系,或子類向上調用父類方法等概念搞混淆充择。對于安卓事件分發(fā)的‘向上’與‘向下‘傳播德玫,這里的上與下,是指在’遞歸‘調用過程中的上與下(也體現到U型圖里的上與下)椎麦。這個概念宰僧,體現到布局中,就是外與內观挎。即這里所說的事件’向下‘傳播琴儿,等同于在布局上,由外向內傳播嘁捷,而’向上’傳播造成,等同于在布局上,由內向外傳播雄嚣。
在面向對象程序語言中晒屎,對于子類覆蓋父類方法,或子類調用父類方法缓升,這些‘上’與‘下’的關系夷磕,在布局層面上并沒有跨越布局層次,不要與事件傳播的方向概念相混淆仔沿。
攔截方法onInterceptTouchEvent
該方法是ViewGroup類對象所獨有的坐桩,用于對事件進行提前攔截。在一般情況下封锉,該方法是默認返回FALSE的绵跷,即不攔截膘螟。
如果自定義的ViewGroup希望攔截事件,不希望事件繼續(xù)往子View傳播碾局,可以覆寫該方法荆残,返回TRUE,即可阻止向下的傳播過程净当。
實際上内斯,從上面的核心邏輯的偽代碼中可以看出,在ViewGroup調用dispatchTouchEvent后像啼,肯定會調用該方法俘闯,根據該方法的返回值來確定如何處理。若該方法返回True忽冻,則會將事件攔截掉真朗,就給自身的onTouchEvent處理。如果返回False,則繼續(xù)傳遞給child執(zhí)行分發(fā)流程僧诚。
處理方法onTouchEvent
該方法主要對事件進行處理遮婶,若返回True表示已經處理了事件,若返回False則表示沒有對事件進行處理湖笨,需要繼續(xù)傳遞事件旗扑。一般情況下慈省,默認為FALSE臀防。在View的onTouchEvent方法中辫呻,如果設置了onClickListener監(jiān)聽對象琼锋,則會調用其onClick方法。
5. 總結
本文在介紹了事件分發(fā)基本概念的基礎上缕坎,介紹了負責參與事件分發(fā)的核心方法,包括dispatchTouchEvent()谜叹、onInterceptTouchEvent與onTouchEvent方法。通過偽代碼的形式介紹了這些方法的核心邏輯荷腊,重點分析了在Activity艳悔、ViewGroup與View中的dispatchTouchEvent方法女仰。它們三者中的該方法結構類似抡锈,都是先調用子View的同名方法或者listener方法,然后再調用自身的onTouchEvent方法床三。
這些方法在調用關系中體現了一個類似‘遞歸’的調用過程,通過dispatchTouchEvent將事件傳遞下去杨幼,又通過onTouchEvent將事件傳遞上來撇簿。中間的這一過程可以通過讓onInterceptTouchEvent方法(對于ViewGroup),或者另外的負責分發(fā)的方法返回TRUE差购,均可以提前終止這一類似’遞歸‘的調用過程四瘫,進而讓事件的處理符合我們的預期。