Android中dispatchTouchEvent, onInterceptTouchEvent, onTouchEvent的理解

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)單:

Java代碼

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

Java代碼

@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

Java代碼

@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效果的,所以在這段源碼里面能看到

Java代碼

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末茧彤,一起剝皮案震驚了整個(gè)濱河市疆栏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌壁顶,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件险污,死亡現(xiàn)場(chǎng)離奇詭異蛔糯,居然都是意外死亡窖式,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門淮逻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來阁簸,“玉大人,你說我怎么就攤上這事筛严∪拿祝” “怎么了?”我有些...
    開封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵照瘾,是天一觀的道長(zhǎng)丧慈。 經(jīng)常有香客問我,道長(zhǎng)碳却,這世上最難降的妖魔是什么笑旺? 我笑而不...
    開封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任筒主,我火速辦了婚禮,結(jié)果婚禮上乌妙,老公的妹妹穿的比我還像新娘。我一直安慰自己虐沥,他們只是感情好泽艘,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開白布匹涮。 她就那樣靜靜地躺著,像睡著了一般然低。 火紅的嫁衣襯著肌膚如雪雳攘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天鞋真,我揣著相機(jī)與錄音沃于,去河邊找鬼。 笑死檩互,一個(gè)胖子當(dāng)著我的面吹牛咨演,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼鹊杖,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了缰贝?” 一聲冷哼從身側(cè)響起瘩绒,我...
    開封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎脆侮,沒想到半個(gè)月后勇劣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡筋蓖,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年粘咖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了侈百。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡讽坏,死狀恐怖例证,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情胀葱,我是刑警寧澤笙蒙,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布捅位,位于F島的核電站搂抒,受9級(jí)特大地震影響尿扯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜衷笋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一右莱、第九天 我趴在偏房一處隱蔽的房頂上張望档插。 院中可真熱鬧,春花似錦郭膛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽己肮。三九已至,卻和暖如春谎僻,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背赤拒。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工诱鞠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留航夺,地道東北人肋乍。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像敷存,于是被迫代替她去往敵國(guó)和親墓造。 傳聞我的和親對(duì)象是個(gè)殘疾皇子堪伍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容