Android onTouchEvent 返回值不同時對事件的傳遞的影響

最近有些空余時間,復(fù)習(xí)了下Touch事件的傳遞機制,對以前不明白的地方整理了下胸私。關(guān)于Touch事件,前前后后看了不少大神寫的博客鳖谈,也照著博客的解釋去看系統(tǒng)源碼,對Touch事件的理解提升了很多阔涉。
推薦大家看這位大神的博客:
工匠若水 的CSDN博客, 非常詳細(xì)的3篇分析文章
Android觸摸屏事件派發(fā)機制詳解與源碼分析一(View篇)
Android觸摸屏事件派發(fā)機制詳解與源碼分析二(ViewGroup篇)
Android觸摸屏事件派發(fā)機制詳解與源碼分析三(Activity篇)

以及這位大神的 sunzn:
Android 編程下 Touch 事件的分發(fā)和消費機制

看完上面這幾篇博客缆娃,并對照著系統(tǒng)源碼理解后捷绒,相信大家會對Touch事件的理解深入不少。我花了差不多一整天的時間理解后(理解力有些差肮嵋)暖侨,效果很棒。但還沒有理解透徹崇渗,對最里層的view(比如button等)處理事件的結(jié)果層層上傳至最外層的Activity(中間層為viewgroup字逗,view的父控件,如LinearLayout)的過程還有些不太理解宅广。于是自己又做了個小測試『簦現(xiàn)將測試的結(jié)果記錄下來,并簡單說明下跟狱。純當(dāng)做個人筆記俭厚,如有錯誤,望各位同學(xué)指正驶臊。

測試布局很簡單跟sunzn大神博客中一樣的布局挪挤,一個Activity布局中放值一個自定義的RelativeLayout(類名為TestViewGroup),然后RelativeLayout中間再放置一個自定義的Button(類名為TestView)关翎。自定義類分別重寫了幾個關(guān)鍵的方法:dispatchTouchEvent()扛门,onInterceptTouchEvent()onTouchEvent()纵寝,并給TestView設(shè)置OnTouchListenerOnClickListener论寨,方法中都是簡單的打印log。

為方便大家對照著看log店雅,將最常見的幾個touch事件的action記下來:

MotionEvent.ACTION_DOWN = 0
MotionEvent.ACTION_UP = 1
MotionEvent.ACTION_MOVE = 2
MotionEvent.ACTION_CANCEL = 3

因為以上博客中測試的情況都是ViewViewGroup簡單處理事件的結(jié)果政基,比如在onTouchEvent()方法中處理所有downmoveup 事件都返回相同的的結(jié)果true闹啦,false 或者系統(tǒng)默認(rèn)的處理super.onTouchEvent沮明。如果我們在實際開發(fā)中有這樣一種業(yè)務(wù)需求:在一連串的觸摸事件中(按下--移動--抬起)需要根據(jù)不同的情況,返回true或者false時窍奋,事件傳遞是怎么樣的呢荐健?
所以,我測試的重點問題為:
如果我們在TestView中按以下方式重寫ViewonTouchEvent()方法:

@Override
public boolean onTouchEvent(MotionEvent event) {
   Log.i(TAG, "onTouchEvent: " + event.getAction());
   if (event.getAction() == MotionEvent.ACTION_DOWN) {
      return super.onTouchEvent(event);
   } else {
      Log.i(TAG, "onTouchEvent: 返回false");
      return false;
   }
}

也就是說琳袄,最開始的down事件由TestView接收并按系統(tǒng)的方法處理了江场,即會默認(rèn)返回true(因為我們的TestView是繼承自Button的),后續(xù)的moveup 事件由我們手動處理直接返回false窖逗,那么事件從最外層的Activity至中間的TestViewGroup再至最里層的TestView的傳遞處理是怎樣的呢址否。
大家可以先根據(jù)自己的理解,想一下這種情況下面的傳遞流程及結(jié)果。

為了做對照碎紊,我先把最常見的情況羅列出來:

  1. 普通情況下, 都按照系統(tǒng)默認(rèn), 即TestView正常接受所有事件佑附。點擊中間的TestButton后,可以發(fā)現(xiàn)事件都由onTouchEvent消費,每個單獨的事件傳到TestView就結(jié)束了, ViewGroupActivtiyonTouchEvent()方法都沒有執(zhí)行(分別貼出down,move,up事件的log)
