本文由本人原創(chuàng)诚些,轉(zhuǎn)載請注明作者
Android中的事件分發(fā)機(jī)制是新手學(xué)習(xí)的一個(gè)重難點(diǎn)。而且往往學(xué)習(xí)了之后以為掌握的差不多了皇型,但遇到新問題的時(shí)候又發(fā)現(xiàn)沒有掌握到位或者又忘掉了。筆者就是這種情況砸烦,因此將自己已掌握的事件分發(fā)機(jī)制記錄下來弃鸦,作為記錄和交流。
一幢痘、背景目的
- 什么是Android事件分發(fā)機(jī)制唬格?
事件分發(fā)機(jī)制是處理Android各種滑動(dòng)沖突的理論基礎(chǔ),也是學(xué)習(xí)Android的核心知識(shí)點(diǎn)颜说,掌握好View和ViewGroup的事件分發(fā)機(jī)制是十分重要的购岗。
- 為什么Android要設(shè)計(jì)一套事件分發(fā)機(jī)制?
當(dāng)父控件和子控件都可以響應(yīng)用戶輸入作出行為的時(shí)候(比如觸摸滑動(dòng))门粪,這時(shí)候就會(huì)造成事件沖突喊积。如何判斷用戶的輸入事件由父控件處理還是子控件處理,就需要設(shè)計(jì)出一套規(guī)則了玄妈,這套規(guī)則就是Android事件分發(fā)機(jī)制乾吻。
- 沒有子父關(guān)系的控件會(huì)出現(xiàn)事件沖突嗎?
沒有子父關(guān)系的控件拟蜻,它們是不會(huì)產(chǎn)生事件沖突的绎签。因此在處理事件沖突的時(shí)候,首先要分析產(chǎn)生沖突的子控件是哪一個(gè)酝锅,父控件是哪一個(gè)诡必。
二、基礎(chǔ)知識(shí)
- View和ViewGroup
Android中的控件大致可以分為兩類搔扁,分別是View和ViewGroup爸舒。其中ViewGroup實(shí)際上也是繼承自View。ViewGroup表示可以擁有子控件的控件阁谆,常見的有LinearLayout碳抄、ListView這些。后面我們將通過學(xué)習(xí)知道场绿,在Android事件分發(fā)機(jī)制中剖效,View是沒有攔截方法的,而ViewGroup的攔截方法沒有重寫的話默認(rèn)也是不進(jìn)行攔截的。
- MotionEvent分類
Android設(shè)備可以接收的事件總類很多璧尸,比如按壓咒林、拖拽、滑動(dòng)爷光,這些事件分類是由android底層完成的垫竞。總的來說蛀序,用戶操作可以分為三類欢瞪,及MotionEvent中的ACTION_DOWN(按下),ACTION_MOVE(移動(dòng))徐裸,ACTION_UP(抬起)遣鼓。從用戶手指按下到抬起這一連串的事件被稱之為一個(gè)事件序列合呐。關(guān)于事件序列犀盟,下面也會(huì)說到龙填。
三菊匿、事件分發(fā)流程
從這張圖片來看一次點(diǎn)擊事件的傳遞咙咽。在這張圖中有三個(gè)存在子父View關(guān)系的View丧诺,其中上面兩個(gè)能作為父View的自然是屬于ViewGroup了脯颜。
ViewGroup—dispatchTouchEvent方法
Touch事件發(fā)生后悠汽,頂級(jí)父View先接受到消息潜圃,此時(shí)會(huì)先調(diào)用頂級(jí)View的dispatchTouchEvent方法缸棵,這在圖片上沒有畫出來。該方法的返回true的話秉犹,代表事件被消費(fèi)掉了(當(dāng)事件被消費(fèi)時(shí)便不再傳遞)蛉谜;返回false的話事件不再往下傳遞,由上一級(jí)View的onTouchEvent方法來處理崇堵。如果沒有重寫該方法的話(即調(diào)用ViewGroup中的dispatchTouchEvent方法)型诚,會(huì)判斷onInterceptTouchEvent的返回值來確定下一步傳遞方向。
ViewGroup—onInterceptTouchEvent方法
該方法顧名思義鸳劳,判斷當(dāng)前View是否攔截該事件狰贯。如圖所示,返回Ture的話會(huì)攔截事件傳遞赏廓,調(diào)用頂級(jí)View的onTouchEvent方法來處理事件涵紊。返回false的話表示不中斷,事件繼續(xù)向下傳遞幔摸。傳遞到下一級(jí)父View的過程也是一樣摸柄,onInterceptTouchEvent方法返回false的話會(huì)一直向下傳遞到子View。
View-dispatchTouchEvent方法
View的dispatchTouchEvent方法跟ViewGroup是有區(qū)別的既忆。通過看源碼可以知道驱负,View沒有onInterceptTouchEvent方法嗦玖,因此也它會(huì)直接調(diào)用onTouchEvent方法來判斷事件是否被消耗。另外如果View被設(shè)置了各種Listener(如OnClickListener)之后跃脊,對(duì)應(yīng)的事件也會(huì)隨著Listener中對(duì)應(yīng)的方法返回true而被消耗宇挫。
View—onTouchEvent方法
傳遞到子View的時(shí)候會(huì)調(diào)用子View的dispatchTouchEvent方法,一般自定義View的時(shí)候在onTouchEvent中處理與用戶觸摸按壓的交互邏輯酪术。不管該方法過程如何器瘪,如果onTouchEvent的返回值為True表示事件被消耗,事件不再傳遞绘雁。反之橡疼,事件將向上傳遞,傳給父View去處理咧七。另外需要注意的是當(dāng)View的clickable和longClickabale屬性同時(shí)為false的時(shí)候衰齐,代表View不可點(diǎn)擊(如TextView),因此onTouchEvent方法也會(huì)默認(rèn)返回false不消耗事件继阻。
ViewGroup—onTouchEvent方法
ViewGroup對(duì)onTouchEvent的方法和View一樣,返回true的話代表事件被消耗废酷,返回false將事件繼續(xù)向上傳遞瘟檩。具體的行為要看具體的重寫方法。
四澈蟆、總結(jié)分發(fā)流程
可以看到事件傳遞時(shí)是一層層向下傳遞接受墨辛,再由下往上進(jìn)行處理。這和現(xiàn)實(shí)工作也很類似:
產(chǎn)品經(jīng)理提了一個(gè)新的需求趴俘,高級(jí)程序員接到需求先考慮下要不要自己做(onInterceptTouchEvent方法過程)睹簇,覺得應(yīng)該自己處理就自己完成了(ViewGroup—onTouchEvent方法過程);覺得應(yīng)該由下屬去做就下發(fā)給見習(xí)程序員寥闪,見習(xí)程序員沒有下屬只能自己去處理(View—onTouchEvent方法過程)太惠;見習(xí)程序員如果很好地解決需求了(onTouchEvent方法返回true),這個(gè)事件就到此結(jié)束了疲憋。如果需求太難凿渊,見習(xí)程序員處理不了這個(gè)需求(onTouchEvent方法返回false),那么就會(huì)再交給上級(jí)去處理缚柳。
可見Google的程序員在設(shè)計(jì)這些代碼時(shí)的用的方法十分巧妙埃脏,也是和現(xiàn)實(shí)相結(jié)合地去設(shè)計(jì)代碼,對(duì)于開發(fā)者也便于理解秋忙。
任玉剛大神在《Android開發(fā)藝術(shù)探索》一書中對(duì)事件分發(fā)機(jī)制的講解十分到位彩掐,推薦有興趣的朋友去看看。書中用一段偽代碼把事件分發(fā)機(jī)制抽象的非常清楚:
public boolean dispatchTouchEvent(MotionEvent event) {
boolean consume =false;
if(onInterceptTouchEvent(ev)) {
consume = onTouchEvent(ev);
} else {
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
其中comsume代表事件是否被消耗灰追。當(dāng)事件傳遞到ViewGroup的時(shí)候堵幽,先判斷是否攔截狗超。攔截的話由自己的onTouchEvent方法處理;不攔截的話谐檀,分發(fā)給子View處理抡谐,調(diào)用子View的dispatchTouchEvent方法。