簡(jiǎn)記:Touch事件處理

上周面試被問到這個(gè)自己覺得簡(jiǎn)單不會(huì)問的問題[哭],回答邏輯混亂,這里看一復(fù)習(xí)一遍。。

參考:http://blog.csdn.net/lmj623565791/article/details/38960443

參考文章中已經(jīng)描述的很清楚,這里僅記錄下要點(diǎn)加深映像。

View事件處理

首先進(jìn)入dispatchTouchEvent方法

1.View是在dispatchTouchEvent方法中處理touch事件的,并且是由ViewGroup在dispatchTransformedTouchEvent(..., View child, ...)方法中調(diào)用。

2.dispatchTouchEvent中優(yōu)先判斷是否設(shè)置了touchListener,如果設(shè)置了并且onTouch方法返回true,則不執(zhí)行后續(xù)的onTouchEvent方法,且dispatchTouchEvent方法返回true表當(dāng)前view要消費(fèi)這個(gè)事件。 ? 如果沒有設(shè)置onTouchListener或者onTouch方法返回false,則調(diào)用view自身的onTouchEvent方法,并且dispatchTouchEvent方法返回onTouchEvent方法的返回值。

進(jìn)入onTouchEvent方法

3.一開始直接判斷當(dāng)前view如果是disable的并且是clickable或longClickable或contentClickable的,直接返回true.表示當(dāng)前view消費(fèi)事件但是并不執(zhí)行任務(wù)操作。

4.接著判斷如果touchDelegate不為空并且onTouch方法返回true,則onTouchEvent直接返回true.

5.然后在判斷如果當(dāng)前view是clickable或者longClickable或者contentClickable的,直接返回true,否則直接返回false。這說明不管當(dāng)前view有沒有設(shè)置各種click的listener,只要是各種clickable的就會(huì)消費(fèi)當(dāng)前事件。

6.ACTION_DOWN時(shí),先看代碼

a.首先會(huì)判斷當(dāng)前view是否在一個(gè)可以滾動(dòng)的viewgroup中,isInScrollingContainer方法會(huì)遞歸往父級(jí)找,直到找到某一級(jí)viewgroup的shouldDelayChildPressedState方法返回true(ViewGroup類中此方法默認(rèn)返回true),表示需要延遲一下child的pressed狀態(tài)設(shè)置(通過mPendingCheckForTap),然后通過post一個(gè)CheckForTap的runnable來延遲設(shè)置pressed狀態(tài)(如注釋描述,這樣做為了避免當(dāng)前用戶是要進(jìn)行scroll操作而不是click),并且這里會(huì)把當(dāng)前狀態(tài)設(shè)置為prepressed狀態(tài)。 ?否則如果不在一個(gè)scrollingContainer中則直接改變當(dāng)前view的狀態(tài)為pressed并且調(diào)用checkForLongClick方法post一個(gè)CheckForLongPress的runnable對(duì)象。

b.看下CheckForTab

首先把prepressed狀態(tài)清除,狀態(tài)調(diào)用setPressed方法設(shè)置為pressed狀態(tài),其中會(huì)根據(jù)press的狀態(tài)改變?cè)O(shè)置drawable,并且會(huì)調(diào)用dispatchSetPressed(boolean pressed)方法。然后在調(diào)checkForLongClick方法post一個(gè)longClick處理的runnable,并且這里會(huì)用longClick的生效時(shí)間longPressTimeout500減掉從down事件到現(xiàn)在delay的tapTimeout100.

c.在看CheckForLongPress

從down開始過了500ms后checkForLongPress還沒有被remove掉則會(huì)調(diào)用處理longClick事件。如果當(dāng)前是pressed狀態(tài)并且parent為不空,則會(huì)調(diào)用performLongClick方法,其中會(huì)調(diào)用到onLongClickListener的onLongClick,并且如果onLongClick返回true則mHasPerformedLongPress會(huì)被設(shè)置為true.這個(gè)變量在up時(shí)會(huì)用來判斷是否需要執(zhí)行click.

7.的回到onTouchEvent的ACTION_MOVE

這里僅是判斷當(dāng)前move的位置是否在當(dāng)前view內(nèi),如果不在則調(diào)用removeTapCallback,清除prepressed狀態(tài)和remove掉checkForTap的runnable,不管從down到現(xiàn)在是否已經(jīng)超過100ms(如果超過了也不會(huì)有影響)。如果當(dāng)前是pressed狀態(tài)則把checkForLongPress也remove掉并且取消pressed狀態(tài)。