I/MainActivity: dispatchTouchEvent: 0
I/testViewGroup: dispatchTouchEvent: 0
I/testViewGroup: onInterceptTouchEvent: 0
I/TestView: dispatchTouchEvent: 0
I/TestView: onTouch: 點擊了
I/TestView: onTouchEvent: 0
I/MainActivity: dispatchTouchEvent: 2
I/testViewGroup: dispatchTouchEvent: 2
I/testViewGroup: onInterceptTouchEvent: 2
I/TestView: dispatchTouchEvent: 2
I/TestView: onTouch: 點擊了
I/TestView: onTouchEvent: 2
I/MainActivity: dispatchTouchEvent: 1
I/testViewGroup: dispatchTouchEvent: 1
I/testViewGroup: onInterceptTouchEvent: 1
I/TestView: dispatchTouchEvent: 1
I/TestView: onTouch: 點擊了
I/TestView: onTouchEvent: 1
I/TestView: onClick: 點擊了
  1. 第二種情況,ActivityViewGroup的所有方法都用系統(tǒng)默認(rèn)的.TestViewonTouchEvent方法中對所有事件均全部返回false,打印的log如下:
I/MainActivity: dispatchTouchEvent: 0
I/TestViewGroup: dispatchTouchEvent: 0
I/TestViewGroup: onInterceptTouchEvent: 0
I/TestView: dispatchTouchEvent: 0
I/TestView: onTouch: 點擊了
I/TestView: onTouchEvent: 0
I/TestView: onTouchEvent: 返回false
I/testViewGroup: onTouchEvent: 0
I/MainActivity: onTouchEvent: 0
I/MainActivity: dispatchTouchEvent: 2
I/MainActivity: onTouchEvent: 2
I/MainActivity: dispatchTouchEvent: 2
I/MainActivity: onTouchEvent: 2
I/MainActivity: dispatchTouchEvent: 2
I/MainActivity: onTouchEvent: 2
I/MainActivity: dispatchTouchEvent: 2
I/MainActivity: onTouchEvent: 2
I/MainActivity: dispatchTouchEvent: 2
I/MainActivity: onTouchEvent: 2
I/MainActivity: dispatchTouchEvent: 1
I/MainActivity: onTouchEvent: 1

可以發(fā)現(xiàn)TestView收到down事件后返回false,它的外層父控件也調(diào)用了onTouchEvent方法并默認(rèn)返回false,最后一直調(diào)用到ActivityonTouchEvent,后續(xù)的moveup 事件都只有Activity接收到并處理了.

  1. 接下來看上面說到的那種情況:
    ActivityViewGroup的所有方法都用系統(tǒng)默認(rèn)的處理方法,TestView中按以下方式重寫ViewonTouchEvent()方法:
@Override
public boolean onTouchEvent(MotionEvent event) {
   Log.i(TAG, "onTouchEvent: " + event.getAction());
   if (event.getAction() == MotionEvent.ACTION_DOWN) {
      return super.onTouchEvent(event);
   } else {
      Log.i(TAG, "onTouchEvent: 返回false");
      return false;
    }
}

打印的log如下:

I/MainActivity: dispatchTouchEvent: 0
I/testViewGroup: dispatchTouchEvent: 0
I/testViewGroup: onInterceptTouchEvent: 0
I/TestView: dispatchTouchEvent: 0
I/TestView: onTouch: 點擊了
I/TestView: onTouchEvent: 0
I/MainActivity: dispatchTouchEvent: 2
I/testViewGroup: dispatchTouchEvent: 2
I/testViewGroup: onInterceptTouchEvent: 2
I/TestView: dispatchTouchEvent: 2
I/TestView: onTouch: 點擊了
I/TestView: onTouchEvent: 2
I/TestView: onTouchEvent: 返回false
I/MainActivity: onTouchEvent: 2
I/MainActivity: dispatchTouchEvent: 1
I/testViewGroup: dispatchTouchEvent: 1
I/testViewGroup: onInterceptTouchEvent: 1
I/TestView: dispatchTouchEvent: 1
I/TestView: onTouch: 點擊了
I/TestView: onTouchEvent: 1
I/TestView: onTouchEvent: 返回false
I/MainActivity: onTouchEvent: 1

可以看到TestView處理了down事件并返回true后, ViewGroupActivityonTouchEvent都沒有處理down事件,而是立即下發(fā)了第二個move事件樊诺,這里跟第一種情況是一樣的處理結(jié)果. TestView收到move事件并返回false后, ViewGroup收到TestView返回的結(jié)果后,并沒有在onTouchEvent中對move事件處理,而是繼續(xù)將false結(jié)果上傳,直至最后上傳給最外層的Activity,并最終由ActivityonTouchEvent處理move事件.后續(xù)的所有move, up 事件都會先傳遞給TestView處理后, 再最終向上傳遞給Activity處理.中間所有的 ViewGroup都不會處理,只是起傳遞作用.

