前言
很高興遇見你~
本文是事件分發(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é),更好地把握事件是如何在不同的對象和方法之間進行傳遞成艘。
回顧
先來回顧一下整體的流程赏半,以便更好地定位我們的知識。
- 觸摸信息從手機觸摸屏幕時產(chǎn)生淆两,通過IMS和WMS發(fā)送到viewRootImpl
- viewRootImpl通過調(diào)用view的dispatchPointerEvent方法把觸摸信息傳遞給view
- 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接口中包含了 dispatchTouchEvent
和 onTouchEvent
方法自沧,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)系,可以得到下面的工作流程圖:
這里為了展示類遞歸關(guān)系使用了畫了兩個viewGroup乞巧,只需看中間一個即可涨椒,下面對這個圖進行解析:
- viewGroup
- viewGroup的dispatchTouchEvent方法接收到事件消息,首先會去調(diào)用onInterceptTouchEvent判斷是否攔截事件
- 如果攔截,則調(diào)用自身的onTouchEvent方法
- 如果不攔截則調(diào)用子view的dispatchTouchEvent方法
- 子view沒有消費事件蚕冬,那么會調(diào)用viewGroup本身的onTouchEvent
- 上面1免猾、2步的處理結(jié)果為viewGroup的dispatchTouchEvent方法的處理結(jié)果,并返回給上一層的onTouchEvent處理
- viewGroup的dispatchTouchEvent方法接收到事件消息,首先會去調(diào)用onInterceptTouchEvent判斷是否攔截事件
- view
- view的dispatchTouchEvent默認情況下會調(diào)用onTouchEvent來處理事件囤热,返回true表示消費事件猎提,返回false表示沒有消費事件
- 第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攔截的情況)
- 當viewGroup攔截子view的move或up事件之后,會將當前事件改為cancel事件并發(fā)送給子view
- 如果當前事件序列還未結(jié)束几睛,那些接下來的事件都會交給viewGroup的ouTouchEvent處理
- 此時不管是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:
- viewGroup會按照ACTION_DOWN的方式去分發(fā)ACTION_POINTER_DOWN事件
- 如果子view消費該事件轮听,那么和單點觸控的流程一致
- 如果子view未消費該事件骗露,那么會交給上一個最后接收down事件的view去處理
- 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方法钥星。對上圖做個簡單的分析:
- activity接收到觸摸事件之后沾瓦,會直接把觸摸事件分發(fā)給viewGroup
- 如果viewGroup的dispatchTouchEvent方法返回false,那么會調(diào)用Activity的onTouchEvent來處理事件
- 第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ū)或私信交流剔猿。另外歡迎光臨筆者的個人博客:傳送門