Android事件攔截機(jī)制分析

導(dǎo)語(yǔ)

當(dāng)Android系統(tǒng)捕獲到用戶的各種輸入事件后,如何準(zhǔn)確地傳遞給真正需要這個(gè)事件的控件呢手销?Android給我們提供了一整套完善的事件傳遞充岛、處理機(jī)制,來(lái)幫助開發(fā)者完成準(zhǔn)確的事件分配與處理筛峭。

主要內(nèi)容

  • 了解MotionEvent
  • 了解事件攔截機(jī)制

具體內(nèi)容

要了解觸摸事件的攔截機(jī)制,首先要了解什么是觸摸事件陪每?顧名思義影晓,觸摸事件就是捕獲觸摸屏幕后產(chǎn)生的事件镰吵。當(dāng)點(diǎn)擊一個(gè)按鈕時(shí),通常就會(huì)產(chǎn)生兩個(gè)或者三個(gè)事件——按鈕按下挂签,這是事件一疤祭;如果不小心滑動(dòng)一點(diǎn),這就是事件二饵婆;當(dāng)手抬起勺馆,這是事件三。Android為觸摸事件封裝了一個(gè)類——MotionEvent侨核,如果重寫onTouchEvent()方法草穆,那就會(huì)發(fā)現(xiàn)給方法的參數(shù)就是這樣一個(gè)MotionEvent。其實(shí)搓译,只要是重寫觸摸相關(guān)的方法悲柱,參數(shù)一般都含有MotionEvent,可見它的重要性些己。
如此看來(lái)豌鸡,觸摸事件還是比較簡(jiǎn)單的,其實(shí)就是一個(gè)動(dòng)作類型加坐標(biāo)而已段标。但是我們知道涯冠,Android的View結(jié)構(gòu)是樹形結(jié)構(gòu),也就是說(shuō)逼庞,View可以放在ViewGroup里面蛇更,通過不同的組合來(lái)實(shí)現(xiàn)不同的樣式。那么問題來(lái)了往堡,View放在一個(gè)ViewGroup里面械荷,這個(gè)ViewGroup又放在另一個(gè)ViewGroup里面,甚至還有可能繼續(xù)嵌套虑灰,一層層地疊起來(lái)吨瞎。可我們的觸摸事件就一個(gè)穆咐,到底該分給誰(shuí)呢颤诀?同一個(gè)事件,子View和父ViewGroup都有可能想要進(jìn)行處理对湃。因此崖叫,這就產(chǎn)生了“事件攔截”這個(gè)“霸氣”的稱呼。

MotionEvent

在MotionEvent里面封裝了不少好東西拍柒,比如觸摸點(diǎn)的坐標(biāo)心傀,可以通過event.getX()方法和event.getRawX()方法取出坐標(biāo)點(diǎn);再比如獲得點(diǎn)擊的事件類型拆讯,可以通過不同的Action來(lái)進(jìn)行區(qū)分脂男,并實(shí)現(xiàn)不同的邏輯养叛。

Action分以下三種:

  • MotionEvent.ACTION_DOWN,當(dāng)按下時(shí)觸發(fā)宰翅。
  • MotionEvent.ACTION_MOVE弃甥,當(dāng)移動(dòng)時(shí)觸發(fā)。
  • MotionEvent.ACTION_UP汁讼,當(dāng)抬起時(shí)觸發(fā)淆攻。
    代碼如下所示:
@Override
public boolean onTouchEvent(MotionEvent event) {
    switch(event.getAction()) {
        // 當(dāng)按下時(shí)觸發(fā)。
        case MotionEvent.ACTION_DOWN:

        break;
        // 當(dāng)移動(dòng)時(shí)觸發(fā)嘿架。
        case MotionEvent.ACTION_MOVE:

        break;
        // 當(dāng)抬起時(shí)觸發(fā)瓶珊。
        case MotionEvent.ACTION_UP:

        break;
    }
}

事件攔截機(jī)制

