事件攔截機制分析(Android群英傳)

內容是博主照著書敲出來的炒事,博主碼字挺辛苦的臀栈,轉載請注明出處,后序內容陸續(xù)會碼出挠乳。

當Android系統(tǒng)捕獲到用戶的各種輸入事件后权薯,如何準確地傳遞給真正需要這個事件的控件呢?Android給我們提供了一整套完善的事件傳遞睡扬、處理機制盟蚣,來幫助開發(fā)者完成準確的事件分配與處理。
  要了解觸摸事件的攔截機制卖怜,首先要了解什么是觸摸事件屎开?顧名思義,觸摸事件就是捕獲觸摸屏幕后產(chǎn)生的事件韧涨。當點擊一個按鈕時牍戚,通常就會產(chǎn)生兩個或者三個事件——按鈕按下侮繁,這是事件一;如果不小心滑動一點如孝,這就是事件二宪哩;當手抬起,這是事件三第晰。Android為觸摸事件封裝了一個類——MotionEvent锁孟,如果重寫onTouchEvent()方法,那就會發(fā)現(xiàn)給方法的參數(shù)就是這樣一個MotionEvent茁瘦。其實品抽,只要是重寫觸摸相關的方法,參數(shù)一般都含有MotionEvent甜熔,可見它的重要性圆恤。
  在MotionEvent里面封裝了不少好東西,比如觸摸點的坐標腔稀,可以通過event.getX()方法和event.getRawX()方法取出坐標點盆昙;再比如獲得點擊的事件類型,可以通過不同的Action(如MotionEvent.ACTION_DOWN焊虏、MotionEvent.ACTION_MOVE)來進行區(qū)分淡喜,并實現(xiàn)不同的邏輯。
  如此看來诵闭,觸摸事件還是比較簡單的炼团,其實就是一個動作類型加坐標而已。但是我們知道疏尿,Android的View結構是樹形結構瘟芝,也就是說,View可以放在ViewGroup里面润歉,通過不同的組合來實現(xiàn)不同的樣式模狭。那么問題來了,View放在一個ViewGroup里面踩衩,這個ViewGroup又放在另一個ViewGroup里面嚼鹉,甚至還有可能繼續(xù)嵌套,一層層地疊起來驱富∶啵可我們的觸摸事件就一個,到底該分給誰呢褐鸥?同一個事件线脚,子View和父ViewGroup都有可能想要進行處理。因此,這就產(chǎn)生了“事件攔截”這個“霸氣”的稱呼浑侥。
  當然姊舵,事件攔截可以很復雜,也可以很簡單寓落。但是初學者卻經(jīng)忱ǘ。“卡”在這里不知道如何繼續(xù)進行,所以我們不想通過過多的源代碼讓大家不知所措伶选。我們通過最直觀的Log信息史飞,讓大家先有一個大概的了解,知道事件攔截的本質仰税,然后大家在結合源代碼學習時构资,就可以有方向、有目的性去理解了陨簇。
  首先吐绵,請想象一下生活中非常常見的場景:假設你所在的公司,有一個總經(jīng)理塞帐,級別最高拦赠;他下面有一個部長巍沙,級別次之葵姥;最低層,就是干活的你句携,沒有級別±菩遥現(xiàn)在董事會交給總經(jīng)理一項任務,總經(jīng)理將這項任務布置給了部長矮嫉,部長又把任務安排給了你削咆。而當你好不容易干完活了,你就把任務交給部長蠢笋,部長覺得任務完成得不錯拨齐,于是就簽了他的名字交給總經(jīng)理,總經(jīng)理看了也覺得不錯昨寞,就業(yè)簽了名字交給董事會瞻惋。這樣,一個任務就順利完成了援岩。如果大家能非常清楚地理解這樣一個場景歼狼,那么對于事件攔截機制,你就超過了40%的開發(fā)者了享怀。下面羽峰,我們再來超越剩下的開發(fā)者。為了能過方便地了解整個事件的流程,我們設計了這樣一個實例梅屉,如下圖所示值纱。

