事件傳遞雖然算不上某個單獨的知識點,但是在實際項目開發中肯定會碰到,如果不明白其中的原理,那在設計各種滑動效果時就會感到很困惑。
關于事件的傳遞,我們可能會有以下疑問:
事件是如何傳遞的 事件是如何處理的 自定義view的時候,事件也沖突了怎麼解決
帶着這三個疑問,我們來總結一下事件傳遞機制是怎麼回事。
一、事件分發的原理:
1、事件是如何傳遞的:
(1)首先由activity分發,分發給根view,也就是decorview(decorview為整個window界面的最頂層view) (2)然後由根view分發到子的view
如下圖所示:
再來看下面這張圖:(這張圖是整個事件傳遞機制的核心)
上圖顯示:
在viewgroup中可以通過onintercepttouchevent方法對事件傳遞進行攔截。onintercepttouchevent方法:
傳回true代表不允許事件繼續向子view傳遞,将會觸發目前view的ontouchevent(),進行事件的消費;
傳回false代表不對事件進行攔截,事件可以傳遞給孩子
預設傳回false
2、事件是如何處理的:
再來看下面這張圖:
上圖顯示:子view中如果将傳遞的事件消費掉,父類的viewgroup中将無法接收到任何事件。
二、ontouch和onclick事件同時發生的問題:
首先這裡要解釋一下各種概念,避免混淆。
1、各種概念:
事件:
混合體(可能是點選事件也可能是觸摸事件)。
觸摸事件:
按下、滑動和離開
點選事件:
按下、停留一會兒和離開
觸摸ontouch事件和點選onclick事件有什麼關系?
(1)執行先後不一樣。觸摸事件先執行 (2)觸摸事件傳回值影響點選事件(前者影響後者,而後者不影響前者)
2、ontouch和onclick事件同時執行:
如果按鈕的ontouch和onclick方法同時執行,會有什麼效果呢?我們通過代碼來看一下:
上方代碼中,按鈕btn既包含了ontouch事件,也包含了onclick事件,現在運作程式,點選按鈕,背景列印的日志如下:
通過上方日志我們可以看到,ontouch事件是比onclick事件先執行的。
備注:這裡提示一下,如果我們僅僅隻是用手指點選按鈕,然後馬上松開,ontouch事件中隻會執行action_down和action_up動作;如果用手機點選按鈕,并且手指還在按鈕上滑動了一會兒,那麼滑動的過程中,action_move動作就會不停的執行。現在我們應該能明白這三個動作的含義了吧?
3、隻執行ontouch事件,不執行onclick事件:
如果按鈕的ontouch和onclick方法同時執行,在有些情況下不太滿足産品的需求。那如果隻想執行ontouch事件,不執行onclick事件,該怎麼做呢?很簡單,隻需要在上方代碼中,将第39行的代碼改為return true,就行了,即:将ontouch方法的傳回值改為true,就會隻執行ontouch事件,不執行onclick事件。改完代碼之後,背景的運作效果如下:
為什麼這樣改代碼就可以了呢?這就需要從源碼的角度來了解了。
button按鈕中沒有dispatchtouchevent方法,需要去它的父類view.java中去找dispatchtouchevent方法。源碼如下所示:
上圖分析:紅框部分的ontouch()方法預設是傳回false,是以就會執行藍框部分的代碼,即:調用ontouchevent()方法。而ontouchevent方法中,會在action_up動作裡面會去初始化onclick事件。如下圖所示:
于是onclick事件就得到了執行。
三、onclick和onlongclick事件能同時發生:
我們通過代碼來示範一下。
1、ontouch事件、onlongclick事件、onclick事件預設是同時執行:(執行的先後順序:ontouch > onlongclick > onclick)
完整版代碼如下:
運作程式後,長按按鈕,背景日志如下:
源碼比較長,就不貼出來了,通過檢視源碼我們得知,當ontouch事件中的action_down動作執行180ms之後,就會執行onlongclick事件。
那我們現在知道了,如果在一個按鈕上按下的時間過長,onlongclick事件會比onclick事件先執行。
2、隻執行ontouch事件和onlongclick事件,不執行onclick事件:
為了實作這種邏輯,也很簡單,隻需要将上方的第50行代碼改為return true就行了,即:将onlongclick方法的傳回值改為true,就不會執行onclick事件了。改完代碼之後,背景的運作效果如下:
為什麼這樣改代碼就可以了呢?這就需要從源碼的角度來了解了。在view.java中的dispatchtouchevent方法裡,action_up動作裡面對onlongtouch事件進行了處理,具體源碼就不展示出來了,這個有點複雜。
四、事件傳遞機制調用順序:
viewgroup的事件傳遞方法:
dispatchtouchevent
onintercepttouchevent
ontouchevent
view的事件傳遞方法:
view的dispatchtouchevent
view的ontouchevent
注意,隻有父的viewgroup容器才有onintercepttouchevent方法。這也很好了解,最小的那個子的view沒必要再攔截了,因為無法繼續向下傳遞事件,是否攔截已經沒有意義了。
接下來,我們用linearlayout代表viewgroup,用button代表子view,然後去重寫linearlayout和button中的事件傳遞方法,看一下各個方法的調用順序。代碼如下:
(1)mylinearlayout.java:(重寫linearlayout中的事件傳遞方法)
(2)mybutton.java:(重寫button中的事件傳遞方法,注意:這裡面沒有onintercepttouchevent方法)
上方代碼中,将ontouchevent方法的傳回值修改為true(59行),表示這個子的view希望消費這個事件。
(3)activity_main.xml:
<code>上面的xml中,将我們自定義的mylinearlayout和mybutton用上了。</code>
(4)mainactivity.java:
分析之前,我們先記住下面這句話:(記住這句話,分析下面的日志就好了解了)
在android中,一切事件處理的開始都是從down事件開始的,如何你處理了down事件,其他的事件就都收不到了。
1、按照上面的代碼,背景日志如下:
通過上圖的箭頭處可以看到,事件是傳遞給了子view去消費。
2、上面的代碼中,将mylinearlayout.java中ontouchevent方法傳回值修改為true,将mybutton.java中的ontouchevent方法傳回值修改為false,背景日志如下:
通過上圖的箭頭處可以看到,事件是傳遞給了子view,子view說它不消費了,于是又回傳給父的viewgroup,viewgroup消費了這個事件。
3、上面的代碼中,将mylinearlayout.java中ontouchevent方法傳回值修改為true,将mylinearlayout.java中onintercepttouchevent方法傳回值修改為true,背景日志如下:
通過上圖的箭頭處可以看到,此時viewgroup已經将事件攔截了,是以根本就不會傳遞給子的veiw,父的viewgroup自己把事件給消費掉了。