編輯:關於Android編程
事件分發在Android中非常重要,在滑動沖突,下拉刷新,嵌套滑動的時候都需要非常清楚事件分發的機制,才能寫好對應的處理代碼。曾經以為我對事件分發已經很清楚了,也寫過幾篇文章,但是總感覺沒有完全說清楚,今天再從代碼的角度分析一遍事件分發機制,希望以後遇到所有事件分發的問題,都能在這裡找到答案。
先看幾個問題,如果這些問題你都知道答案,那本篇文章就不用看了。
1、如果攔截了某個事件,是否就會交由本view的View:dispatchTouchEvent處理?
2、一個事件,如果子view處理失敗,是否就交還給父view處理?
3、如果一個down事件,大家都不處理,會怎麼樣?
4、parent把事件傳遞給哪個子view呢?是根據位置查一遍的嗎?
規則1:事件傳遞由父控件傳遞到子控件,事件消費是子控件優先。
規則2:down事件,子控件如果不消費,就還給父控件。
規則3:我是一個壞父親,父親吃到肉了,絕不會再給兒子,兒子吃到肉了,父親還可能搶。
手指從按下到抬起,我們稱為一個cycle,以DOWN事件開始,UP事件結束,裡面有若干個MOVE事件。一個cycle內,v1處理了某事件,後邊的事件絕不會被v1的child處理,v1肯定會攔下來。
很多文章在介紹事件分發的時候,都會提到onTouch或者onTouchEvent,本文不會說這2個,因為這2個都是View的dispatchTouchEvent方法內,本文只會提到dispatchTouchEvent方法,這樣更准確一點。當然,其實大部分情況下,View的dispatchTouchEvent就是調用onTouchEvent,一般onTouch是沒有的,這塊的邏輯如果不清楚的話,可以看android點擊事件(View)。
在講述事件分發的流程前,先定義三個角色,p,pp,c其中p為主角ViewGroup,pp是p的parent,c為p的child。
手指按下就會觸發down事件。例如我們點擊了一個TextView,down事件會從activity開始傳遞,然後傳遞給DecorView,接著往下傳遞給對應的ViewGroup,一層層傳下來直到TextView。
觸摸了任何一個ViewGroup都會調用ViewGroup的dispatchTouchEvent。首先會先進入onInterceptTouchEvent,如果返回true的話,就攔截了,交由本viewgroup的View::dispatchTouchEvent方法,注意這裡和前面的dispatchTouchEvent方法不一樣,一個是View的dispatchTouchEvent,一個是Viewgroup的dispatchTouchEvent。View的dispatchTouchEvent我們在android點擊事件(View)詳細說過了,而Viewgroup的dispatchTouchEvent就是負責事件分發的核心代碼,也就是我們這篇文章的主要內容,看明白了這個函數的200多行代碼,事件分發的所有問題都能明白。
結合下邊的圖,我們可以明白down事件的傳遞機制。
1、如果p的onInterceptTouchEvent返回true,那就直接攔截,交給p的View::dispatchTouchEvent處理,流程圖中的super.dispatchTouchEvent就是指View::dispatchTouchEvent,後面的流程暫時不說;
2、如果p的onInterceptTouchEvent返回false,那就不攔截,繼續查點擊到了哪個child,如果查不到,那就還是交給p的View::dispatchTouchEvent處理。如果查到了,那就交給這個child(簡稱c)的dispatchTouchEvent處理。c的dispatchTouchEvent有2種結果,true和false,如果返回false,那還是交給p的View::dispatchTouchEvent處理;如果返回了true表示c已經處理好了這個事件,那p就很開心了,小弟幫我完成了一件事,記下他的功勞,把mFirstTouchTarget進行賦值,指向c,然後p的dispatchTouchEvent返回true。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPrjVssW9u7j4cLXEVmlldzo6ZGlzcGF0Y2hUb3VjaEV2ZW50tKbA7aOsuvPD5rXEwfezzLu5w7vLtaGjtNPHsMPmtcTB97PMv8nS1L+0tb2jrNfftb3V4sDv09Az1tbUrdLyoaMxoaJvbkludGVyY2VwdFRvdWNoRXZlbnS3tbvYwct0cnVlo6zAub3YwcujuzKhorXju/e1xNXiuPa148O709C21NOmtcRjaGlsZKO7M6GiY8O709C0psDtusOjrLe1u9jBy2ZhbHNloaNwtcRWaWV3OjpkaXNwYXRjaFRvdWNoRXZlbnTSsta709Ay1ta94bn7o6x0cnVlu/LV32ZhbHNlo6zI57n7t7W72MHLdHJ1ZaOsxMfV+7j2cLXEZGlzcGF0Y2hUb3VjaEV2ZW50vs23tbvYwct0cnVlo6y1q8rHtMvKsW1GaXJzdFRvdWNoVGFyZ2V0zqpudWxso6zS8s6qysfX1Ly6tKbA7bXEo6yyu8rHY2hpbGS0psDttcSho8jnuftwtcRWaWV3OjpkaXNwYXRjaFRvdWNoRXZlbnS3tbvYwctmYWxzZaOsxMfV+7j2cLXEZGlzcGF0Y2hUb3VjaEV2ZW50vs23tbvYZmFsc2WhozwvcD4NCjxwPrTLyrFwtcRkaXNwYXRjaFRvdWNoRXZlbnS94cr4wcujrL3hyvi1xMqxuvK74be1u9h0cnVlu/LV32ZhbHNlo6zEx7rzw+a74beiyfrKssO0xNijv87Sw8e/tNXiuPbB97PMzbyjrNKq09C13bnptcTLvM/roaO0y8qxcDogZGlzcGF0Y2hUb3VjaEV2ZW50zeqzySzG5Mq1us1jOmRpc3BhdGNoVG91Y2hFdmVudCgpysfSu9H5tcSjrNKqsNG94bn7uObL33Bwo6hwtcRwYXJlbnSjqaGjPC9wPg0KPHA+tMvKsbXE17TMrNPQM9bWOjxiciAvPg0K17TMrDGjunA6IGRpc3BhdGNoVG91Y2hFdmVudCgpt7W72HRydWWjrLKix9JwtcRtRmlyc3RUb3VjaFRhcmdldL/Vo6y0+rHtysdwtKbA7cHLysK8/mRvd248YnIgLz4NCte0zKwgMqO6cDogZGlzcGF0Y2hUb3VjaEV2ZW50KCm3tbvYZmFsc2U8YnIgLz4NCte0zKwzOnA6IGRpc3BhdGNoVG91Y2hFdmVudCgpt7W72HRydWWjrLKix9JwtcRtRmlyc3RUb3VjaFRhcmdldLfHv9UstPqx7XC1xGNoaWxktKbA7cHLysK8/qGjPC9wPg0KPHA+08NtRmlyc3RUb3VjaFRhcmdldLzHwrzT0Mqyw7S6w7SmxNijv8/rz+ujrMjnufu63MnPsuO1xHZpZXfP69aqtcC1vbXXy63Bos/CwcvI57TLtPO5pqOstKbA7cHLysK8/qOsy7PXxW1GaXJzdFRvdWNoVGFyZ2V01dK5/cC0vs3Q0MHLo6zG5Mq11rvT0NTaZG93bsrCvP61xMqxuvK74bj5vt2wtM/CtcTOu9bDwLSy6dXSttTTprXE19N2aWV3o6y688PmtcTKwrz+trzKx7j5vt1tRmlyc3RUb3VjaFRhcmdldMC0sunV0rXEo6zV4tH5w/fP1MzhuN/Qp8LKoaM8L3A+DQo8aDEgaWQ9"move的事件分發">MOVE的事件分發
我們先回頭看下,down事件結束之後的三種狀態,其實可以合並成2種狀態。
先看狀態1,p: dispatchTouchEvent()返回true,那麼pp的dispatchTouchEvent()肯定也返回true,並且pp的mFirstTouchTarget指向p,看看這個是不是和狀態3類似的,只是p換成了pp。 這種情況我們稱為case1,case1的本質是什麼?有人成功處理了down事件。
再看狀態2,p: dispatchTouchEvent()返回false,會來到pp的dispatchTouchEvent()代碼內,pp的dispatchTouchEvent()可能返回true或者false,如果返回了true,那其實和case1類似了。如果還是返回false,那就繼續往上傳,只要祖宗有一個返回了true,那就掉入了case1.如果大家堅持返回false,那就會一直傳到DecorView。這種情況我們稱為case2,本質就是無人成功處理down事件。
無人處理down事件,比較簡單,我們先說,發生的概率也很小。沒有人處理down事件,這個事件就會一直往上拋,直到PhoneWindow$DecorView。而DecorView的onTouchEvent一般返回false,DecorView的mFirstTouchTarget為null。下一次move事件來了,直接攔截並且自己處理。所以結果就是後面的所有事件都停在了DecorView,不會下傳,而DecorView的處理結果就是false。所以這種情況下,後面的事件都不會被處理,可以認為被丟棄了。
假設有view族譜p1,p2,…pn,後面一個是前面一個的parent。假設p2處理了down事件,那麼我們根據規則3,move事件不可能給p1,所以我們不用考慮p1。此時p2的mFirstTouchTarget為null,p3,p4等的mFirstTouchTarget非空。所以此時有2種類型的view要考慮,第一種是p2類型的,mFirstTouchTarget為null;第二種是p3,p4類型的,mFirstTouchTarget非空。
move事件的傳遞,可以分為2個階段,第一階段就是決定intecepted的值,第二階段就是根據intecepted的值進行事件分發.
第一階段流程圖如下所示。
第一種情況,mFirstTouchTarget為null,intecepted直接會變為true,攔截所有事件,這就是規則3的來源。
第二種情況,mFirstTouchTarget非空,會根據disallowIntercept標志和onInterceptTouchEvent()來決定intecepted的值。
第二階段流程圖如下所示
此時可以分3個case來看
先看mFirstTouchTarget為空的情況,那麼他的intecepted必定是true,會調用View:dispatchTouchEvent()作為返回值
若mFirstTouchTarget非空,intecepted為false,此時按理說會去找對應位置的child,NONONO。這裡的邏輯和down事件不一樣,這裡不會根據位置去找,而是根據mFirstTouchTarget去找,因為我們down事件的child已經記錄在mFirstTouchTarget內了,所以直接找mFirstTouchTarget就行。(其實mFirstTouchTarget其實是個鏈表,跟著鏈表爬一遍)。mFirstTouchTarget的處理結果就作為整個dispatchTouchEvent的返回結果。
若mFirstTouchTarget非空,intecepted為true,他會給mFirstTouchTarget指向的view發一個cancel事件,然後mFirstTouchTarget置null,然後返回true。啊??居然不調用自己的View:dispatchTouchEvent嗎?確實是的。本次move事件,實際上不會調用自己的View:dispatchTouchEvent。但是此時view mFirstTouchTarget已經為null了,所以下一次move來的時候,走的是case2,View:dispatchTouchEvent.這一點我之前是理解錯誤的。其實這裡損失了一個MOVE事件,這個MOVE事件雖然返回了true,但是其實沒有任何人處理他。
由於case2和case3的代碼我不太熟悉,所以抓出來分析一下。
先看case2,此時mFirstTouchTarget非空,在L13把事件發給child
// Dispatch to touch targets, excluding the new touch target if we already // dispatched to it. Cancel touch targets if necessary. TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; //在這裡把事件發給child if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; }
再來看case3,mFirstTouchTarget非空,intecepted為true
來看這段代碼,L12因為intercepted為true,所以cancelChild為true,會走到dispatchTransformedTouchEvent,dispatchTransformedTouchEvent內部會發一個cancel事件出去,然後返回true(後邊會詳細說)。然後L18,因為cancelChild為null,所以會執行L21,把mFirstTouchTarget置null。
{ // Dispatch to touch targets, excluding the new touch target if we already // dispatched to it. Cancel touch targets if necessary. TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { //注意這裡,因為intercepted為true,所以cancelChild也會為true final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } if (cancelChild) { if (predecessor == null) { //mFirstTouchTarget置null mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } }
我們再看看dispatchTransformedTouchEvent的流程,此時傳進來的cancel為true,會再9設置CANCEL事件,在L14由child發出去。
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; // Canceling motions is a special case. We don't need to perform any transformations // or filtering. The important part is the action, not the contents. final int oldAction = event.getAction(); if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { handled = super.dispatchTouchEvent(event); } else { //走這裡,發一個cancel消息出去 handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); //返回true return handled; }
UP事件其實和MOVE事件基本一致,UP事件一般不攔截。即使攔截了UP事件,也不會調用自己的View:dispatchTouchEvent.為什麼?可以參考 move事件第二階段的case3,簡單來說如果攔截UP事件,此時mFirstTouchTarget非空的話,此次dispatchTouchEvent會讓child發一個cancel出去,把自己的mFirstTouchTarget置空,然後返回true,不會調用View:dispatchTouchEvent。因為只有下一個事件來臨的時候才調用View:dispatchTouchEvent,可是UP已經是最後一個事件了,所以不會發生後面的事。
講了這麼多,我嘗試著用偽代碼寫ViewGroup的dispatchTouchEvent,其實也不麻煩,40行代碼說明了一切。
// Check for interception. final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { intercepted = onInterceptTouchEvent(ev); } else { intercepted = false; } } else { intercepted = true; } //查找合適的子view if(down事件&&!intercepted){ handled=某個child.dispatchTouchEvent(); if(handled){ mFirstTouchTarget賦值 } } if(mFirstTouchTarget==null){ //只有這種情況,parent親自處理 handled=super.dispatchTouchEvent() }else{ if(intercepted){ mFirstTouchTarget發一個cancel事件 mFirstTouchTarget=null; }else{ return mFirstTouchTarget.dispatchTouchEvent(); } } return handled;
1、如果攔截了某個事件,是否就會交由本view的View:dispatchTouchEvent處理?
這裡的攔截的意思是指在onInterceptTouchEvent裡返回了true,如果攔截的只是down事件,那麼必然會交給View:dispatchTouchEvent處理。如果攔截的只是MOVE事件,那麼是不會交給View:dispatchTouchEvent處理的,此時只是把mFirstTouchTarget置null,下一個MOVE才會交由View:dispatchTouchEvent處理。如果攔截的只是UP事件,那就更加不可能交給View:dispatchTouchEvent處理了。
2、一個事件,如果子view處理失敗,是否就交還給父view處理?
只有mFirstTouchTarget為null,才交由parent處理。down事件肯定會給parent處理,其他就不一定了,還是看mFirstTouchTarget的值。
3、如果一個down事件,大家都不處理,會怎麼樣?
這個文中說的很詳細了,不停往上拋直到DecorView,返回false,然後MOVE和UP給了DecorView處理,DecorView攔下來返回false。相當於所有事件都丟棄了。
4、parent把事件傳遞給哪個子view呢?是根據位置查一遍的嗎?
down是根據位置查的,move和up是根據mFirstTouchTarget來處理的
本文提了3條規則,畫了三幅流程圖,寫了一段偽代碼,希望以後我遇到事件分發的問題,都能從這裡找到答案。
來自:https://developer.android.com/training/basics/intents/index.html 前言:我們都知道在APP
一、微博開發者平台的使用新浪微博這裡主要是介紹使用新浪微博的開發者平台。想要做一個基於微博登陸或者其他一系列操作的,我們先要登陸他們的官網進行注冊使用,首先是要注冊成為一
很多項目要用到圖片選擇控件,每次都要寫一大堆邏輯。於是基於圖片選擇組件(PhotoPicker)封裝了一個控件PhotoUploadView。方便簡易,一鍵集成,幾句代碼
一、Service的種類1.按運行地點分類: 類別 區別 優點 缺點 應用 本地服務 (Local) 該服務依附在主進程上 服務依附在主進程上而不是獨立