事件攔截實例

  一個總經(jīng)理——MyViewGroupA,最外層的ViewGroup(紅色)坯汤。
  一個部長——MyViewGroupB计雌,中間的ViewGroup(綠色)。
  一個干活的你——MyView玫霎,在最底層(藍色)凿滤。
  本實例的整個布局結構如下圖所示。

實例布局結構

代碼非常簡單庶近,只是重寫了事件攔截和處理的幾個方法翁脆,并給它加上了一些Log而已。
  對于ViewGroup來說鼻种,重寫了如下所示的三個方法反番。

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

而對于View來說,重寫了如下所示的兩個方法叉钥。

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

從上面的代碼中可以看到罢缸,ViewGroup級別比較高,比View多了一個方法——onInterceptTouchEvent()投队。這個方法看名字就能猜到是事件攔截的核心方法枫疆。我們先不修改任何返回值,只是點擊一下View敷鸦,然后看Log會怎樣記錄我們的操作和程序響應息楔。點擊View后的Log如下所示。

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

可以看見扒披,正常情況下值依,時間的傳遞順序是:
  總經(jīng)理(MyViewGroupA)→部長(MyViewGroupB)→你(View)。事件傳遞的時候碟案,先執(zhí)行dispatchTouchEvent()方法愿险,再執(zhí)行onInterceptTouchEvent()方法。
  事件的處理順序是:
  你(View)→部長(MyViewGroupB)→總經(jīng)理(MyViewGroupA)价说。事件處理都是執(zhí)行onTouchEvent()方法辆亏。
  事件傳遞的返回值非常容易理解:true,攔截熔任,不繼續(xù)褒链;false,不攔截疑苔,繼續(xù)流程甫匹。
  事件處理的返回值也類似:true,處理了,不用審核了兵迅;false抢韭,給上級處理。
  初始情況下恍箭,返回值都是false刻恭。
  這里為了能夠方便大家理解事件攔截的過程,在事件傳遞中扯夭,我們只關心onInterceptTouchEvent()鳍贾,而dispatchTouchEvent()方法雖然是事件分發(fā)的第一步,但一般情況下交洗,我們不太會去改寫這個方法骑科,所以暫時不管這個方法」谷可以把上面的整個事件過程整理成如下圖所示的一張圖咆爽。

事件處理過程

相信大家只要把MyView想成自己,就能充分理解事件分發(fā)置森、攔截斗埂、處理的整個流程了。
  下面我們稍微改動一下凫海,假設總經(jīng)理(MyViewGroupA)發(fā)現(xiàn)這個任務太簡單了呛凶,覺得自己完成就可以了,完全沒必要再找下屬盐碱。因此時間就被總經(jīng)理(MyViewGroupA)使用onInterceptTouchEvent()方法把事件給攔截了把兔,即讓MyViewGroupA的onInterceptTouchEvent()方法返回true,我們再來看一下Log瓮顽。

D/blankj: ViewGroupA dispatchTouchEvent0
D/blankj: ViewGroupA onInterceptTouchEvent0
D/blankj: ViewGroupA onTouchEvent0

跟我們設想的一樣,總經(jīng)理(MyViewGroupA)把所有事情都干了围橡,沒后面人的事了暖混。同理,我們讓部長(MyViewGroupB)也來當一次好人翁授,即讓部長(MyViewGroupB)使用onInterceptTouchEvent()方法返回true拣播,把事件攔截下來咆槽,Log就會使以下這樣标沪。

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

可以看到,這次部長(MyViewGroupB)當了好人耍铜,你(MyView)就不用干活了塞赂。
  那么這兩種情況泪勒,也可以整理成類似如上圖所示的圖。
  總經(jīng)理(MyViewGroupA)攔截事件,如下圖所示圆存。

事件攔截A

部長(MyViewGroupB)攔截事件叼旋,如下圖所示。

