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