經(jīng)過上面的兩個(gè)remove之后,如果從down到現(xiàn)在還不到500,那么根據(jù)Up中開頭上面的判斷,當(dāng)前即不會(huì)是prepressed也不會(huì)是pressed狀態(tài),所以手指只要move出當(dāng)前view的范圍過,那么click事件就不會(huì)在觸發(fā)了。如果已經(jīng)超過500,longClick已經(jīng)觸發(fā),那么move只會(huì)影響click的觸發(fā),不管longClickListener是否存在或者onLongClick返回值是什么。

8.然后是CANCEL事件

正常情況下不會(huì)收到cancel事件,而且也只會(huì)在當(dāng)前view之前已經(jīng)消費(fèi)了donw事件后才有可能收到,有時(shí)候父級(jí)控件中有可能需要阻斷事件的傳遞自己消費(fèi)(即使之前的donw事件已經(jīng)由子view消費(fèi)),這時(shí)候父級(jí)控件就會(huì)給之前消費(fèi)了down事件的子view一個(gè)cancel事件讓子view恢復(fù)之前事件設(shè)置的各種狀態(tài)。

9.最后是UP事件

a.如果當(dāng)前是prepressed狀態(tài)(100ms前),直接設(shè)置為pressed.

b.如果mHasPerformedLongPress為false,可能是沒有到達(dá)500ms或者到達(dá)了但是沒有LongClickListener或onLongClick返回false.則會(huì)處理click事件。

c.通過UnsetPressedState來把當(dāng)前view的pressed狀態(tài)設(shè)置為false.這里如果是還不到100ms會(huì)delayPost 64ms清除的操作,猜測(cè)是因?yàn)閺膁own到up如果還不到100ms(checkForTab沒有執(zhí)行),就在up中手動(dòng)設(shè)置了pressed為true,為了讓view從pressed到unpressed有一小段時(shí)間間隔(如為了drawable展示切換能被看到)才加的。

d.最后調(diào)用removeTapCallback()把checkForTap檢查remove掉。這里之前老是想為什么要放到最后執(zhí)行,萬一在onClick中執(zhí)行時(shí)間過長(zhǎng),在執(zhí)行到這句代碼前checkForTap的runnable初始執(zhí)行了怎么辦,后來想起來都是在主線程執(zhí)行的,不會(huì)存在這種問題,放到最前和最后都是一樣的。

ViewGroup事件處理

首先入口也是dispatchTouchEvent方法(重寫了View的方法),其次是在PhoneWindow中被調(diào)用的

這里不在往上查是哪兒調(diào)的phonewindow,著重分析ViewGroup的dispatchTouchEvent,代碼很長(zhǎng),分塊講解。

1.如果當(dāng)前是DOWN事件,進(jìn)行初始化設(shè)置。

如注釋描述,可能由于app的切換,ANR或者其它狀態(tài)變化的原因?qū)е耭ramework把上一次手勢(shì)的UP或者CANCEl事件丟掉了,從而導(dǎo)致在上一次手勢(shì)中設(shè)置的各種狀態(tài)沒有處理完,如果不做初始化會(huì)使得這次手勢(shì)處理混亂。

這個(gè)方法中的mFirstTouchTarget代表的是上次手勢(shì)流程中消費(fèi)所有touch事件的view。這里初始化會(huì)通過dispatchTransformedTouchEvent方法給這個(gè)view分發(fā)一個(gè)cancel事件來讓它自己初始化它由上一次手勢(shì)設(shè)置的各種狀態(tài)。然后通過clearTouchTargets把它回收掉并置空(后面需要重新找到消費(fèi)這次手勢(shì)流程的touchTarget)。

2.判斷是否需要intercept事件傳遞。

a.首先由于上一步的原因,這里外層if中的兩個(gè)條件肯定最多只會(huì)有一個(gè)為true.

b.如果兩個(gè)都為false,說明當(dāng)前不是初始的down事件而且之前的down事件也沒有找到需要消費(fèi)的target,那么直接就在else中把intercepted設(shè)為true代表直接阻斷不用在去子view中找需要消費(fèi)事件的view.

