MotionEvent
每一次用戶Touch事件都會被包裝為一個(gè)MotionEvent
對象购对,對象中包含關(guān)于這個(gè)事件你想要的全部信息噪猾,包括事件所產(chǎn)生的動(dòng)作(ACTION_DOWN掌呜、ACTION_MOVE疗绣、ACTION_UP侦另、ACTION_CANCEL等)
岁疼、事件產(chǎn)生的位置坐標(biāo)、事件發(fā)生時(shí)屏幕上手指的數(shù)量和時(shí)間的發(fā)生時(shí)間等晨逝。
在Android中蛾默,一個(gè)手勢(gesture)的定義是一組開始于ACTION_DOWN
并結(jié)束于ACTION_UP
的事件集合。
Touch事件傳遞
對于每一個(gè)由硬件和底層框架產(chǎn)生的事件捉貌,這些事件會首先被發(fā)送到當(dāng)前顯示的Activity中支鸡,這個(gè)過程是由框架調(diào)用Activity的dispatchTouchEvent()
來實(shí)現(xiàn)的,不管你在程序中做了何種事件處理趁窃,Activity的dispatchTouchEvent()
都會是Event被處理的第一站牧挣。在這個(gè)方法里,Event將開始由上到下被傳遞棚菊,從rootView依次往下傳遞直到視圖樹的最底部浸踩,之后,事件會反過來以冒泡的方式向上傳遞统求。這里的傳遞機(jī)制是事件會在程序中從上往下再從下往上穿梭检碗,直到某個(gè)View宣布其對這個(gè)事件感興趣為止。是否感興趣取決于View的onTouchEvent()
方法是否返回了true码邻。
當(dāng)嘗試做自定義事件處理時(shí)折剃,ACTION_DOWN
事件需要被關(guān)注的,如果你對ACTION_DOWN
后續(xù)發(fā)生的事件感興趣像屋,就需要在onTouchEvent()
中返回true怕犁。Android系統(tǒng)認(rèn)為如果你對手勢中的ACTION_DOWN
事件不感興趣,那么你對這一手勢中的其他事件亦不會感興趣己莺,一旦表示對ACTION_DOWN
感興趣奏甫,那么手勢中的其他事件便會以此View為直接目的地。
總結(jié)一下凌受,事件在視圖樹中的傳遞方式就是從rootView開始自頂向下傳遞阵子,根據(jù)各層是ViewGroup還是View調(diào)用各自的dispatchTouchEvent()
方法,ViewGroup會把事件遞歸地傳遞給它的child胜蛉。接下來挠进,事件從下往上傳遞時(shí)色乾,會調(diào)用ViewGroup和View的onTouchEvent()
。所以领突,Activity中的onTouchEvent()
是最后被調(diào)用到的暖璧,如果已經(jīng)有子View的onTouchEvent()
返回了true,那么Activity的onTouchEvent()
則根本不會被調(diào)用君旦。另外我們可以使用TouchListener
來處理觸摸事件澎办,并返回true表示已經(jīng)消費(fèi)了該事件。
下面我們來梳理各個(gè)組件在事件傳遞流程中所承擔(dān)的工作金砍。
Activity
dispatchTouchEvent()
該方法總是在事件發(fā)生時(shí)最先被調(diào)用到浮驳;
執(zhí)行后會將Event發(fā)送給Window,再由Window發(fā)送給DecorView捞魁,最后傳遞到用戶自定義的RootView(通常是一個(gè)ViewGroup)并觸發(fā)RootView的dispatchTouchEvent()
。
onTouchEvent()
在整個(gè)視圖樹中沒有View消費(fèi)了事件的時(shí)候被調(diào)用离咐;
事件傳遞的終點(diǎn)谱俭,總是最后被調(diào)用(如果可能)。
View
dispatchTouchEvent()
如果存在TouchListener
宵蛀,則將Event發(fā)送給執(zhí)行listener.onTouch()
昆著;
如果不存在TouchListener
或listener.onTouch()
沒有返回true,則自己處理該事件术陶,即調(diào)用自身的onTouchEvent()
凑懂。
onTouchEvent()
如果返回false,則將Event向上冒泡梧宫,并且該View不再能接受當(dāng)前手勢中的后續(xù)事件接谨。
ViewGroup
dispatchTouchEvent()
對childView進(jìn)行遍歷和迭代,以確定哪些child可能對事件感興趣(根據(jù)當(dāng)前觸摸位置判斷)塘匣,如果觸摸位置位于多個(gè)child邊界范圍內(nèi)脓豪,則按照被加入到ViewGroup的順序的逆序遍歷這些child,讓child有處理事件的機(jī)會忌卤。
對事件進(jìn)行中斷或竊取扫夜,通過onInterceptTouchEvent()
onInterceptTouchEvent()
此方法在不斷監(jiān)控觸摸事件,在需要滿足該ViewGroup特殊需求的時(shí)候中斷將事件分發(fā)給原本要消費(fèi)事件的childView驰徊,轉(zhuǎn)而讓自己來處理這些事件笤闯,通常在ViewGroup自身要根據(jù)手勢進(jìn)行滾動(dòng)等操作的時(shí)候調(diào)用此方法,調(diào)用此方法可能會向childView傳遞ACTION_CANCEL
事件棍厂。
requestDisallowInterceptTouchEvent()
由父視圖調(diào)用颗味,用來打斷onInterceptTouchEvent()
的邏輯。通常勋桶,事件都是由ViewGroup先監(jiān)測脱衙,再決定是否分發(fā)到childView中侥猬,同時(shí)也由ViewGroup決定是否屏蔽其childView的事件。但是捐韩,當(dāng)childView需要決定父視圖是否可以屏蔽觸摸事件(不管是暫時(shí)還是永久)時(shí)退唠,就需要調(diào)用此方法。如果傳入true荤胁,則剝奪父視圖中斷此事件的能力瞧预。當(dāng)ACTION_UP
事件發(fā)生時(shí),前一次調(diào)用requestDisallowInterceptTouchEvent()
設(shè)置的狀態(tài)會失效仅政。通常垢油,在ViewPager的頁面中嵌入可以橫向滑動(dòng)的View時(shí)你會需要調(diào)用此方法來確保滑動(dòng)View時(shí)不會引起ViewPager的頁面切換圆丹。
Attention
除非真的有必要滩愁,否則盡量不要重寫dispatchTouchEvent()
方法,如果進(jìn)行了重寫辫封,則必須調(diào)用super.dispatchTouchEvent()
方法硝枉,否則事件分發(fā)過程會在此中斷。
當(dāng)dispatchTouchEvent
返回true時(shí)倦微,表示在此時(shí)Event已經(jīng)被處理妻味,事件傳遞到此為止,返回false時(shí)欣福,如果事件來自于Activity责球,則將事件交給Activity的onTouchEvent()
處理,如果來自于父View拓劝,則將事件交給父View的onTouchEvent()
處理
一旦View的onTouchEvent
在處理手勢中的某一Event時(shí)返回了false雏逾,則該手勢中的后續(xù)事件不會再到達(dá)該View