Android事件分發(fā)機制三:事件分發(fā)工作流程

前言

很高興遇見你~

本文是事件分發(fā)系列的第三篇纤掸。

在前兩篇文章中,Android事件分發(fā)機制一:事件是如何到達activity的辈赋? 分析了事件分發(fā)的真正起點:viewRootImpl矫付,Activity只是其中的一個環(huán)節(jié);Android事件分發(fā)機制二:viewGroup與view對事件的處理 源碼解析了viewGroup和view是如何分發(fā)事件的莱革。

事件分發(fā)的核心內(nèi)容,則為viewGroup和view對事件的分發(fā)讹开,也就是第二篇文章盅视。第二篇文章對源碼的分析較為深入,缺乏一個更高的角度來審視事件分發(fā)流程旦万。本文在前面的分析基礎(chǔ)上闹击,對整個事件分發(fā)的工作流程進行一個總結(jié),更好地把握事件是如何在不同的對象和方法之間進行傳遞成艘。

回顧

先來回顧一下整體的流程赏半,以便更好地定位我們的知識。

image
  1. 觸摸信息從手機觸摸屏幕時產(chǎn)生淆两,通過IMS和WMS發(fā)送到viewRootImpl
  2. viewRootImpl通過調(diào)用view的dispatchPointerEvent方法把觸摸信息傳遞給view
  3. view通過調(diào)用自身的dispatchTouchEvent方法開始了事件分發(fā)

圖中的view指的是一個控件樹断箫,他可以是一個viewGroup也可以是一個簡單的view。因為viewGroup是繼承自view琼腔,所以一個控件樹瑰枫,也可以看做是一個view。

我們今天探討的工作流程丹莲,就是從圖中的view調(diào)用自身的dispatchTouchEvent開始光坝。

主要對象與方法

事件分發(fā)的對象

這一部分內(nèi)容在第二篇有詳細解析,這里做個簡單的回顧甥材。

當我們手機觸碰屏幕時會產(chǎn)生一系列的MotionEvent對象盯另,根據(jù)觸摸的情況不同,這些對象的類型也會不同洲赵。具體如下:

  • ACTION_DOWN: 表示手指按下屏幕
  • ACTION_MOVE: 手指在屏幕上滑動時鸳惯,會產(chǎn)生一系列的MOVE事件
  • ACTION_UP: 手指抬起,離開屏幕叠萍、
  • ACTION_CANCEL:當出現(xiàn)異常情況事件序列被中斷芝发,會產(chǎn)生該類型事件
  • ACTION_POINTER_DOWN: 當已經(jīng)有一個手指按下的情況下,另一個手指按下會產(chǎn)生該事件
  • ACTION_POINTER_UP: 多個手指同時按下的情況下苛谷,抬起其中一個手指會產(chǎn)生該事件

事件分發(fā)的方法

事件分發(fā)屬于控件系統(tǒng)的一部分辅鲸,主要的分發(fā)對象是viewGroup與view。而其中核心的方法有三個: dispatchTouchEvent 腹殿、onInterceptTouchEvent 独悴、 onTouchEvent 。那么在講分發(fā)流程之前锣尉,先來介紹一下這三個方法刻炒。這三個方法屬于view體系的類,其中Window.CallBack接口中包含了 dispatchTouchEventonTouchEvent 方法自沧,Activity和Dialog都實現(xiàn)了Window.CallBack接口坟奥,因此都實現(xiàn)了該方法。因這三個方法經(jīng)常在自定義view中被重寫拇厢,以下的分析筏勒,如果沒有特殊說明都是在默認方法實現(xiàn)的情況下。

dispatchTouchEvent

該方法是事件分發(fā)的核心方法旺嬉,事件分發(fā)的邏輯都是在這個方法中實現(xiàn)管行。該方法存在于類View中,子類ViewGroup邪媳、以及其他的實現(xiàn)類如DecorView都重寫了該方法捐顷。

無論是在viewGroup還是view,該方法的主要作用都是處理事件雨效。如果成功處理則返回true迅涮,處理失敗則返回false,表示事件沒有被處理徽龟。具體到類叮姑,在viewGroup相關(guān)類中,該方法的主要作用是把事件分發(fā)到該viewGroup所擁有的子view,如果子view沒有處理則自己處理传透;在view的相關(guān)類中耘沼,該方法的主要作用是消費觸摸事件。

onInterceptTouchEvent

