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