寫這篇文章實際上是有緣由的,說實話此次面試真的很失敗,看着身邊的人都拿到了高薪的工資,感受本身仍是有些慚愧。也更說明本身在不少方面的知識點仍是不夠紮實,因而再一次拿起了view的事件分發的源碼給看了一遍。java
android中事件分發機制是android中常見的問題,通常你們都知道view的分發事件是從view的Viewgroup(Parent)#dispatchTouchEvent
到Viewgroup(Parent)#onInterceptTouchEvent
再到View#dispatchTouchEvent
,而後到view的onTouchEvent
,最後又回到了Viewgroup(Parent)#onTouchEvent
。若是你們記不住方法名,能夠直接說先是parent的分發到攔截再到view的分發,再到view的消費,最後到parent的消費android
這樣回答確定是很淺顯的,由於沒有說出是否攔截、是否分發、是否消費的各類條件,沒有涉及到各類action的分發狀況,上面說的默認分發只是針對action_down的,由於view/viewgroup
各類super調用都是不進行分發、攔截、消費的,因此在沒找處處理touch事件的view時候,是一直往上層view傳遞的,一直傳到activity裏面,下面咱們再來整理一下:面試
若是viewgroup不進行分發,那麼
action_down
、action_move
和action_up
只會執行到viewgroup的dispatchTouchEvent
,不分發的條件是dispatchTouchEvent
直接返回true或false,true和false的區別是true會執行action_down
、action_move
和action_up
,而若是直接返回false只會執行到action_down。而且後續的viewgroup的onInterceptTouchEvent
後續方法都不會被執行到。bash
關於爲何view/Viewgroup的dispatchTouchEvent
返回true的時候三個action都能執行到,而返回false的話,只能執行到action_down,這個須要到view/Viewgroup的父類中dispatchTouchEvent
找答案,該方法中會在action_down的時候調用dispatchTransformedTouchEvent
方法,而該方法是經過子view的dispatchTouchEvent方法的返回值來決定父類的dispatchTransformedTouchEvent
方法的返回值,而dispatchTransformedTouchEvent
的返回值會決定mFirstTouchTarget
是否爲空,因此在action_down的過程當中實際中經過子view的dispatchTouchEvent
方法返回值來肯定mFirstTouchTarget
是否爲空。這裏貼出viewgroup中dispatchTransformedTouchEvent
方法的刪減代碼:學習
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
------------------
//省略了cancel部分的代碼
------------------------
//若是child爲空,直接調用本身的dispatchTouchEvent方法,此時本身就至關於一個view,touch事件走本身的
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
//返回值直接經過孩子來獲取返回值
handled = child.dispatchTouchEvent(transformedEvent);
}
transformedEvent.recycle();
return handled;
}
複製代碼
因此若是view/viewgroup的dispatchTouchEvent
方法返回false,表示在action_down的時候,父類的dispatchTransformedTouchEvent
方法返回false;若是返回true會調用addTouchTarget
方法,給mFirstTouchTarget
設置值:ui
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
複製代碼
緊接着在在後面又會調用了:spa
dispatchTouchEvent
返回false的時候,纔會走這裏,因此後面的
action_move
和
action_up
都會走這裏,而此時傳入的child=null,從上面代碼能夠看到,直接調用了父類的
dispatchTouchEvent
方法。因此從這裏不難看出在view/viewgroup的
dispatchTouchEvent
返回false的時候直接調用了父類的
dispatchTouchEvent
方法,所以只有action_down事件。
其實這道題考察你們對view的dispatchTouchEvent和view的onTouchEvent事件的處理流程,上面已經分析了想要view能執行到view的touch事件,那麼必需要求view的dispatchTouchEvent
返回true,而dispatchTouchEvent
返回true要麼是dispatchTouchEvent
直接返回true或者view的onTouchEvent
返回true。若是從效率上看,直接將dispatchTouchEvent
返回true就ok,而不須要再去關心onTouchEvent
方法。設計
關於攔截無非就是攔截或不攔截,而攔截的條件是返回true,不攔截是返回false或返回super.onInterceptTouchEvent,默認的super是返回false的,所以能夠用super表示不攔截日誌
viewgroup攔截實際是經過在dispatchTouchEvent
方法中,設置intercepted變量,若是在攔截方法裏面返回true,那麼intercepted爲true,若是爲true則在action_down的時候mFirstTouchTarget=null,那麼此時是直接調用dispatchTransformedTouchEvent
傳入的child=null,所以將事件交給了super.dispatchTouchEvent
,此時把它當成一個view來處理了。code
先貼出事例代碼:
testView在testViewgroup裏面,testViewgroup在action_move的時候攔截(onInterceptTouchEvent在move返回true),testView不進行分發(dispatchTouchEvent返回true) 咋們經過log來看結果:
mFirstTouchTarget
不爲空,緊接着在action_move的時候進行了攔截,則intercepted=true,
既然在move過程當中肯定了intercepted=true,mFirstTouchTarget不爲空,則能夠看viewgroup.dispatchTouchEvent部分代碼:
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
//alreadyDispatchedToNewTouchTarget=false
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
//因爲move過程當中intercepted=true,則cancelChild=true
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//看到了沒這裏就是觸發child的dispatchTouchEvent的action_cancel
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
//因爲next=null,所以mFirstTouchTarget=null,因此在action_move剛進來的時候mFirstTouchTarget=null了,
//待會咱們經過反射看下該變量
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
複製代碼
上面也說明了在action_move進來的時候先是觸發了testView#dispatchTouchEvent的action_cancel,緊接着mFirstTouchTarget
=null了,因爲mFirstTouchTarget
是viewgroup類中私有的變量,咱們能夠經過反射調用該變量看下是否爲空:
//反射代碼,關於反射能夠看我以前的文章java反射整理
mFirstTouchTarget
屬性:
經過上面能夠驗證剛纔move過程當中mFirstTouchTarget
爲空的判斷,日誌以下:
mFirstTouchTarget
還不是null,第二次move的時候就是null了,所以在後續的move和up過程當中,只會此處:
因此針對上面面試官提的問題,你們知道怎麼說了吧,仍是針對該問題作個小結:
先是down事件會通過viewgroup的dispatchTouchEvent,再到viewgroup的onInterceptTouchEvent,最後到view的dispatchTouchEvent,此時mFirstTouchTarget不爲空,緊接着到了move首先到viewgroup的dispatchTouchEvent,再到viewgroup的onInterceptTouchEvent,因爲在move過程當中攔截了,緊接着走view的dispatchTouchEvent的action_cancel,此時接着把mFirstTouchTarget至爲null,所以後續的move和up事件只會走viewgroup的dispatchTouchEvent和onTouchEvent。 畫出一張圖來給你們看下:
好了,關於這個問題告一段落了,若是分析有問題,你們能夠提出疑問。
其實這個問題在上面分析中已經分析過了,testview的onTouchEvent中消費,因此在action_down中mFirstTouchTarget
不爲空,所以在action_move和action_up中mFirstTouchTarget
仍是不爲空,因此無論手指是否已經離開了testview,action_move和action_up仍是會走testview的dispatchTouchEvent和onTouchEvent。
首先肯定action_down過程當中mFirstTouchTarget
是否爲空,若是不爲空,因此無論手指是否已經不在testView上了,action_move和action_up仍是會在testView的onTouchEvent上進行消費的。
這個問題就沒涉及viewgroup到view的事件傳遞,onTouch指setOnTouchListener的回調方法,它是優先於onTouchEvent事件的,你們能夠看下view的dispatchTouchEvent中有以下代碼:
onClick事件是在onTouchEvent消費事件中的action_up觸發的,onTouch是在dispatchTouchEvent中觸發的,因此onTouch要先於onClick事件,咱們也能夠經過onTouch返回true來屏蔽掉onClick事件。
好了,關於此次我面試中遇到的事件分發主要是上面這幾個問題,你們有什麼其餘的問題,能夠在評論區互動。
其實android事件分發核心是在
viewgroup
的dispatchTouchEvent
的action_down
過程當中找到mFirstTouchTarget
是否爲空,經過反序遍歷子view的dispatchTouchEvent的方法,若是發現有一個子view的dispatchTouchEvent
方法返回true,那麼mFirstTouchTarget
就不爲空,不然爲空。若是mFirstTouchTarget
不爲空,那麼action_move
和action_up
纔會往下傳遞,若是在action_move
和action_up
過程當中有viewgroup攔截了事件,則此時先向子view的dispatchTouchEvent
傳遞一個action_cancel
,而且將mFirstTouchTarget
至爲null,因此此時action_move
和action_up
只會走viewgroup
的dispatchTouchEvent
和onTouchEvent
;若是mFirstTouchTarget
在action_down
過程當中就已經null的話,則從action_down
一直向上層view傳遞,不會有後續的action_move
和action_up
了。
其實看到身邊不少朋友抱怨本身的工資很低,包括筆者也是同樣的,其緣由是在面試過程當中沒有給面試官一個很好的答案。因此筆者會持續更新面試過程當中遇到的問題,也但願你們和筆者一塊兒進步,一塊兒學習。好了,此次文章就到這裏,有什麼問題,歡迎你們互動。