標籤:
內容是博主照著書敲出來的,博主碼字挺辛苦的,轉載請註明出處,後序內容陸續會碼出。
當Android系統捕獲到使用者的各種輸入事件後,如何準確地傳遞給真正需要這個事件的控制項呢?Android給我們提供了一整套完善的事件傳遞、處理機制,來協助開發人員完成準確的事件分配與處理。
要瞭解觸摸事件的攔截機制,首先要瞭解什麼是觸摸事件?顧名思義,觸摸事件就是捕獲觸控螢幕幕後產生的事件。當點擊一個按鈕時,通常就會產生兩個或者三個事件——按鈕按下,這是事件一;如果不小心滑動一點,這就是事件二;當手抬起,這是事件三。Android為觸摸事件封裝了一個類——MotionEvent,如果重寫onTouchEvent()方法,那就會發現給方法的參數就是這樣一個MotionEvent。其實,只要是重寫觸摸相關的方法,參數一般都含有MotionEvent,可見它的重要性。
在MotionEvent裡面封裝了不少好東西,比如觸摸點的座標,可以通過event.getX()方法和event.getRawX()方法取出座標點;再比如獲得點擊的事件類型,可以通過不同的Action(如MotionEvent.ACTION_DOWN、MotionEvent.ACTION_MOVE)來進行區分,並實現不同的邏輯。
如此看來,觸摸事件還是比較簡單的,其實就是一個動作類型加座標而已。但是我們知道,Android的View結構是樹形結構,也就是說,View可以放在ViewGroup裡面,通過不同的組合來實現不同的樣式。那麼問題來了,View放在一個ViewGroup裡面,這個ViewGroup又放在另一個ViewGroup裡面,甚至還有可能繼續嵌套,一層層地疊起來。可我們的觸摸事件就一個,到底該分給誰呢?同一個事件,子View和父ViewGroup都有可能想要進行處理。因此,這就產生了“事件攔截”這個“霸氣”的稱呼。
當然,事件攔截可以很複雜,也可以很簡單。但是初學者卻經常“卡”在這裡不知道如何繼續進行,所以我們不想通過過多的原始碼讓大家不知所措。我們通過最直觀的Log資訊,讓大家先有一個大概的瞭解,知道事件攔截的本質,然後大家在結合原始碼學習時,就可以有方向、有目的性去理解了。
首先,請想象一下生活中非常常見的情境:假設你所在的公司,有一個總經理,層級最高;他下面有一個部長,層級次之;最低層,就是幹活的你,沒有層級。現在董事會交給總經理一項任務,總經理將這項任務布置給了部長,部長又把任務安排給了你。而當你好不容易幹完活了,你就把任務交給部長,部長覺得任務完成得不錯,於是就簽了他的名字交給總經理,總經理看了也覺得不錯,就業簽了名字交給董事會。這樣,一個任務就順利完成了。如果大家能非常清楚地理解這樣一個情境,那麼對於事件攔截機制,你就超過了40%的開發人員了。下面,我們再來超越剩下的開發人員。為了能過方便地瞭解整個事件的流程,我們設計了這樣一個執行個體,如所示。
一個總經理——MyViewGroupA,最外層的ViewGroup(紅色)。
一個部長——MyViewGroupB,中間的ViewGroup(綠色)。
一個幹活的你——MyView,在最底層(藍色)。
本執行個體的整個布局結構如所示。
代碼非常簡單,只是重寫了事件攔截和處理的幾個方法,並給它加上了一些Log而已。
對於ViewGroup來說,重寫了如下所示的三個方法。
@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) { Log.d("blankj", "ViewGroupA dispatchTouchEvent" + ev.getAction()); return super.dispatchTouchEvent(ev);}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) { Log.d("blankj", "ViewGroupA onInterceptTouchEvent" + ev.getAction()); return super.onInterceptTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) { Log.d("blankj", "ViewGroupA onTouchEvent" + event.getAction()); return super.onTouchEvent(event);}
而對於View來說,重寫了如下所示的兩個方法。
@Overridepublic boolean onTouchEvent(MotionEvent event) { Log.d("blankj", "View onTouchEvent" + event.getAction()); return super.onTouchEvent(event);}@Overridepublic 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
可以看見,正常情況下,時間的行程順序是:
總經理(MyViewGroupA)→部長(MyViewGroupB)→你(View)。事件傳遞的時候,先執行dispatchTouchEvent()方法,再執行onInterceptTouchEvent()方法。
事件的處理順序是:
你(View)→部長(MyViewGroupB)→總經理(MyViewGroupA)。事件處理都是執行onTouchEvent()方法。
事件傳遞的傳回值非常容易理解:true,攔截,不繼續;false,不攔截,繼續流程。
事件處理的傳回值也類似:true,處理了,不用審核了;false,給上級處理。
初始情況下,傳回值都是false。
這裡為了能夠方便大家理解事件攔截的過程,在事件傳遞中,我們只關心onInterceptTouchEvent(),而dispatchTouchEvent()方法雖然是事件分發的第一步,但一般情況下,我們不太會去改寫這個方法,所以暫時不管這個方法。可以把上面的整個事件程序整理成如所示的一張圖。
相信大家只要把MyView想成自己,就能充分理解事件分發、攔截、處理的整個流程了。
下面我們稍微改動一下,假設總經理(MyViewGroupA)發現這個任務太簡單了,覺得自己完成就可以了,完全沒必要再找下屬。因此時間就被總經理(MyViewGroupA)使用onInterceptTouchEvent()方法把事件給攔截了,即讓MyViewGroupA的onInterceptTouchEvent()方法返回true,我們再來看一下Log。
D/blankj: ViewGroupA dispatchTouchEvent0
D/blankj: ViewGroupA onInterceptTouchEvent0
D/blankj: ViewGroupA onTouchEvent0
跟我們設想的一樣,總經理(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)就不用幹活了。
那麼這兩種情況,也可以整理成類似如所示的圖。
總經理(MyViewGroupA)攔截事件,如所示。
部長(MyViewGroupB)攔截事件,如所示。
對事件的分發、攔截,現在大家應該比較清楚了,下面我們再看看事件的處理。先來看看底層人民——你(MyView)。最開始的時候講了,當你處理完任務後會向上級報告,需要上級的確認,所以你的事件處理返回false。那麼你突然有一天受不了老闆的壓迫了,罷工不幹了,那麼你的任務就沒人做了,也就不用報告上機了,所以就直接返回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
可以看見,事件傳遞跟以前一樣,但是事件處理,到你(MyView)這就結束了,因為你返回true,表示不用向上級彙報了。這時,我們同樣來整理下關係圖,如下所示。
你(MyView)終於翻身做了主,決定了自己的命運。但是,如果部長(MyViewGroupB)看到了你的報告,覺得太丟人,不敢給經理看,所以他就偷偷地返回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
他們之間的關係圖如所示。
通過對前面幾種情況的分析,相信大家能比較容易地瞭解事件的分發、攔截、處理事件的流程了。在後面的學習中,結合源碼,你會更加深入地理解,為什麼流程會是這樣的?初學者在學習的時候,最好先對流程有一個大致的認識之後,再去接觸源碼,這樣就不會一頭霧水,摸不著頭腦,從而喪失學習的興趣。
項目地址→EventIntercept
原文地址事件攔截機制分析(Android群英傳)
我的自媒體部落格blankj小站(OJ、LeetCode、Android開發),歡迎來逛逛。
事件攔截機制分析(Android群英傳)