[轉(zhuǎn)載]可能是講解Android事件分發(fā)最好的文章

文/milter(簡(jiǎn)書作者)
原文鏈接:http://www.reibang.com/p/2be492c1df96
著作權(quán)歸作者所有。

我?guī)缀蹩催^國(guó)內(nèi)講解Android事件分發(fā)的所有文章晌坤,但遺憾的是都沒有這篇講的好版扩,原因有二:它闡明了具體的事件分發(fā)機(jī)制的設(shè)計(jì)意圖贸营,讓人既知其然捡偏,又知其所以然;它沒有貼源碼坏瘩,嚇唬本寶寶山林。所以我決定將它翻譯出來,造福廣大Android開發(fā)者怔球。

有時(shí)嚼酝,你必須要自己處理觸摸事件(touch events)而不能依賴于有可用的onSomethingListener。我就遇到過這樣的時(shí)候竟坛,當(dāng)時(shí)我很想有一篇文章能簡(jiǎn)單地解釋觸摸事件是怎樣在視圖層次(view hierarchy)中傳播的闽巩,從而可以將之作為進(jìn)一步深入學(xué)習(xí)的起點(diǎn)。這篇博客是我的一次嘗試担汤,它看起來有點(diǎn)長(zhǎng)涎跨,但這是因?yàn)槲沂前凑沼|摸事件的傳播過程一步一步來寫的。

一些假設(shè)

我們只考慮最重要的四個(gè)觸摸事件崭歧,即:DOWN,MOVE,UP和CANCEL隅很。一個(gè)手勢(shì)(gesture)是一個(gè)事件列,以一個(gè)DOWN事件開始(當(dāng)用戶觸摸屏幕時(shí)產(chǎn)生)率碾,后跟0個(gè)或多個(gè)MOVE事件(當(dāng)用戶四處移動(dòng)手指時(shí)產(chǎn)生)叔营,最后跟一個(gè)單獨(dú)的UP或CANCEL事件(當(dāng)用戶手指離開屏幕或者系統(tǒng)告訴你手勢(shì)(gesture)由于其他原因結(jié)束時(shí)產(chǎn)生)。當(dāng)我們說到“手勢(shì)剩余部分”時(shí)指的是手勢(shì)后續(xù)的MOVE事件和最后的UP或CANCEL事件所宰。

在這里我也不考慮多點(diǎn)觸摸手勢(shì)(我們只假設(shè)用一個(gè)手指)并且忽略多個(gè)MOVE事件可以被歸為一組這一實(shí)際情況绒尊。最后,我們假設(shè)文中的view都沒有注冊(cè)onTouchListener仔粥。

我們將要討論的視圖層次是這樣的:最外層是一個(gè)ViewGroup A垒酬,包含一個(gè)或多個(gè)子view(children),其中一個(gè)子view是ViewGroup B件炉,ViewGroupB中又包含一個(gè)或多個(gè)子view勘究,其中一個(gè)子view是 View C,C不是一個(gè)ViewGroup。這里我們忽略同層級(jí)view之間可能的交叉疊加斟冕。

這里寫圖片描述

假設(shè)用戶首先觸摸到的屏幕上的點(diǎn)是C上的某個(gè)點(diǎn)口糕,該點(diǎn)被標(biāo)記為觸摸點(diǎn)(touch point),DOWN事件就在該點(diǎn)產(chǎn)生磕蛇。然后用戶移動(dòng)手指并最后離開屏幕景描,此過程中手指是否離開C的區(qū)域無關(guān)緊要十办,關(guān)鍵是手勢(shì)(gesture)是從哪里開始的。

默認(rèn)情況

假設(shè)上面的A,B,C都沒有覆寫默認(rèn)的事件傳播行為超棺,那么下面就是事件傳播的過程:
1. DOWN事件被傳到C的onTouchEvent方法中向族,該方法返回false,表示“我不關(guān)心這個(gè)手勢(shì)(gesture)”棠绘。
2. 因此件相,DOWN事件被傳到B的onTouchEvent方法中,該方法同樣返回false氧苍,表示B也不關(guān)心這個(gè)手勢(shì)夜矗。
3. 同樣,因?yàn)锽不關(guān)心這個(gè)手勢(shì)让虐,DOWN事件被傳到A的onTouchEvent方法中紊撕,該方法也返回false。

處理事件

