說在前面的話
事件分發(fā)是一個重點也是難點,所以钩乍,本篇幅有點長辞州,如果耐心看完本篇,相信讀者會有收獲的寥粹。同時变过,讀者也可以自己寫例子測試,畢竟涝涤,紙上得來終覺淺媚狰,絕知此事要躬行。但是阔拳,對于水平高的讀者崭孤,其實最好的方式是看源代碼,因為一切原因都可以從源頭找到答案糊肠。
關(guān)于事件分發(fā)
關(guān)于事件分發(fā)辨宠,其實主要就是理解三個函數(shù),這三個函數(shù)分別是dispatchTouchEvent(MotionEvent ev)
货裹,onInterceptTouchEvent(MotionEvent ev)
以及onTouchEvent(MotionEvent ev)
彭羹,這里直接上一張圖:
這張圖將分發(fā)事件中的重要的三個函數(shù)之間的關(guān)系表達(dá)的比較清晰,這里再簡單的解釋一下上面的偽代碼:一個點擊事件產(chǎn)生后泪酱,會從外到內(nèi)傳遞派殷,傳遞到根ViewGroup后,會調(diào)用根ViewGroup的dispatchTouchEvent方法墓阀,然后如果自己的onInterceptTouchEvent方法返回true毡惜,表示攔截事件,那么這個事件就會自己處理斯撮,也就是調(diào)用自己的onTouchEvent方法经伙,如果返回false,就表示自己不攔截這個事件勿锅,事件就會傳遞給子元素帕膜,也就是調(diào)用子元素的dispatchTouchEvent方法,如此傳遞下去溢十,直到事件被處理垮刹。
知道了關(guān)系以后,我們還需要了解這三個函數(shù)张弛,了解一個函數(shù)荒典,其實無非就是理解它的作用酪劫,傳入的參數(shù)以及返回值。
先說dispatchTouchEvent(MotionEvent ev)
:
這個函數(shù)的作用是分發(fā)事件寺董,不管是ViewGroup還是View都有這個方法覆糟,它的返回值受本身的onInterceptTouchEvent(MotionEvent ev)
和child.dispatchTouchEvent(MotionEvent ev)
共同影響(從上面那張圖就可以看出來),返回的各個值的意義如下:
return true
:表示該View內(nèi)部消化掉了所有事件遮咖。
return false
:事件在本層不再繼續(xù)進(jìn)行分發(fā)滩字,這個false
也就是本身的dispatchTouchEvent(MotionEvent ev)
返回值,而這個返回值會回溯給上層控件的dispatchTouchEvent(MotionEvent ev)
御吞,表示自己沒有接受這個事件麦箍,不管上層控件是view
還是viewGroup
,都是交由上層控件的onTouchEvent(MotionEvent ev)
方法進(jìn)行消費(如果本層控件已經(jīng)是Activity魄藕,那么事件將被系統(tǒng)消費或處理)内列。
如果事件分發(fā)返回系統(tǒng)默認(rèn)的 super.dispatchTouchEvent(ev)
撵术,事件將分發(fā)給本層的事件攔截onInterceptTouchEvent(MotionEvent ev)
方法進(jìn)行處理背率,而不是super的onInterceptTouchEvent。因為return super.dispatchTouchEvent(ev)
會去運行父viewGroup的dispatchTouchEvent(ev)
嫩与,然后運行onInterceptTouchEvent寝姿,那么這個onInterceptTouchEvent是誰的呢?根據(jù)方法是基于對象的划滋,所以就會運行child的onInterceptTouchEvent(MotionEvent ev)
也就是本層的事件攔截器饵筑,而不是super的onInterceptTouchEvent。詳情可以參考規(guī)則中的第十條处坪。
然后是onInterceptTouchEvent(MotionEvent ev)
:
這個函數(shù)的作用是攔截事件根资,只有ViewGroup
有這個方法,返回的各個值的意義如下:
return true
:表示將事件進(jìn)行攔截同窘,并將攔截到的事件交由本層控件 的 onTouchEvent 進(jìn)行處理玄帕;
return false
:則表示不對事件進(jìn)行攔截,事件得以成功分發(fā)到子View想邦。并由子View的dispatchTouchEvent進(jìn)行處理裤纹。
如果返回super.onInterceptTouchEvent(ev)
丧没,默認(rèn)false鹰椒,即表示不攔截該事件,這樣事件才能以分發(fā)下去呕童。
最后是onTouchEvent(MotionEvent ev)
:
這個函數(shù)的作用是處理觸摸事件漆际,ViewGroup
和View
都有這個方法,返回值的意義如下:
如果return true夺饲,表示onTouchEvent處理完事件后消費了此次事件灿椅。此時事件終結(jié)套蒂;
如果return fasle,則表示不響應(yīng)事件茫蛹,如果是ACTION_DOWN事件操刀,那么該事件將會不斷向上層View的onTouchEvent方法傳遞,直到某個View的onTouchEvent方法返回true婴洼,如果到了最頂層View還是返回false,那么事件就會交給Activity處理柬采。且在同一個事件系列中,當(dāng)前View無法再次接收到該事件序列粉捻,如果不是ACTION_DOWN事件,那么不會返回給父view的onTouchEvent處理肩刃,而是給Activity處理祟霍,并且該view可以繼續(xù)接收該事件序列;
如果return super.onTouchEvent(event);盈包,默認(rèn)是true,即表示處理事件崭添。那這個和return true有什么區(qū)別呢?從代碼就可以看出來叛氨,return super.onTouchEvent(event)會執(zhí)行super.onTouchEvent(event)這個方法呼渣。比如,當(dāng)你繼承EditText后寞埠,重寫onTouchEvent(MotionEvent event)方法,如果你將return super.onTouchEvent(event);換成return true畸裳,就會發(fā)現(xiàn)當(dāng)你按返回取消輸入框,再次點擊自定義EditText時就會無法彈出輸入框帅容,解決辦法可以是將return true修改成return super.onTouchEvent(event)伍伤,或者是在之前調(diào)用一次super.onTouchEvent(event)方法并徘,彈出輸入框是在action為ACTION_UP的時候彈出的扰魂。
重要的知識點(大家拿本子記一下蕴茴,高考必考啊)
1.一個viewGroup一旦決定攔截事件(這里分兩種情況姐直,一個是攔截了ACTION_DOWN事件,還有一個是沒有子View滿足分發(fā)事件的條件或者子view在ACTION_DOWN時返回了false)声畏,那么后面的事件序列都會交給它處理,并且不會再調(diào)用onInterceptTouchEvent(ev)
方法插龄, 當(dāng)ACTION_DOWN事件成功傳入子view的時候, 那么父ViewGroup在別的事件分發(fā)的時候糠雨,比如ACTION_MOVE,每次都會調(diào)用onInterceptTouchEvent來判斷是否攔截當(dāng)前事件甘邀。 也就是說真椿,父ViewGroup的onInterceptTouchEvent不會再次調(diào)用的時機(jī)只是自己來處理這個事件乎澄,也就是自己的onTouchEvent被調(diào)用,只有這個時候才不會再次調(diào)用onInterceptTouchEvent置济,當(dāng)事件傳入子view來處理事件的時候,父ViewGroup都會每次都調(diào)用onInterceptTouchEvent來決定是否攔截當(dāng)前事件浙于。
2.dispatchTouchEvent無論返回true還是false,事件都不再進(jìn)行分發(fā)腐宋,只有當(dāng)其返回super.dispatchTouchEvent(ev),才表明其具有向下層分發(fā)的愿望胸竞,但是是否能夠分發(fā)成功躏精,則需要經(jīng)過事件攔截onInterceptTouchEvent的審核。事件是否向上傳遞處理是由onTouchEvent的返回值決定的讹挎。
3.正常情況下吆玖,一個事件序列只能被一個view攔截且消耗马篮,因為沾乘,一旦決定攔截事件浑测,那么這個事件只能被這個view消耗,并且它的onInterceptTouchEvent(ev)方法也不會再次調(diào)用(這里的攔截和規(guī)則一中的攔截是一樣的。這里的再次調(diào)用是指當(dāng)確定攔截事件后尽爆,除了在ACTION_DOWN時調(diào)用onInterceptTouchEvent(ev),后面都不調(diào)用槐雾,其實跟規(guī)則一中說的一樣)幅狮,如果你想這個事件序列被多個view攔截消耗募强,那么你可以在攔截事件的那個view中的onTouchEvent()方法中調(diào)用你想讓其攔截事件的那個view的onTouchEvent()方法來實現(xiàn)崇摄。
4.view一旦用onTouchEvent()開始處理事件,如果沒有處理ATION_DOWN事件逐抑,那么同一個事件序列中的事件也不會交給他處理,會回溯給他的父控件进每,如果你處理了ACTION_DOWN但是沒有處理ACTION_MOVE或者ACTION_UP命斧,那么這個事件還是被你消耗田晚,不會調(diào)用父控件的onTouchEvent方法国葬,最后會是Activity處理,后面的事件還是繼續(xù)交給你處理接奈。其實,這就類似現(xiàn)實鲫趁,如果別人第一次叫你做事利虫,你沒做好堡僻,那么后面就都不會放心叫你做了,如果你第一次做好了钉疫,后面沒做好巢价,別人還是會給你做的,所以壤躲,第一次很重要。
5.view的onTouchEvent默認(rèn)都是消費事件的(返回true)碉克,除非是不可點擊的,也就是longClickable和clickable都為false客税,只有這個屬性會影響view的onTouchEvent的返回值,別的屬性不會更耻,比如捏膨,Enabled屬性,就算是Enabled屬性為false脊奋,也就是disable狀態(tài)疙描,view的onTouchEvent默認(rèn)返回的還是true。
6.事件傳遞是由外向內(nèi)的起胰,即事件總是傳遞給父元素,再由父元素分發(fā)給子控件地消,通過requestDisallowInterceptTouchEvent();方法可以在子元素中干預(yù)父元素的事件分發(fā)過程,但是不能干預(yù)ACTION_DOWN事件脉执,因為當(dāng)時ACTION_DOWN事件的時候戒劫,父元素會重置FLAG_DISALLOW_INTERCEPT標(biāo)志位婆廊。
7.使用內(nèi)部攔截法的時候,為了弄清楚順序巫橄,我就直接調(diào)試,結(jié)果湘换,運行到父元素的dispatchTouchEvent后,不會去調(diào)用父元素的onInterceptTouchEvent方法筹我,直接就到了子元素的dispatchTouchEvent,依然會運行到子view的onTouchEvent崎溃,等到ACTION_UP的時候才會又跑到父元素中的dispatchTouchEvent和onInterceptTouchEvent去判斷是否攔截ACTION_UP事件盯质。我倒騰了一天,才發(fā)現(xiàn)是需要移動呼巷,也就是讓move多次調(diào)用才行,因為事件是由外向內(nèi)的王悍,當(dāng)?shù)谝淮蜛CTION_MOVE事件到的時候破镰,先運行父ViewGroup的dispatchTouchEvent方法压储,此時FLAG_DISALLOW_INTERCEPT依然是設(shè)置成true,所以孕似,不會運行父ViewGroup的onInterceptTouchEvent方法,直接就會運行子view的dispatchTouchEvent方法喉祭,然后FLAG_DISALLOW_INTERCEPT被設(shè)置成false雷绢,于是當(dāng)?shù)诙蔚腁CTION_MOVE到來的的時候,才會去運行父viewGroup的onInterceptTouchEvent方法翘紊,然后子view收到ACTION_CANCEL事件,等到第三個ACTION_MOVE的時候父viewGroup才開始攔截事件。但是因為我之前是調(diào)試中捆,所以都只有一次move事件坊饶,結(jié)果就不一樣了。也是醉了匿级。并且使用內(nèi)部攔截法的時候,ACTION_UP事件也會被父view攔截痘绎,不會傳遞到子view中,也就意味著子view的onClick事件不會響應(yīng)尔苦,這一點要記住行施。
8.內(nèi)部攔截法和外部攔截法的區(qū)別:內(nèi)部攔截法需要到該事件的第三個的時候才有用,也就是該事件的第一個依然被子view得到蛾号,外部攔截法則是到第二個就有用了,子view不會得到該事件的任何一個鲜结,比如,攔截ACTION_MOVE的時候拗胜,使用內(nèi)部攔截法在攔截第三個ACTION_MOVE的時候才攔截了怒允,因為第一個ACTION_MOVE會被子view得到埂软,而使用外部攔截法則是第二個ACTION_MOVE的時候就攔截了误算,因為子view不會得到ACTION_MOVE中的任何一個。詳情可以見9,10庆寺。所以,使用外部攔截法要好點懦尝。
9.當(dāng)viewGroup沒有攔截ACTION_DOWN而攔截了ACTION_MOVE或者ACTION_UP的時候壤圃,那么琅轧,第一個被攔截的動作不會在viewGroup中的onTouchEvent中觸發(fā),也不會在子view的onTouchEvent中觸發(fā)冲杀,而是子view會受到ACTION_CANCEL事件。該事件序列后面的事件都會被攔截权谁,并且下一個同類型的事件傳來時憋沿,不會再調(diào)用viewGroup的onInterceptTouchEvent方法,直接就調(diào)用viewGroup的onTouchEvent方法辐啄,這里解釋一下,什么是第一個被攔截的動作共缕,比如,多個move的時候图谷,第一個move就不會被父view或者子view執(zhí)行阱洪,感覺是這個事件變成了ACTION_CANCEL事件傳遞到了子view。也就是說冗荸,一旦在這種情況下,ACTION_UP事件永遠(yuǎn)不會被子view接收蚌本。也就意味著,不管是使用外部攔截法還是內(nèi)部攔截法舷嗡,只要攔截了嵌莉,那么子view就收不到ACTION_UP事件。還有就是ViewGroup就不要攔截ACTION_UP了,因為這樣大家都得不到ACTION_UP事件中鼠,何必呢?
10.為了講述方便矛渴,當(dāng)從一個ViewGroup分發(fā)事件到子ViewGroup時惫搏,在子ViewGroup的dispatchTouchEvent方法中調(diào)用父類的dispatchTouchEvent,發(fā)現(xiàn)不會繼續(xù)調(diào)用父類的onInterceprTouchEvent晶府,而是直接調(diào)用子ViewGroup的onInterceptTouchEvent,為什么在這里調(diào)用父類的dispatchTouchEvent不會跟著調(diào)用父類的onInterceptTouchEvent剂习?我調(diào)試和看源碼發(fā)現(xiàn)當(dāng)運行到findChildWithAccessibilityFocus()方法時较沪,view會變成接受到事件的view鳞绕,然后就不知道了尸曼。水平還是看不懂源代碼。其實這是因為我對java的理解有錯誤冤竹,基于方法都是基于對象的茬射,所以在子viewGroup中調(diào)用父類的dispatchTouchEvent鹦蠕,也就是super.dispatchTouchEvent()時在抛,這時會運行到父viewGroup的dispatchTouchEvent里,會調(diào)用onInterceptTouchEvent方法肠阱,這時的onInterceptTouchEvent其實就已經(jīng)是子viewGroup的onInterceptTouchEvent方法朴读,而不是父ViewGroup的dispatchTouchEvent方法,因為方法是基于對象的磨德。
11.onClick發(fā)生的前提就是可點擊吆视,并且收到了ACTION_DOWN和ACTION_UP事件酥宴。這里解釋一下您觉,這里的收到了用詞不是那么準(zhǔn)確,應(yīng)該是能接收到事件琳水,并且return super.onTouchEvent()了,記住是return super.onTouchEvent()诚啃,如果是return true都不行私沮,因為return true沒有執(zhí)行view的onTouchEvent方法始赎,而點擊事件是在ACTION_UP中設(shè)置的仔燕。即在ACTION_UP的時候源碼中調(diào)用了performClick()
方法。這里貼一部分源代碼
TextView的源代碼:
final boolean superResult = super.onTouchEvent(event);
View的源代碼:
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}