該方法只存在于viewGroup中朱盐,當一個事件需要被分發(fā)到子view時群嗤,viewGroup會調(diào)用此方法檢查是否要進行攔截。如果攔截則自己處理兵琳,而如果不攔截才會調(diào)用子view的 dispatchTouchEvent 方法分發(fā)事件狂秘。

方法返回true表示攔截事件,返回false表示不攔截躯肌。

這個方法默認只對鼠標的相關(guān)操作的一種特殊情況進行了攔截者春,其他的情況需要具體的實現(xiàn)類去重寫攔截。

onTouchEvent

該方法是消費事件的主要方法清女,存在于view中钱烟,viewGroup默認并沒有重寫該方法。方法返回true表示消費事件校仑,返回false表示不消費事件忠售。

viewGroup分發(fā)事件時,如果沒有一個子view消費事件迄沫,那么會調(diào)用自身的onTouchEvent方法來處理事件稻扬。View的dispatchTouchEvent方法中,并不是直接調(diào)用onTouchEvent方法來消費事件羊瘩,而是先調(diào)用onTouchListener判斷是否消費泰佳;如果onTouchListener沒有消費事件,才會調(diào)用onTouchEvent來處理事件尘吗。

我們?yōu)関iew設置的onClickListener與onLongClickListener都是在View的dispatchTouchEvent方法中逝她,根據(jù)具體的觸摸情況被調(diào)用。

重要規(guī)則

事件分發(fā)有一個很重要的原則:一個觸控點的事件序列只能給一個view消費睬捶,除非發(fā)生異常情況如被viewGroup攔截 黔宛。具體到代碼實現(xiàn)就是:消費了一個觸控點事件序列的down事件的view,將持續(xù)消費該觸控點事件序列接下來的所有的事件 擒贸。舉個栗子:

當我手指按下屏幕時產(chǎn)生了一個down事件臀晃,只有一個view消費了這個down事件,那么接下來我的手指滑動屏幕產(chǎn)生的move事件會且僅會給這個view消費介劫。而當我手機抬起徽惋,再按下時,這時候又會產(chǎn)生新的down事件座韵,那么這個時候就會再一次去尋找消費down事件的view险绘。所以,事件分發(fā),是以事件序列為單位的 宦棺。

因此下面的工作流程中都是指down事件的分發(fā) 瓣距,而不是ACTION_MOVE或ACTION_UP的分發(fā)。因為消費了down事件渺氧,意味著接下來的move和up事件都會給這個view處理旨涝,也就無所謂分發(fā)了蹬屹。但同時注意事件序列是可以被viewGroup的onInterceptTouchEvent中斷的侣背,這些就屬于其他的情況了。

細心的讀者還會發(fā)現(xiàn)事件分發(fā)中包含了多點觸控慨默。在多點觸控的情況下贩耐,ACTION_POINTER_DOWN與ACTION_DOWN的分發(fā)規(guī)則是不同的,具體可前往第二篇文章了解詳細厦取。ACTION_POINTER_DOWN在ACTION_DOWN的分發(fā)模型上稍作了一些修改而已潮太,后面會詳細解析,

工作流程模型

工作流程模型虾攻,本質(zhì)上就是不同的控件對象铡买,viewGroup和view之間事件分發(fā)方法的關(guān)系。需要注意的是霎箍,這里討論的是viewGroup和view的默認方法實現(xiàn)奇钞,不涉及其他實現(xiàn)類如DecorView的重寫方法。

下面用一段偽代碼來表示三個事件分發(fā)方法之間的關(guān)系( 這里再次強調(diào)漂坏,這里的事件分發(fā)模型分發(fā)的事件類型是ACTION_DOWN且都是默認的方法景埃,沒有經(jīng)過重寫,這點很重要 ):

public boolean dispatchTouchEvent(MotionEvent event){
    
    // 先判斷是否攔截
    if (onInterceptTouchEvent()){
        // 如果攔截調(diào)用自身的onTouchEvent方法判斷是否消費事件
        return onTouchEvent(event);
    }
    // 否則調(diào)用子view的分發(fā)方法判斷是否處理事件
    if (childView.dispatchTouchEvent(event)){
        return true;
    }else{
        return onTouchEvent(event);
    }
}

這段代碼非常好的展示了三個方法之間的關(guān)系:在viewGroup收到觸摸事件時顶别,會先去調(diào)用 onInterceptTouchEvent 方法判斷是否攔截谷徙,如果攔截則調(diào)用自己的 onTouchEvent 方法處理事件,否則調(diào)用子view的 dispatchTouchEvent 方法來分發(fā)事件驯绎。因為子view也有可能是一個viewGroup完慧,這樣就形成了一個類似遞歸的關(guān)系。