事件攔截可以很復(fù)雜,也可以很簡(jiǎn)單眶明。但是初學(xué)者卻經(jīng)臣瓒荆“卡”在這里不知道如何繼續(xù)進(jìn)行筐高,所以我們不想通過過多的源代碼讓大家不知所措搜囱。我們通過最直觀的Log信息,讓大家先有一個(gè)大概的了解柑土,知道事件攔截的本質(zhì)蜀肘,然后大家在結(jié)合源代碼學(xué)習(xí)時(shí),就可以有方向稽屏、有目的性去理解了扮宠。
首先,請(qǐng)想象一下生活中非常常見的場(chǎng)景:假設(shè)你所在的公司狐榔,有一個(gè)總經(jīng)理坛增,級(jí)別最高;他下面有一個(gè)部長(zhǎng)薄腻,級(jí)別次之收捣;最低層,就是干活的你庵楷,沒有級(jí)別“瞻現(xiàn)在董事會(huì)交給總經(jīng)理一項(xiàng)任務(wù),總經(jīng)理將這項(xiàng)任務(wù)布置給了部長(zhǎng)尽纽,部長(zhǎng)又把任務(wù)安排給了你咐蚯。而當(dāng)你好不容易干完活了,你就把任務(wù)交給部長(zhǎng)弄贿,部長(zhǎng)覺得任務(wù)完成得不錯(cuò)春锋,于是就簽了他的名字交給總經(jīng)理,總經(jīng)理看了也覺得不錯(cuò)差凹,就業(yè)簽了名字交給董事會(huì)期奔。這樣豆拨,一個(gè)任務(wù)就順利完成了。如果大家能非常清楚地理解這樣一個(gè)場(chǎng)景能庆,那么對(duì)于事件攔截機(jī)制施禾,你就超過了40%的開發(fā)者了。下面搁胆,我們?cè)賮?lái)超越剩下的開發(fā)者弥搞。為了能過方便地了解整個(gè)事件的流程,我們?cè)O(shè)計(jì)了這樣一個(gè)實(shí)例渠旁。

  • 一個(gè)總經(jīng)理——MyViewGroupA攀例,最外層的ViewGroup(紅色)。
  • 一個(gè)部長(zhǎng)——MyViewGroupB顾腊,中間的ViewGroup(綠色)粤铭。
  • 一個(gè)干活的你——MyView,在最底層(藍(lán)色)杂靶。
    本實(shí)例的整個(gè)布局結(jié)構(gòu)如下圖所示梆惯。
布局結(jié)構(gòu)

代碼非常簡(jiǎn)單,只是重寫了事件攔截和處理的幾個(gè)方法吗垮,并給它加上了一些Log而已垛吗。
對(duì)于ViewGroup來(lái)說(shuō),重寫了如下所示的三個(gè)方法烁登。

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    Log.d("cc", "ViewGroupA dispatchTouchEvent" + ev.getAction());
    return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    Log.d("cc", "ViewGroupA onInterceptTouchEvent" + ev.getAction());
    return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
    Log.d("cc", "ViewGroupA onTouchEvent" + event.getAction());
    return super.onTouchEvent(event);
}

而對(duì)于View來(lái)說(shuō)怯屉,重寫了如下所示的兩個(gè)方法。

@Override
public boolean onTouchEvent(MotionEvent event) {
    Log.d("cc", "View onTouchEvent" + event.getAction());
    return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    Log.d("cc", "View dispatchTouchEvent" + event.getAction());
    return super.dispatchTouchEvent(event);
}

從上面的代碼中可以看到饵沧,ViewGroup級(jí)別比較高锨络,比View多了一個(gè)方法——onInterceptTouchEvent()。這個(gè)方法看名字就能猜到是事件攔截的核心方法狼牺。我們先不修改任何返回值羡儿,只是點(diǎn)擊一下View,然后看Log會(huì)怎樣記錄我們的操作和程序響應(yīng)锁右。點(diǎn)擊View后的Log如下所示失受。

D/cc: ViewGroupA dispatchTouchEvent0 
D/cc: ViewGroupA onInterceptTouchEvent0 
D/cc: ViewGroupB dispatchTouchEvent0 
D/cc: ViewGroupB onInterceptTouchEvent0 
D/cc: View dispatchTouchEvent0 
D/cc: View onTouchEvent0 
D/cc: ViewGroupB onTouchEvent0 
D/cc: ViewGroupA onTouchEvent0