c.如果當(dāng)前為down事件(當(dāng)前還沒有消費(fèi)事件的mFirstTouchTarget)或者mFirstTouchTarget不為空(說明當(dāng)前不是down事件,且之前在down時(shí)已經(jīng)找到了消費(fèi)這輪手勢(shì)的target),就需要判斷下是否需要阻斷事件。判斷是根據(jù)是否可以阻斷(disallowIntercept為true代表不能阻斷)和是否需要阻斷(onInterceptTouchEvent返回true代表需要阻斷,默認(rèn)false)來決定的。


剛剛查了下簡(jiǎn)書原來可以設(shè)置為markdown編輯器的。。無知如我。。但是只能新建文章才能用。。這篇就將就下繼續(xù)貼圖吧。。

3.查找消費(fèi)手勢(shì)的子view

僅在當(dāng)前事件為down并且沒有被intercepted時(shí)才會(huì)執(zhí)行查找操作(多指觸摸不分析了。。)。各種判斷不貼了

如注釋描述,從前到后掃描子view,找出可以接收事件的子view。這里有兩個(gè)接口會(huì)影響掃描子view時(shí)的順序,一個(gè)是buildTouchDispatchChildList(),通過構(gòu)造一個(gè)接收事件分發(fā)的子view列表,它里面是根據(jù)子view中的z(和x,y對(duì)應(yīng))屬性和自定義的drawingorder來構(gòu)造的。另外一種影響順序的方式就是自定義的drawingorder了。

遍歷子view列表,判斷每個(gè)子view是否可以接收觸摸事件,看不到的view或者正在執(zhí)行動(dòng)畫的子view不能接收。并且判斷觸摸點(diǎn)是否在子view的范圍內(nèi)。

接著通過dispatchTransformedTouchEvent方法,把事件通過子view的dispatchTouchEvent方法傳遞下去,讓子view自己決定是否需要消費(fèi)事件。如果子view消費(fèi)了事件就會(huì)把child通過addTouchTarget方法設(shè)置為之前見過的mFirstTouchTarget(重申這里不考慮多指觸摸的情況)。然后直接break跳出查找。到這就找到新的需要消費(fèi)downg事件的newTouchTarget.

4.其它

1.mFirstTouchTarget == null有三種情況。一種是down事件時(shí)查找消費(fèi)事件的子view沒找到,這時(shí)直接調(diào)dispatchTransformedTouchEvent,因?yàn)閭鞯腸hild為空,這時(shí)會(huì)直接調(diào)super.dispatchTouchEvent方法,處理的方式就和上面講的View的事件處理一樣了。另一種情況是還是在down事件但是并沒有查找子view(被intercepted了)。第三種是當(dāng)前不是down事件并且在之前的down事件中沒有消費(fèi)事件的子view(可能是沒找到也可能是viewgroup自己處理了),這里能接收到非down事件說明之前的down事件中這個(gè)viewgroup的dispatchtouchevent方法肯定返回的true.

2.alreadyDispatchedToNewTouchTarget為true是之前down事件查找消費(fèi)的子view時(shí)子view返回true.代表已經(jīng)處理過down事件,這里就不用在執(zhí)行一次了。

3.這里是處理兩種情況的,一種是viewgroup想要intercept,所以會(huì)給子view分發(fā)一個(gè)cancel的事件。另一種是把當(dāng)前事件(非down事件)分發(fā)給子view。這里我有點(diǎn)疑惑的是第一種情況下,viewgroup想intercept,在給子view分發(fā)一個(gè)cancel事件后并沒有在調(diào)用super.dispatchTouchEvent來自己處理當(dāng)前事件

總結(jié)

差不多就這樣了。

1.首先入口都是dispatchTouchEvent方法。

2.viewgroup可以阻斷,子view也可以通過requestDisallowInterceptTouchEvent方法設(shè)置不讓parent阻斷,并且這個(gè)設(shè)置在每次手勢(shì)流程結(jié)束后(不管是UP還是Cancel)都會(huì)初始重置。

3.viewgroup如果阻斷了子view消費(fèi)事件會(huì)給子view一個(gè)cancel.

4.只有在down是viewgroup才會(huì)查找子view中需要消費(fèi)事件的,如果之前已經(jīng)找到就直接把事件分發(fā)給它。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容