這里我再補上view分發(fā)邏輯的簡化偽代碼:

public boolean dispatchTouchEvent(MotionEvent event){
    // 先判斷是否存在onTouchListener且返回值為true
    if (mOnTouchListener!=null && mOnTouchListener.onTouch(event)){
        // 如果成功消費則返回true
        return true;
    }else{
        // 否則調(diào)用onTouchEvent消費事件
        return onTouchEvent(event);
    }
}

view與viewGroup不同的是他不需要分發(fā)事件剩失,所以也就沒有必要攔截事件屈尼。view會先檢查是否有onTouchListener且返回值是否為true,如果是true則直接返回赴叹,否則調(diào)用onTouchEvent方法來處理事件鸿染。

基于上述的關(guān)系,可以得到下面的工作流程圖:

image

這里為了展示類遞歸關(guān)系使用了畫了兩個viewGroup乞巧,只需看中間一個即可涨椒,下面對這個圖進行解析:

  • viewGroup
    1. viewGroup的dispatchTouchEvent方法接收到事件消息,首先會去調(diào)用onInterceptTouchEvent判斷是否攔截事件
      • 如果攔截,則調(diào)用自身的onTouchEvent方法
      • 如果不攔截則調(diào)用子view的dispatchTouchEvent方法
    2. 子view沒有消費事件蚕冬,那么會調(diào)用viewGroup本身的onTouchEvent
    3. 上面1免猾、2步的處理結(jié)果為viewGroup的dispatchTouchEvent方法的處理結(jié)果,并返回給上一層的onTouchEvent處理
  • view
    1. view的dispatchTouchEvent默認情況下會調(diào)用onTouchEvent來處理事件囤热,返回true表示消費事件猎提,返回false表示沒有消費事件
    2. 第1步的結(jié)果就是dispatchTouchEvent方法的處理結(jié)果,成功消費則返回true旁蔼,沒有消費則返回false并交給上一層的onTouchEvent處理

可以看到整個工作流程就是一個“U”型結(jié)構(gòu)锨苏,在不攔截的情況下,會一層層向下尋找消費事件的view棺聊。而如果當前view不處理事件伞租,那么就一層層向上拋,尋找處理的viewGroup限佩。

上述的工作流程模型并不是完整的葵诈,還有其他的特殊情況沒有考慮。下面討論幾種特殊的情況:

事件序列被中斷

