簡潔的事件分發(fā)

說在前面的話

事件分發(fā)是一個重點也是難點,所以钩乍,本篇幅有點長辞州,如果耐心看完本篇,相信讀者會有收獲的寥粹。同時变过,讀者也可以自己寫例子測試,畢竟涝涤,紙上得來終覺淺媚狰,絕知此事要躬行。但是阔拳,對于水平高的讀者崭孤,其實最好的方式是看源代碼,因為一切原因都可以從源頭找到答案糊肠。

關(guān)于事件分發(fā)

關(guān)于事件分發(fā)辨宠,其實主要就是理解三個函數(shù),這三個函數(shù)分別是dispatchTouchEvent(MotionEvent ev)货裹,onInterceptTouchEvent(MotionEvent ev)以及onTouchEvent(MotionEvent ev)彭羹,這里直接上一張圖:

偽代碼-20161116
偽代碼-20161116

這張圖將分發(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ù)的作用是處理觸摸事件漆际,ViewGroupView都有這個方法,返回值的意義如下:
如果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();
                            }
                        }
                    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市外恕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌罪郊,老刑警劉巖建丧,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異翎朱,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)争舞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門澈灼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來店溢,“玉大人委乌,你說我怎么就攤上這事≡饷常” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵著蛙,是天一觀的道長耳贬。 經(jīng)常有香客問我,道長咒劲,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任慕的,我火速辦了婚禮挤渔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘判导。我一直安慰自己,他們只是感情好眼刃,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著仪际,像睡著了一般昵骤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上变秦,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機(jī)與錄音赎婚,去河邊找鬼。 笑死挣输,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的歧焦。 我是一名探鬼主播肚医,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼舰涌!你這毒婦竟也來了你稚?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤刁赖,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后鸡典,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體枪芒,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年纽甘,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片悍赢。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡货徙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出破婆,到底是詐尸還是另有隱情,我是刑警寧澤祷舀,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站抛丽,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏亿鲜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一饶套、第九天 我趴在偏房一處隱蔽的房頂上張望垒探。 院中可真熱鬧妓蛮,春花似錦圾叼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至呜笑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間叫胁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工微谓, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人豺型。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓买乃,卻偏偏與公主長得像,于是被迫代替她去往敵國和親剪验。 傳聞我的和親對象是個殘疾皇子前联,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容