內容是博主照著書敲出來的炒事,博主碼字挺辛苦的臀栈,轉載請注明出處,后序內容陸續(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)攔截事件,如下圖所示圆存。
部長(MyViewGroupB)攔截事件叼旋,如下圖所示。
對事件的分發(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,表示不用向上級匯報了碑隆。這時恭陡,我們同樣來整理下關系圖,如下所示上煤。
你(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
他們之間的關系圖如下圖所示组橄。
通過對前面幾種情況的分析,相信大家能比較容易地了解事件的分發(fā)愧薛、攔截晨炕、處理事件的流程了。在后面的學習中毫炉,結合源碼瓮栗,你會更加深入地理解,為什么流程會是這樣的瞄勾?初學者在學習的時候费奸,最好先對流程有一個大致的認識之后,再去接觸源碼进陡,這樣就不會一頭霧水愿阐,摸不著頭腦,從而喪失學習的興趣趾疚。
項目地址→EventIntercept
原文地址事件攔截機制分析(Android群英傳)
我的自媒體博客blankj小站(OJ缨历、LeetCode、Android開發(fā))糙麦,歡迎來逛逛辛孵。