我們知道祟同,當一個view接收了down事件之后作喘,該觸控點接下來的事件都會被這個view消費。但是晕城,viewGroup是可以在中途掐斷事件流的泞坦,因為每一個需要分發(fā)給子view的事件都需要經(jīng)過攔截方法:onInterceptTouchEvent (當然,這里不討論子view設置不攔截標志的情況)广辰。那么當viewGroup掐斷事件流之后暇矫,事件的走向又是如何的呢?參看下圖:(注意择吊,這里不討論多指操作的情況李根,僅討論單指操作的move或up事件被viewGroup攔截的情況

image
  1. 當viewGroup攔截子view的move或up事件之后,會將當前事件改為cancel事件并發(fā)送給子view
  2. 如果當前事件序列還未結(jié)束几睛,那些接下來的事件都會交給viewGroup的ouTouchEvent處理
  3. 此時不管是viewGroup還是view的onTouchEvent返回了false房轿,那么將導致整個控件樹的dispatchTouchEvent方法返回false
    • 秉承著一個事件序列只能給一個view消費的原則,如果一個view消耗了down事件卻在接下來的move或up事件返回了false所森,那么此事件不會給上層的viewGroup處理囱持,而是直接返回false。
多點觸控情況

上面討論的所有情況焕济,都是不包含多點觸控情況的纷妆。多點觸控的情況,在原有的事件分發(fā)流程上晴弃,新增了一些特殊情況掩幢。這里就不再畫圖逊拍,而是把一些特殊情況描述一下,讀者了解一下就可以了际邻。

默認情況下芯丧,viewGroup是支持多點觸控的分發(fā),但view是不支持多點觸控的世曾,需要自己去重寫 dispatchTouchEvent 方法來支持多點觸控缨恒。

多點觸控的分發(fā)規(guī)則如下:

viewGroup在已有view接受了其他觸點的down事件的情況下,另一個手指按下產(chǎn)生ACTION_POINTER_DOWN事件傳遞給viewGroup:

  1. viewGroup會按照ACTION_DOWN的方式去分發(fā)ACTION_POINTER_DOWN事件
    • 如果子view消費該事件轮听,那么和單點觸控的流程一致
    • 如果子view未消費該事件骗露,那么會交給上一個最后接收down事件的view去處理
  2. viewGroup兩個view接收了不同的down事件,那么攔截其中一個view的事件序列蕊程,viewGroup不會消費攔截的事件序列椒袍。換句話說驼唱,viewGroup和其中的view不能同時接收觸摸事件藻茂。

Activity的事件分發(fā)

細心的讀者會發(fā)現(xiàn),上述的工作流程并不涉及Activity玫恳。我們印象中的事件分發(fā)都是 Activity -> Window -> ViewGroup 辨赐,那么這是怎么回事?這一切,都是DecorView “惹的禍” 。

DecorView重寫viewGroup的 dispatchTouchEvent 方法饲宛,當接收到觸摸事件后麻敌,DecorView會首先把觸摸對象傳遞給內(nèi)部的callBack對象。沒錯缠诅,這個callBack對象就是Activity。加入Activity這個環(huán)節(jié)之后,分發(fā)的流程如下圖所示:

整體上和前面的流程沒有多大的不同换吧,Activity繼承了Window.CallBack接口,所以也有dispatchTouchEvent和onTouchEvent方法钥星。對上圖做個簡單的分析:

  1. activity接收到觸摸事件之后沾瓦,會直接把觸摸事件分發(fā)給viewGroup
  2. 如果viewGroup的dispatchTouchEvent方法返回false,那么會調(diào)用Activity的onTouchEvent來處理事件
  3. 第1谦炒、2步的處理結(jié)果就是activity的dispatchTouchEvent方法的處理結(jié)果贯莺,并返回給上層

上面的流程不僅適用于Activity,同樣適用于Dialog等使用DecorView和callback模式的控件系統(tǒng)宁改。

最后

到這里缕探,事件分發(fā)的主要內(nèi)容也就講解完了。結(jié)合前兩篇文章还蹲,相信讀者對于事件分發(fā)有更高的認知爹耗。

紙上得來終覺淺豁鲤,絕知此事要躬行。學了知識之后最重要的就是實踐鲸沮。下一篇文章將簡單分析一下如何利用學習到的事件分發(fā)知識運用到實際開發(fā)中琳骡。

原創(chuàng)不易,你的點贊是我創(chuàng)作最大的動力讼溺,感謝閱讀 ~

全文到此楣号,原創(chuàng)不易,覺得有幫助可以點贊收藏評論轉(zhuǎn)發(fā)怒坯。
筆者才疏學淺炫狱,有任何想法歡迎評論區(qū)交流指正。
如需轉(zhuǎn)載請評論區(qū)或私信交流剔猿。

另外歡迎光臨筆者的個人博客:傳送門

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末视译,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子归敬,更是在濱河造成了極大的恐慌酷含,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件汪茧,死亡現(xiàn)場離奇詭異椅亚,居然都是意外死亡,警方通過查閱死者的電腦和手機舱污,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進店門呀舔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人扩灯,你說我怎么就攤上這事媚赖。” “怎么了珠插?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵惧磺,是天一觀的道長。 經(jīng)常有香客問我丧失,道長豺妓,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任布讹,我火速辦了婚禮琳拭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘描验。我一直安慰自己白嘁,他們只是感情好,可當我...
    茶點故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布膘流。 她就那樣靜靜地躺著絮缅,像睡著了一般鲁沥。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上耕魄,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天画恰,我揣著相機與錄音,去河邊找鬼吸奴。 笑死允扇,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的则奥。 我是一名探鬼主播考润,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼读处!你這毒婦竟也來了糊治?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤罚舱,失蹤者是張志新(化名)和其女友劉穎井辜,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體馆匿,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡抑胎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了渐北。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡铭拧,死狀恐怖赃蛛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情搀菩,我是刑警寧澤呕臂,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站肪跋,受9級特大地震影響歧蒋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜州既,卻給世界環(huán)境...
    茶點故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一谜洽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧吴叶,春花似錦阐虚、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奥秆。三九已至,卻和暖如春咸灿,著一層夾襖步出監(jiān)牢的瞬間构订,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工避矢, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留鲫咽,地道東北人。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓谷异,卻偏偏與公主長得像分尸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子歹嘹,可洞房花燭夜當晚...
    茶點故事閱讀 43,612評論 2 350

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