為什么會這樣呢?

對照ViewGroup dispatchTouchEvent()方法的源碼中最關(guān)鍵的一段,這段代碼只在down事件下發(fā)時才會執(zhí)行,理解上面這種結(jié)果會容易很多,源碼如下:

if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
    // Child wants to receive touch within its bounds.
    mLastTouchDownTime = ev.getDownTime();
    if (preorderedList != null) {
        // childIndex points into presorted list, find original index
        for (int j = 0; j < childrenCount; j++) {
            if (children[childIndex] == mChildren[j]) {
                mLastTouchDownIndex = j;
                break;
            }
        }
    } else {
        mLastTouchDownIndex = childIndex;
    }
    mLastTouchDownX = ev.getX();
    mLastTouchDownY = ev.getY();
    newTouchTarget = addTouchTarget(child, idBitsToAssign);
    alreadyDispatchedToNewTouchTarget = true;
    break;
}

第一行的dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)方法就是下發(fā)事件ev給子控件 child的方法,子控件會調(diào)用child.dispatchTouchEvent(event)處理down事件.child最終會調(diào)用onTouchEvent處理down事件并返回結(jié)果,返回的結(jié)果會再返回給這里的dispatchTransformedTouchEvent(),然后再第一行進(jìn)行if判斷,因為我們的處理邏輯是down事件返回true,所以進(jìn)入if判斷執(zhí)行下面的代碼.然后再倒數(shù)第四行 newTouchTarget = addTouchTarget(child, idBitsToAssign); 將我們的TestView設(shè)置為執(zhí)行觸摸事件的目標(biāo)控件.后續(xù)的move,up事件都會下發(fā)給它了.
后續(xù)的move ,up事件在ViewGroupdispatchTouchEvent()方法中會執(zhí)行的代碼下面的代碼,

while (target != null) {
        final TouchTarget next = target.next;
        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
            handled = true;
        } else {
            final boolean cancelChild = resetCancelNextUpFlag(target.child)
                    || intercepted;
            if (dispatchTransformedTouchEvent(ev, cancelChild,
                    target.child, target.pointerIdBits)) {
                handled = true;
            }
            if (cancelChild) {
                if (predecessor == null) {
                    mFirstTouchTarget = next;
                } else {
                    predecessor.next = next;
                }
                target.recycle();
                target = next;
                continue;
            }
        }
        predecessor = target;
        target = next;
    }
}

看到中間的這段關(guān)鍵代碼了嗎?

if (dispatchTransformedTouchEvent(ev, cancelChild,
           target.child, target.pointerIdBits)) {
     handled = true;
}

這個if判斷并沒有else部分,所以如果move, up 返回false, ViewGroupdispatchTouchEvent()方法沒有做任何處理,最后僅僅是將handled 變量標(biāo)記為false,再傳遞給了更外層的Activity. 所以我們看到的結(jié)果就是上面的log打印出來的.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末音同,一起剝皮案震驚了整個濱河市词爬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌权均,老刑警劉巖顿膨,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異叽赊,居然都是意外死亡恋沃,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進(jìn)店門蛇尚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來芽唇,“玉大人,你說我怎么就攤上這事取劫〈殷裕” “怎么了?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵谱邪,是天一觀的道長炮捧。 經(jīng)常有香客問我,道長惦银,這世上最難降的妖魔是什么咆课? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮扯俱,結(jié)果婚禮上书蚪,老公的妹妹穿的比我還像新娘。我一直安慰自己迅栅,他們只是感情好殊校,可當(dāng)我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著读存,像睡著了一般为流。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上让簿,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天敬察,我揣著相機與錄音,去河邊找鬼尔当。 笑死莲祸,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播虫给,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼藤抡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了抹估?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤弄兜,失蹤者是張志新(化名)和其女友劉穎药蜻,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體替饿,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡语泽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了视卢。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片踱卵。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖据过,靈堂內(nèi)的尸體忽然破棺而出惋砂,到底是詐尸還是另有隱情,我是刑警寧澤绳锅,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布西饵,位于F島的核電站,受9級特大地震影響鳞芙,放射性物質(zhì)發(fā)生泄漏眷柔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一原朝、第九天 我趴在偏房一處隱蔽的房頂上張望驯嘱。 院中可真熱鬧,春花似錦喳坠、人聲如沸鞠评。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽谢澈。三九已至,卻和暖如春御板,著一層夾襖步出監(jiān)牢的瞬間锥忿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工怠肋, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留敬鬓,地道東北人。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像钉答,于是被迫代替她去往敵國和親础芍。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,675評論 2 359

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