現(xiàn)在赡突,讓我們假設(shè)C實(shí)際上是關(guān)心這個(gè)手勢(shì)(gesture)的对扶,原因可能是C被設(shè)置成可點(diǎn)擊的(clickable)或者你覆寫了C的onTouchEvent方法。
1. DOWN事件被傳遞給C的onTouchEvent方法惭缰,該方法可以做任何它想做的事情浪南,最后返回true。
2. 因?yàn)镃說它正在處理這個(gè)手勢(shì)(gesture)从媚,則DOWN事件將不再被傳遞給B和A的onTouchEvent方法逞泄。
3. 因?yàn)镃說它正在處理這個(gè)手勢(shì)(gesture)患整,所以“手勢(shì)剩余部分”的事件也將傳遞給C的onTouchEvent方法拜效,此時(shí)該方法返回true或false都無關(guān)緊要了,但是為保持一致最好還是返回true各谚。

onInterceptTouchEvent

現(xiàn)在我們將討論一個(gè)新的方法:onInterceptTouchEvent紧憾,它只存在于ViewGroup中,普通的View中沒有這個(gè)方法昌渤。在任何一個(gè)view的onTouchEvent被調(diào)用之前赴穗,它的父輩們(ancestors)將先獲得攔截這個(gè)事件的一次機(jī)會(huì),換句話說膀息,它們可以竊取該事件般眉。在剛才的“處理事件”部分中,我們遺漏了這一過程潜支,現(xiàn)在甸赃,讓我們把它加上:

  1. DOWN事件被傳給A的onInterceptTouchEvent,該方法返回false冗酿,表示它不想攔截埠对。
  2. DOWN又被傳遞給B的onInterceptTouchEvent络断,它也不想攔截,因此該方法也返回false项玛。
  3. 現(xiàn)在貌笨,DOWN事件被傳遞到C的onTouchEvent方法,該方法返回true襟沮,因?yàn)樗胩幚硪栽撌录槭椎氖謩?shì)(gesture)锥惋。
  4. 現(xiàn)在,該手勢(shì)的下一個(gè)事件MOVE到來了臣嚣。這個(gè)MOVE事件再一次被傳遞給A的onInterceptTouchEvent方法净刮,該方法再一次返回false,B也同樣如此硅则。
  5. 然后淹父,MOVE事件被傳遞給C的onTouchEvent,就像在前一部分中一樣怎虫。

這里有兩點(diǎn)需要注意:
1. 雖然ViewGroup A和B的onInterceptTouchEvent方法對(duì)DOWN事件返回了false暑认,后續(xù)的事件依然會(huì)傳遞給它們的onInterceptTouchEvent方法,這一點(diǎn)與onTouchEvent的行為是不一樣的大审。
2. 假如DOWN事件傳給C的onTouchEvent方法時(shí)蘸际,它返回了false,DOWN事件會(huì)繼續(xù)向上傳遞給B和A的onTouchEvent徒扶,即使它們?cè)趏nInterceptTouchEvent方法中說它們不想攔截這個(gè)DOWN事件粮彤,但沒辦法,沒有子View愿意處理該事件姜骡。

個(gè)人理解:感謝@編程世界的孩子 的提醒导坟,由此可見,DOWN事件的處理實(shí)際上經(jīng)歷了一下一上兩個(gè)過程圈澈,下是指A->B的onInterceptTouchEvent惫周,上是指C->B->A的onTouchEvent,當(dāng)然康栈,任意一步的方法中返回true,都能阻止它繼續(xù)傳播递递。

攔截事件

