onInterceptTouchEvent用于改變事件的傳遞方向瑞信。決定傳遞方向的是返回值穴豫,返回為false時(shí)事件會(huì)傳遞給子控件逼友,返回值為true時(shí)事件會(huì)傳遞給當(dāng)前控件的onTouchEvent()帜乞,這就是所謂的Intercept(攔截)筐眷。
[tisa ps:正確的使用方法是,在此方法內(nèi)僅判斷事件是否需要攔截照棋,然后返回武翎。即便需要攔截也應(yīng)該直接返回true,然后由onTouchEvent方法進(jìn)行處理符隙。]
onTouchEvent用于處理事件垫毙,返回值決定當(dāng)前控件是否消費(fèi)(consume)了這個(gè)事件。尤其對(duì)于ACTION_DOWN事件,返回true欺栗,表示我想要處理后續(xù)事件迟几;返回false,表示不關(guān)心此事件类腮,并返回由父類進(jìn)行處理蚜枢。
可能你要問是否消費(fèi)了又區(qū)別嗎,反正我已經(jīng)針對(duì)事件編寫了處理代碼厂抽?答案是有區(qū)別筷凤!比如ACTION_MOVE或者ACTION_UP發(fā)生的前提是一定曾經(jīng)發(fā)生了ACTION_DOWN苞七,如果你沒有消費(fèi)ACTION_DOWN挪丢,那么系統(tǒng)會(huì)認(rèn)為ACTION_DOWN沒有發(fā)生過,所以ACTION_MOVE或者ACTION_UP就不能被捕獲惠啄。
在沒有重寫onInterceptTouchEvent()和onTouchEvent()的情況下(他們的返回值都是false),?對(duì)上面這個(gè)布局巢块,MotionEvent事件的傳遞順序如下:
當(dāng)某個(gè)控件的onInterceptTouchEvent()返回值為true時(shí)族奢,就會(huì)發(fā)生截?cái)啵录粋鞯疆?dāng)前控件的onTouchEvent()越走。如我們將LayoutView2的onInterceptTouchEvent()返回值為true,則傳遞流程變成:
如果我們同時(shí)將LayoutView2的onInterceptTouchEvent()和onTouchEvent()設(shè)置成true廊敌,那么LayoutView2將消費(fèi)被傳遞的事件,同時(shí)后續(xù)事件(如跟著ACTION_DOWN的ACTION_MOVE或者ACTION_UP)會(huì)直接傳給LayoutView2的onTouchEvent(),不傳給其他任何控件的任何函數(shù)锅纺。同時(shí)傳遞給子空間一個(gè)ACTION_CANCEL事件肋殴。傳遞流程變成(圖中沒有畫出ACTION_CANCEL事件):
[tisa ps:總體來看护锤,?onInterceptTouchEvent是自rootview向下傳遞, onTouchEvent正好相反。]
正如命名一樣驱入,onInterceptTouchEvent用來攔截事件氯析,onTouchEvent用來處理事件,網(wǎng)上大部分的文章中也都對(duì)這兩類方法的使用情況進(jìn)行了簡(jiǎn)單的說明宴杀。
事件傳遞如下圖拾因,逐級(jí)向下看onInterceptTouchEvent()是否需要截?cái)嗍录跤啵绻麤]發(fā)生截?cái)啾獯铮瑒t逐級(jí)向上尋找能夠處理該事件的onTouchEvent()跪解。
源碼上RelativeLayout等布局是繼承ViewGroup,看源碼上ViewGroup中的onInterceptTouchEvent卻非常簡(jiǎn)單:
publicbooleanonInterceptTouchEvent(MotionEvent?ev)?{
returnfalse;
}
如果不重寫該方法直接就會(huì)return false窘行。
如果在其中一層截?cái)嗍录疾郑宱nInterceptTouchEvent()返回true,例如在上圖中ChildLayout中發(fā)生截?cái)嗷炭矗瑒t事件傳遞則會(huì)向跳過MyView六孵,直接從這一層進(jìn)入onTouchEvent。若ChildLayout的onTouchEvent返回false本今,則傳遞流程如下圖
很重要的一點(diǎn):事件傳遞是這樣的冠息,你可以在傳遞到響應(yīng)的方法里面做任何你想做的事情煤禽,這些事情不會(huì)改變事件傳遞岖赋,能影響事件傳遞的是這些方法的返回值。例如你在ChildLayout的onTouchEvent做了很多事情选脊,只要返回了false脸甘,事件還是會(huì)進(jìn)入到ParentLayout的onTouchEvent。
若保持ChildLayout的onInterceptTouchEvent返回true钝的,將它的onTouchEvent返回true,則事件不會(huì)再進(jìn)入其他組件的onTouchEvent沿猜,后續(xù)的時(shí)間會(huì)依次進(jìn)入這個(gè)返回了true的onTouchEvent碗脊,傳遞則變?yōu)槿缦聢D:
自此說的內(nèi)容都跟博客開頭那個(gè)鏈接里面的文章類似。下面寫一些那篇文章里面沒有談及或者不夠全面的地方祈坠。下文中舉例進(jìn)行的操作都是拖動(dòng)矢劲,即ACITON_DOWN -> ACTION_MOVE -> ACTION_UP
那篇文章里面說在事件發(fā)生截?cái)嗟臅r(shí)候,會(huì)像子View發(fā)出ACTION_CANCEL另绩,但是我在ChildLayout的onInterceptTouchEvent里面return true之后花嘶,卻沒有收到ACTION_CANCEL,經(jīng)實(shí)驗(yàn)發(fā)現(xiàn)车海,談?wù)勱P(guān)于ACTION_CANCEL的事情隘击。ACTION_CANCEL的發(fā)出是有條件的:如果子一層曾經(jīng)處理過事件,即事件進(jìn)入到onTouchEvent中州叠,則此時(shí)截?cái)嗍录琢蓿弦粚訒?huì)向下一層發(fā)出ACTION_CANCEL。例如在ChildLayout的onInterceptTouchEvent中對(duì)event.getAction()進(jìn)行判斷致板,如果是ACTION_DOWN咏窿,返回false集嵌,在ACTION_MOVE的時(shí)候御毅,返回true進(jìn)行截?cái)嗥秸丁t可以發(fā)現(xiàn)MyView接到了action為ACTION_DOWN的事件,但是當(dāng)出現(xiàn)ACTION_MOVE在ChildLayout截?cái)喟l(fā)生時(shí)欺税,MyView則收到了ACTION_CANCEL的消息揭璃。代碼如下:
ChildLayout
@Override
publicbooleanonInterceptTouchEvent(MotionEvent?ev)?{
booleanresult?=super.onInterceptTouchEvent(ev);
switch(ev.getAction())?{
caseMotionEvent.ACTION_DOWN:
Log.i("ZZZZ","ChildLayout?onInterceptTouchEvent?ACITON_DOWN:");
break;
caseMotionEvent.ACTION_MOVE:
Log.i("ZZZZ","ChildLayout?onInterceptTouchEvent?ACITON_MOVE:");
result?=true;
break;
caseMotionEvent.ACTION_CANCEL:
Log.i("ZZZZ","ChildLayout?onInterceptTouchEvent?ACITON_CANCEL:");
break;
caseMotionEvent.ACTION_UP:
Log.i("ZZZZ","ChildLayout?onInterceptTouchEvent?ACITON_UP:");
break;
}
Log.i("ZZZZ","ChildLayout?onInterceptTouchEvent?return?"+result);
returnresult;
}
@Override
publicbooleanonTouchEvent(MotionEvent?ev)?{
booleanresult?=super.onTouchEvent(ev);
switch(ev.getAction())?{
caseMotionEvent.ACTION_DOWN:
Log.i("ZZZZ","ChildLayout?onTouchEvent?ACTION_DOWN");
break;
caseMotionEvent.ACTION_MOVE:
Log.i("ZZZZ","ChildLayout?onTouchEvent?ACTION_MOVE");
break;
caseMotionEvent.ACTION_CANCEL:
Log.i("ZZZZ","ChildLayout?onTouchEvent?ACTION_CANCEL");
caseMotionEvent.ACTION_UP:
Log.i("ZZZZ","ChildLayout?onTouchEvent?ACTION_UP");
break;
}
result?=true;
Log.i("ZZZZ","ChildLayout?onTouchEvent?return?"+result);
returnresult;
}
為了增加事件的復(fù)雜性瘦馍,挑選了有滾動(dòng)效果的GridView在ChildLayout下一層的MyView作為實(shí)驗(yàn)對(duì)象
MyGridView
@Override
publicbooleanonInterceptTouchEvent(MotionEvent?ev)?{
booleanresult?=super.onInterceptTouchEvent(ev);
switch(ev.getAction())?{
caseMotionEvent.ACTION_DOWN:
Log.d("ZZZZ","MyGridView?onInterceptTouchEvent?ACITON_DOWN:");
break;
caseMotionEvent.ACTION_MOVE:
Log.d("ZZZZ","MyGridView?onInterceptTouchEvent?ACITON_MOVE:");
break;
caseMotionEvent.ACTION_CANCEL:
Log.d("ZZZZ","MyGridView?onInterceptTouchEvent?ACITON_CANCEL:");
break;
caseMotionEvent.ACTION_UP:
Log.d("ZZZZ","MyGridView?onInterceptTouchEvent?ACITON_UP:");
break;
}
Log.d("ZZZZ","MyGridView?onInterceptTouchEvent?return?"+result);
returnresult;
}
@Override
publicbooleanonTouchEvent(MotionEvent?ev)?{
booleanresult?=super.onTouchEvent(ev);
switch(ev.getAction())?{
caseMotionEvent.ACTION_DOWN:
Log.d("ZZZZ","MyGridView?onTouchEvent?ACTION_DOWN");
break;
caseMotionEvent.ACTION_MOVE:
Log.d("ZZZZ","MyGridView?onTouchEvent?ACTION_MOVE");
break;
caseMotionEvent.ACTION_CANCEL:
Log.d("ZZZZ","MyGridView?onTouchEvent?ACTION_CANCEL");
break;
caseMotionEvent.ACTION_UP:
Log.d("ZZZZ","MyGridView?onTouchEvent?ACTION_UP");
break;
}
Log.d("ZZZZ","MyGridView?onTouchEvent?return?"+result);
returnresult;
運(yùn)行日志:
再次修改代碼情组,取消ChildLayout里onInterceptTouchEvent對(duì)事件的攔截返回false院崇,讓MyGridView的onTouchEvent方法return super.onTouchEvent(ev)默認(rèn)返回true。
在同樣嘗試拖動(dòng)效果的時(shí)候底瓣,按照上面的說法捐凭,理論上在ParentLayout,ChildLayout茁肠,MyGridView都不攔截事件return false的狀態(tài)下垦梆,事件會(huì)通過依次通過ParentLayout,ChildLayout奶赔,MyGridView的onInterceptTouchEvent杠氢,然后到達(dá)MyGridView的onTouchEvent鼻百,但是我們發(fā)現(xiàn)事實(shí)并不是如此摆尝,移動(dòng)一段距離后因悲,就沒有任何onInterceptTouchEvent執(zhí)行了。運(yùn)行效果如下:
說好的依次傳遞并沒有發(fā)生讯检,無奈只能去源碼中尋找原因卫旱,GridView繼承于AbsListView,與之前ChildLayout投放,ParentLayout繼承與ViewGroup不同适贸,在AbsListView中的onTouchEvent的ACTION_MOVE的這個(gè)case中看到有個(gè)startScrollIfNeeded方法,點(diǎn)進(jìn)去才發(fā)現(xiàn)有方法說明“Check?if?we?have?moved?far?enough?that?it?looks?more?like?a?scroll?than?a?tap”烙样,?在我們tap屏幕的時(shí)候有一段距離touchSlop砾隅,小于這個(gè)距離的ACTION_MOVE是會(huì)被判定成為tap效果的,所以在這段源碼里面能看到
if(overscroll?||?distance?>?mTouchSlop)?{
createScrollingCache();
if(overscroll)?{
mTouchMode?=?TOUCH_MODE_OVERSCROLL;
mMotionCorrection?=0;
}else{
mTouchMode?=?TOUCH_MODE_SCROLL;
mMotionCorrection?=?deltaY?>0??mTouchSlop?:?-mTouchSlop;
}
finalHandler?handler?=?getHandler();
//?Handler?should?not?be?null?unless?the?AbsListView?is?not?attached?to?a
//?window,?which?would?make?it?very?hard?to?scroll?it...?but?the?monkeys
//?say?it's?possible.
if(handler?!=null)?{
handler.removeCallbacks(mPendingCheckForLongPress);
}
setPressed(false);
View?motionView?=?getChildAt(mMotionPosition?-?mFirstPosition);
if(motionView?!=null)?{
motionView.setPressed(false);
}
reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
//?Time?to?start?stealing?events!?Once?we've?stolen?them,?don't?let?anyone
//?steal?from?us
finalViewParent?parent?=?getParent();
if(parent?!=null)?{
parent.requestDisallowInterceptTouchEvent(true);
}
scrollIfNeeded(y);
returntrue;
}
里面有代碼,對(duì)parent判空了執(zhí)行requestDisallowInterceptTouchEvent(true);看到注釋則說明的很清楚了儒洛,如果滿足了條件,被判定成為looks more like a scroll than a tap卦停,則start stealing events, once we've stolen them, don't let anyone steal from us恼蓬,好傲嬌的樣子,直接屏蔽了各種有可能阻截這些事件的情況小槐,然后能夠阻截這個(gè)MotionEvent的就只有onInterceptTouchEvent了,這也就是為什么在有GridView的情況下件豌,"ACTION_DOWN -> ACTION_MOVE -> ACTION_UP"操作一小距離之后卻看不到任何onInterceptTouchEvent被執(zhí)行的原因了
參考:http://blog.csdn.net/guitk/article/details/7057155
http://waynehu16.iteye.com/blog/1926741