可以看見,正常情況下咏瑟,事件的傳遞順序是:

  • 總經(jīng)理(MyViewGroupA)→部長(zhǎng)(MyViewGroupB)→你(View)拂到。事件傳遞的時(shí)候,先執(zhí)行dispatchTouchEvent()方法码泞,再執(zhí)行onInterceptTouchEvent()方法兄旬。

事件的處理順序是:

  • 你(View)→部長(zhǎng)(MyViewGroupB)→總經(jīng)理(MyViewGroupA)。事件處理都是執(zhí)行onTouchEvent()方法。
  • 事件傳遞的返回值非常容易理解:true领铐,攔截悯森,不繼續(xù);false绪撵,不攔截瓢姻,繼續(xù)流程。
  • 事件處理的返回值也類似:true音诈,處理了幻碱,不用審核了;false细溅,給上級(jí)處理褥傍。
  • 初始情況下,返回值都是false喇聊。

這里為了能夠方便大家理解事件攔截的過程恍风,在事件傳遞中,我們只關(guān)心onInterceptTouchEvent()誓篱,而dispatchTouchEvent()方法雖然是事件分發(fā)的第一步朋贬,但一般情況下,我們不太會(huì)去改寫這個(gè)方法燕鸽,所以暫時(shí)不管這個(gè)方法兄世√淅保可以把上面的整個(gè)事件過程整理成如下圖所示的一張圖啊研。

事件處理過程

相信大家只要把MyView想成自己,就能充分理解事件分發(fā)鸥拧、攔截党远、處理的整個(gè)流程了。
  下面我們稍微改動(dòng)一下富弦,假設(shè)總經(jīng)理(MyViewGroupA)發(fā)現(xiàn)這個(gè)任務(wù)太簡(jiǎn)單了沟娱,覺得自己完成就可以了,完全沒必要再找下屬腕柜。因此時(shí)間就被總經(jīng)理(MyViewGroupA)使用onInterceptTouchEvent()方法把事件給攔截了济似,即讓MyViewGroupA的onInterceptTouchEvent()方法返回true,我們?cè)賮?lái)看一下Log盏缤。

D/cc: ViewGroupA dispatchTouchEvent0 
D/cc: ViewGroupA onInterceptTouchEvent0 
D/cc: ViewGroupA onTouchEvent0

跟我們?cè)O(shè)想的一樣砰蠢,總經(jīng)理(MyViewGroupA)把所有事情都干了,沒后面人的事了唉铜。同理台舱,我們讓部長(zhǎng)(MyViewGroupB)也來(lái)當(dāng)一次好人,即讓部長(zhǎng)(MyViewGroupB)使用onInterceptTouchEvent()方法返回true潭流,把事件攔截下來(lái)竞惋,Log就會(huì)使以下這樣柜去。

D/cc: ViewGroupA dispatchTouchEvent0 
D/cc: ViewGroupA onInterceptTouchEvent0 
D/cc: ViewGroupB dispatchTouchEvent0 
D/cc: ViewGroupB onInterceptTouchEvent0 
D/cc: ViewGroupB onTouchEvent0 
D/cc: ViewGroupA onTouchEvent0

可以看到,這次部長(zhǎng)(MyViewGroupB)當(dāng)了好人拆宛,你(MyView)就不用干活了嗓奢。
那么這兩種情況,也可以整理成類似如上圖所示的圖浑厚。

  • 總經(jīng)理(MyViewGroupA)攔截事件蔓罚,如下圖所示。
事件攔截A
  • 部長(zhǎng)(MyViewGroupB)攔截事件瞻颂,如下圖所示豺谈。
事件攔截B

對(duì)事件的分發(fā)、攔截贡这,現(xiàn)在大家應(yīng)該比較清楚了茬末,下面我們?cè)倏纯词录奶幚怼O葋?lái)看看底層人民——你(MyView)盖矫。最開始的時(shí)候講了丽惭,當(dāng)你處理完任務(wù)后會(huì)向上級(jí)報(bào)告,需要上級(jí)的確認(rèn)辈双,所以你的事件處理返回false责掏。那么你突然有一天受不了老板的壓迫了,罷工不干了湃望,那么你的任務(wù)就沒人做了换衬,也就不用報(bào)告上機(jī)了,所以就直接返回true≈ぐ牛現(xiàn)在再來(lái)看看Log瞳浦,如下所示。