現(xiàn)在,讓我們更進(jìn)一步啥么,假設(shè)B沒有攔截DOWN事件登舞,但它攔截了接下來的MOVE事件。原因可能是B是一個(gè)scrolling view悬荣。當(dāng)用戶僅僅在它的區(qū)域內(nèi)點(diǎn)擊(tap)時(shí)菠秒,被點(diǎn)擊到的元素應(yīng)當(dāng)能處理該點(diǎn)擊事件。但是當(dāng)用戶手指移動(dòng)了一定的距離后隅熙,就不能再視該手勢(shì)(gesture)為點(diǎn)擊了——很明顯稽煤,用戶是想scroll核芽。這就是為什么B要接管該手勢(shì)(gesture)。
下面是事件被處理的順序:

  1. DOWN事件被依次傳到A和B的onInterceptTouchEvent方法中酵熙,它們都返回的false轧简,因?yàn)樗鼈兡壳斑€不想攔截。
  2. DOWN事件傳遞到C的onTouchEvent方法匾二,返回了true哮独。
  3. 在后續(xù)到來MOVE事件時(shí),A的onInterceptTouchEvent方法仍然返回false察藐。
  4. B的onInterceptTouchEvent方法收到了該MOVE事件皮璧,此時(shí)B注意到用戶手指移動(dòng)距離已經(jīng)超過了一定的threshold(或者稱為slop)。因此分飞,B的onInterceptTouchEvent方法決定返回true悴务,從而接管該手勢(shì)(gesture)后續(xù)的處理。
  5. 然后譬猫,這個(gè)MOVE事件將會(huì)被系統(tǒng)變成一個(gè)CANCEL事件讯檐,這個(gè)CANCEL事件將會(huì)傳遞給C的onTouchEvent方法。
  6. 現(xiàn)在染服,又來了一個(gè)MOVE事件别洪,它被傳遞給A的onInterceptTouchEvent方法,A還是不關(guān)心該事件柳刮,因此onInterceptTouchEvent方法繼續(xù)返回false挖垛。
  7. 此時(shí),該MOVE事件將不會(huì)再傳遞給B的onInterceptTouchEvent方法秉颗,該方法一旦返回一次true痢毒,就再也不會(huì)被調(diào)用了。事實(shí)上站宗,該MOVE以及“手勢(shì)剩余部分”都將傳遞給B的onTouchEvent方法(除非A決定攔截“手勢(shì)剩余部分”)闸准。

下面的一些小事情可能會(huì)令你感到吃驚:
1. 如果一個(gè)ViewGroup攔截了最初的DOWN事件益愈,該事件仍然會(huì)傳遞到該ViewGroup的onTouchEvent方法中梢灭。
2. 另一方面,如果ViewGroup攔截了一個(gè)半路的事件(比如蒸其,MOVE)敏释,這個(gè)事件將會(huì)被系統(tǒng)變成一個(gè)CANCEL事件,并傳遞給之前處理該手勢(shì)(gesture)的子View摸袁,而且不會(huì)再傳遞(無論是被攔截的MOVE還是系統(tǒng)生成的CANCEL)給ViewGroup的onTouchEvent方法钥顽。只有再到來的事件才會(huì)傳遞到ViewGroup的onTouchEvent方法中。

從此開始靠汁,你可以更進(jìn)一步蜂大。比如對(duì)mouthful-method (實(shí)在不知道該怎么翻譯啦C鲱怼)requestDisallowInterceptTouchEvent,C可以用該方法阻止B竊取事件。如果你想更加瘋狂一點(diǎn)奶浦,你可以在你自己的ViewGroup中直接覆寫dispatchTouchEvent方法兄墅,并對(duì)傳遞進(jìn)來的事件做任何你想做的處理。但這樣的話你可能會(huì)破壞一些約定澳叉,所以應(yīng)當(dāng)小心隙咸。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市成洗,隨后出現(xiàn)的幾起案子五督,更是在濱河造成了極大的恐慌,老刑警劉巖瓶殃,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件充包,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡遥椿,警方通過查閱死者的電腦和手機(jī)误证,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來修壕,“玉大人愈捅,你說我怎么就攤上這事〈瑞” “怎么了蓝谨?”我有些...
    開封第一講書人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)青团。 經(jīng)常有香客問我譬巫,道長(zhǎng),這世上最難降的妖魔是什么督笆? 我笑而不...
    開封第一講書人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任芦昔,我火速辦了婚禮,結(jié)果婚禮上娃肿,老公的妹妹穿的比我還像新娘咕缎。我一直安慰自己,他們只是感情好料扰,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開白布凭豪。 她就那樣靜靜地躺著,像睡著了一般晒杈。 火紅的嫁衣襯著肌膚如雪嫂伞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音帖努,去河邊找鬼撰豺。 笑死,一個(gè)胖子當(dāng)著我的面吹牛拼余,可吹牛的內(nèi)容都是我干的郑趁。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼姿搜,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼寡润!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起舅柜,我...
    開封第一講書人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤梭纹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后致份,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體变抽,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年氮块,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了绍载。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡滔蝉,死狀恐怖击儡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蝠引,我是刑警寧澤阳谍,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站螃概,受9級(jí)特大地震影響矫夯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜吊洼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一训貌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧冒窍,春花似錦递沪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拘领。三九已至意乓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背届良。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工笆凌, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人士葫。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓乞而,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親慢显。 傳聞我的和親對(duì)象是個(gè)殘疾皇子爪模,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354