前言
在日常開發過程中,可能會遇到這些問題:滑動沖突、點擊事件響應沖突等問題。那么造成這些問題的根源到底是什么呢?其實這都是Android事件分發導致的,只有掌握了事件分發機制,才能讓我們從根源上理解并解決這類問題。
事件分發對象
事件分發過程中,涉及到三種UI對象類型:Activity、ViewGroup、View 及其派生類。三者之間的關系如下圖:
發生一次點擊事件時,事件會按照Activity->ViewGroup->View的順序,進行事件傳遞。
- Activity:控制UI頁面的生命周期,是事件分發的入口。
- ViewGroup:View的特殊子類,是一組View的集合,是Android中所有布局的父類。
- View:所有UI組件的基類,常見的Button、TextView等控件都繼承自View。
Android事件分發機制,其實就是Activity、ViewGroup、View三者對觸摸點擊事件的事件傳遞過程。
事件整體分發流程
事件分發流程圖
在整個事件分發,并響應事件的過程中,有三個重要的方法:
- dispatchTouchEvent:分發(傳遞)點擊事件,當點擊事件能夠傳遞給當前View時,該方法就會被調用。
- onInterceptTouchEvent:判斷是否攔截某個事件,該方法僅在ViewGroup中存在。一般情況下會在ViewGroup的dispatchTouchEvent方法中調用該方法。
- onTouchEvent:處理點擊事件,在dispatchTouchEvent內部調用。
Activity事件分發流程
Activity中主要涉及以下兩個事件方法:
- boolean dispatchTouchEvent(MotionEvent ev):事件分發
- boolean onTouchEvent(MotionEvent event):事件消費
在Activity中接受到點擊事件,首先會執行dispatchTouchEvent()方法,進行事件分發。經過window、decorView依次傳遞后,頁面上的 ViewGroup會接收到該事件。ViewGroup如果消費了該事件,則分發結束(流程在ViewGroup中繼續向下分發),未消費則繼續調用Activity的onTouchEvent 方法處理事件,流程圖如下:
Activity事件分發流程
ViewGroup事件分發流程
ViewGroup 涉及到三個事件分發與處理的方法:
- dispatchTouchEvent(MotionEvent ev):事件分發
- onIntercepTouchEvent(MotionEvent ev):事件攔截
- onTouchEvent(MotionEvent ev):事件消費
ViewGroup事件分發流程圖
ViewGroup通過dispatchTouchEvent()方法接收到事件,然后根據ViewGroup onInterceptTouchEvent()方法的返回值判斷:
- 返回true,則調用ViewGroup的onTouchEvent()方法,如果消費了事件,則事件傳遞結束,如果不消費事件,則事件傳遞回Activity并執行Activity的onTouchEvent()方法;
- 返回false,則將事件傳遞給子View,由子View繼續完成事件向下分發。
View的事件分發流程
View 主要涉及如下兩個事件分發與處理的方法:
- dispatchTouchEvent(MotionEvent ev):事件分發
- onTouchEvent(MotionEvent ev):事件消費
View事件分發流程圖
View通過dispatchTouchEvent方法接收到從ViewGroup傳遞過來的事件后,直接調用 onTouchEvent方法處理事件。如果沒有消費事件,則調用ViewGroup的onTouchEvent方法處理事件,然后繼續ViewGroup事件流程;如果消費了該事件,則分發結束。
注意:
在View把事件消費后,如果View的onTouch方法返回true,View的dispatchTouchEvent方法會直接返回true,不會再調用View的onClick方法;只有當onTouch方法返回false時,才會有onClick事件處理。
思考點
- onTouch()和onTouchEvent()的區別?
這兩個方法都是在View的dispatchTouchEvent中調用,但onTouch優先于onTouchEvent執行。如果在onTouch方法中返回true將事件消費掉,onTouchEvent()將不會再執行。
特別注意:請看下面代碼
//1. mOnTouchListener的值不能為空
//2. 當前點擊的控件必須是enable的
mOnTouchListener !=null && (mViewFlags & ENABLED_MASK) == ENABLED &&mOnTouchListener.onTouch(this, event)
因此如果你有一個控件是非enable的,那么給它注冊onTouch事件將永遠得不到執行。對于這一類控件,如果我們想要監聽它的touch事件,就必須通過在該控件中重寫onTouchEvent方法來實現。
- Touch事件的后續事件(MOVE、UP)層級傳遞
接收了ACTION_DOWN事件的函數不一定能收到后續事件(ACTION_MOVE、ACTION_UP);如果在某個對象(Activity、ViewGroup、View)的dispatchTouchEvent()消費事件(返回true),那么收到ACTION_DOWN的函數也能收到ACTION_MOVE和ACTION_UP。