D/cc: ViewGroupA dispatchTouchEvent0 
D/cc: ViewGroupA onInterceptTouchEvent0 
D/cc: ViewGroupB dispatchTouchEvent0 
D/cc: ViewGroupB onInterceptTouchEvent0 
D/cc: View dispatchTouchEvent0 
D/cc: View onTouchEvent0

可以看見废士,事件傳遞跟以前一樣叫潦,但是事件處理,到你(MyView)這就結(jié)束了官硝,因?yàn)槟惴祷豻rue矗蕊,表示不用向上級(jí)匯報(bào)了。這時(shí)氢架,我們同樣來(lái)整理下關(guān)系圖傻咖,如下所示。

事件處理A

你(MyView)終于翻身做了主达箍,決定了自己的命運(yùn)没龙。但是,如果部長(zhǎng)(MyViewGroupB)看到了你的報(bào)告,覺得太丟人硬纤,不敢給經(jīng)理看解滓,所以他就偷偷地返回true,整個(gè)事件也就到此為止了筝家,即部長(zhǎng)(MyViewGroupB)將自己的onTouchEvent返回true洼裤,Log如下所示。

D/cc: ViewGroupA dispatchTouchEvent0 
D/cc: ViewGroupA onInterceptTouchEvent0 
D/cc: ViewGroupB dispatchTouchEvent0 
D/cc: ViewGroupB onInterceptTouchEvent0 
D/cc: View dispatchTouchEvent0 
D/cc: View onTouchEvent0 
D/cc: ViewGroupB onTouchEvent0

他們之間的關(guān)系圖如下圖所示溪王。

事件處理B

通過對(duì)前面幾種情況的分析腮鞍,相信大家能比較容易地了解事件的分發(fā)、攔截莹菱、處理事件的流程了移国。在后面的學(xué)習(xí)中,結(jié)合源碼道伟,會(huì)更加深入地理解迹缀,為什么流程會(huì)是這樣的?在學(xué)習(xí)的時(shí)候蜜徽,最好先對(duì)流程有一個(gè)大致的認(rèn)識(shí)之后祝懂,再去接觸源碼,這樣就不會(huì)一頭霧水拘鞋,摸不著頭腦砚蓬,從而喪失學(xué)習(xí)的興趣。

更多內(nèi)容戳這里(整理好的各種文集)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末盆色,一起剝皮案震驚了整個(gè)濱河市灰蛙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌傅事,老刑警劉巖缕允,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異蹭越,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)教届,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門响鹃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人案训,你說(shuō)我怎么就攤上這事买置。” “怎么了强霎?”我有些...
    開封第一講書人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵忿项,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng)轩触,這世上最難降的妖魔是什么寞酿? 我笑而不...
    開封第一講書人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮脱柱,結(jié)果婚禮上伐弹,老公的妹妹穿的比我還像新娘。我一直安慰自己榨为,他們只是感情好惨好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著随闺,像睡著了一般日川。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上矩乐,一...
    開封第一講書人閱讀 52,457評(píng)論 1 311
  • 那天逗鸣,我揣著相機(jī)與錄音,去河邊找鬼绰精。 笑死撒璧,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的笨使。 我是一名探鬼主播卿樱,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼硫椰!你這毒婦竟也來(lái)了繁调?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤靶草,失蹤者是張志新(化名)和其女友劉穎蹄胰,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體奕翔,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡裕寨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年层皱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了闸与。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡貌嫡,死狀恐怖驾窟,靈堂內(nèi)的尸體忽然破棺而出庆猫,到底是詐尸還是另有隱情,我是刑警寧澤绅络,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布月培,位于F島的核電站嘁字,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏杉畜。R本人自食惡果不足惜纪蜒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望寻行。 院中可真熱鬧霍掺,春花似錦、人聲如沸拌蜘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)简卧。三九已至兔魂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間举娩,已是汗流浹背析校。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留铜涉,地道東北人智玻。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像芙代,于是被迫代替她去往敵國(guó)和親吊奢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

推薦閱讀更多精彩內(nèi)容