.net 怎么在控制器action中返回一个试图_一个view事件分发,面试官6连问直击灵魂,我被虐的体无完肤...
注:原文來自掘金作者xiangcman
寫這篇文章其實是有原因的,說實話這次面試真的很失敗,看著身邊的人都拿到了高薪的工資,感覺自己還是有些慚愧。也更說明自己在很多方面的知識點還是不夠扎實,于是再一次拿起了view的事件分發的源碼給看了一遍。
面試官:說說view中的事件分發?
android中事件分發機制是android中常見的問題,一般大家都知道view的分發事件是從view的Viewgroup(Parent)#dispatchTouchEvent到Viewgroup(Parent)#onInterceptTouchEvent再到View#dispatchTouchEvent,然后到view的onTouchEvent,最后又回到了Viewgroup(Parent)#onTouchEvent。如果大家記不住方法名,可以直接說先是parent的分發到攔截再到view的分發,再到view的消費,最后到parent的消費
viewgroup分發
這樣回答肯定是很淺顯的,因為沒有說出是否攔截、是否分發、是否消費的各種條件,沒有涉及到各種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后續方法都不會被執行到。
關于為什么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設置值:
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) { final TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; mFirstTouchTarget = target; return target;}緊接著在在后面又會調用了:
這句只有在view/viewgroup的dispatchTouchEvent返回false的時候,才會走這里,所以后面的action_move和action_up都會走這里,而此時傳入的child=null,從上面代碼可以看到,直接調用了父類的dispatchTouchEvent方法。所以從這里不難看出在view/viewgroup的dispatchTouchEvent返回false的時候直接調用了父類的dispatchTouchEvent方法,因此只有action_down事件。
面試官:如果我只想有view的拖拽事件,而不想要view的點擊事件,讓你重寫這個view的拖拽怎么設計
其實這道題考察大家對view的dispatchTouchEvent和view的onTouchEvent事件的處理流程,上面已經分析了想要view能執行到view的touch事件,那么必須要求view的dispatchTouchEvent返回true,而dispatchTouchEvent返回true要么是dispatchTouchEvent直接返回true或者view的onTouchEvent返回true。如果從效率上看,直接將dispatchTouchEvent返回true就ok,而不需要再去關心onTouchEvent方法。
viewgroup攔截
關于攔截無非就是攔截或不攔截,而攔截的條件是返回true,不攔截是返回false或返回super.onInterceptTouchEvent,默認的super是返回false的,因此可以用super表示不攔截
viewgroup攔截實際是通過在dispatchTouchEvent方法中,設置intercepted變量,如果在攔截方法里面返回true,那么intercepted為true,如果為true則在action_down的時候mFirstTouchTarget=null,那么此時是直接調用dispatchTransformedTouchEvent傳入的child=null,因此將事件交給了super.dispatchTouchEvent,此時把它當成一個view來處理了。
面試官:有個viewgroup,里面有個view,如果view在dispatchTouchView中不分發事件,并且只在action_move中攔截touch事件向下分發,說說viewgroup到view的各個action是如何分發的?
分析:
先貼出事例代碼:
testView在testViewgroup里面,testViewgroup在action_move的時候攔截(onInterceptTouchEvent在move返回true),testView不進行分發(dispatchTouchEvent返回true) 咱們通過log來看結果:
這里執行到TestViewgroup#dispatchTouchEvent的action_move之后就執行了TestView#dispatchTouchEvent的action_cancel,然后后面執行TestViewgroup#dispatchTouchEvent和TestViewgroup#onTouchEvent的action_move和action_up。 從前面viewgroup的dispatchTouchEvent分析知道,如果viewgroup在action_down中發現有子view的dispatchTouchEvent返回true,則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反射整理
接著在testViewgroup#dispatchTouchEvent中獲取mFirstTouchTarget屬性:
通過上面可以驗證剛才move過程中mFirstTouchTarget為空的判斷,日志如下:
看到了沒,第一次move的時候mFirstTouchTarget還不是null,第二次move的時候就是null了,因此在后續的move和up過程中,只會此處:
看到了沒,這里傳進去的child=null,根據上面分析可知,當為null的時候,只會觸發super.dispatchTouchEvent,所以到了第二次的move之后,只能看到TestViewgroup的action_move和action_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。 畫出一張圖來給大家看下:
好了,關于這個問題告一段落了,如果分析有問題,大家可以提出疑問。
面試官:里面的view在onTouchEvent中消費,然后拖動手指,直到手指從其他他viewgroup上挪開手指。
其實這個問題在上面分析中已經分析過了,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上進行消費的。
面試官:view的onTouch和onTouchEvent事件的區別
這個問題就沒涉及viewgroup到view的事件傳遞,onTouch指setOnTouchListener的回調方法,它是優先于onTouchEvent事件的,大家可以看下view的dispatchTouchEvent中有如下代碼:
我想這個地方不用多說吧,如果onTouch方法返回true,是不會觸發onTouchEvent事件的,所以在開篇第二個問題:如果想屏蔽掉view的點擊事件,只想要view的拖拽事件,該怎么處理,其實這里完全可以重寫setOnTouchListener的onTouch方法,并且onTouch里面返回true就會屏蔽掉onClickListener事件。
面試官:view的onClick事件在什么時候觸發的,和onTouch有什么區別
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了。
文末
不管怎么樣,不論是什么樣的大小面試,要想不被面試官虐的不要不要的,只有刷爆面試題題做好全面的準備,當然除了這個還需要在平時把自己的基礎打扎實,這樣不論面試官怎么樣一個知識點里往死里鑿,你也能應付如流啊~
小編將自己6年以來的面試經驗和學習筆記都整理成了一個937頁的PDF,以及我學習進階過程中看過的一些優質視頻教程。如有需要,關注我后私信【面試】即可無償分享
其實看到身邊很多朋友抱怨自己的工資很低,包括筆者也是一樣的,其原因是在面試過程中沒有給面試官一個很好的答案。所以筆者會持續更新面試過程中遇到的問題,也希望大家和筆者一起進步,一起學習。
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的.net 怎么在控制器action中返回一个试图_一个view事件分发,面试官6连问直击灵魂,我被虐的体无完肤...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: app应用内嵌h5页面怎么直接打开saf
- 下一篇: envi中的sg滤波_ENVI滤波