事件攔截B

對事件的分發(fā)沦辙、攔截夫植,現(xiàn)在大家應該比較清楚了,下面我們再看看事件的處理油讯。先來看看底層人民——你(MyView)详民。最開始的時候講了,當你處理完任務后會向上級報告陌兑,需要上級的確認阐斜,所以你的事件處理返回false。那么你突然有一天受不了老板的壓迫了诀紊,罷工不干了谒出,那么你的任務就沒人做了,也就不用報告上機了邻奠,所以就直接返回true◇栽現(xiàn)在再來看看Log,如下所示碌宴。

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

可以看見杀狡,事件傳遞跟以前一樣,但是事件處理贰镣,到你(MyView)這就結束了呜象,因為你返回true,表示不用向上級匯報了碑隆。這時恭陡,我們同樣來整理下關系圖,如下所示上煤。

事件處理A

你(MyView)終于翻身做了主休玩,決定了自己的命運。但是劫狠,如果部長(MyViewGroupB)看到了你的報告拴疤,覺得太丟人,不敢給經(jīng)理看独泞,所以他就偷偷地返回true呐矾,整個事件也就到此為止了,即部長(MyViewGroupB)將自己的onTouchEvent返回true懦砂,Log如下所示蜒犯。

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

他們之間的關系圖如下圖所示组橄。

事件處理B

通過對前面幾種情況的分析,相信大家能比較容易地了解事件的分發(fā)愧薛、攔截晨炕、處理事件的流程了。在后面的學習中毫炉,結合源碼瓮栗,你會更加深入地理解,為什么流程會是這樣的瞄勾?初學者在學習的時候费奸,最好先對流程有一個大致的認識之后,再去接觸源碼进陡,這樣就不會一頭霧水愿阐,摸不著頭腦,從而喪失學習的興趣趾疚。
項目地址→EventIntercept


原文地址事件攔截機制分析(Android群英傳)
我的自媒體博客blankj小站(OJ缨历、LeetCode、Android開發(fā))糙麦,歡迎來逛逛辛孵。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市赡磅,隨后出現(xiàn)的幾起案子魄缚,更是在濱河造成了極大的恐慌,老刑警劉巖焚廊,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件冶匹,死亡現(xiàn)場離奇詭異,居然都是意外死亡咆瘟,警方通過查閱死者的電腦和手機嚼隘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來搞疗,“玉大人嗓蘑,你說我怎么就攤上這事∧淠耍” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵豌汇,是天一觀的道長幢炸。 經(jīng)常有香客問我,道長拒贱,這世上最難降的妖魔是什么宛徊? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任佛嬉,我火速辦了婚禮,結果婚禮上闸天,老公的妹妹穿的比我還像新娘暖呕。我一直安慰自己,他們只是感情好苞氮,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布湾揽。 她就那樣靜靜地躺著,像睡著了一般笼吟。 火紅的嫁衣襯著肌膚如雪库物。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天贷帮,我揣著相機與錄音戚揭,去河邊找鬼。 笑死撵枢,一個胖子當著我的面吹牛民晒,可吹牛的內容都是我干的。 我是一名探鬼主播锄禽,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼潜必,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了沟绪?” 一聲冷哼從身側響起刮便,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎绽慈,沒想到半個月后恨旱,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡坝疼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年搜贤,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钝凶。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡仪芒,死狀恐怖,靈堂內的尸體忽然破棺而出耕陷,到底是詐尸還是另有隱情掂名,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布哟沫,位于F島的核電站饺蔑,受9級特大地震影響,放射性物質發(fā)生泄漏嗜诀。R本人自食惡果不足惜猾警,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一孔祸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧发皿,春花似錦崔慧、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至封救,卻和暖如春拇涤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背誉结。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工鹅士, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人惩坑。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓掉盅,卻偏偏與公主長得像,于是被迫代替她去往敵國和親以舒。 傳聞我的和親對象是個殘疾皇子趾痘,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內容