最近有些空余時間,復(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è)置OnTouchListener
和OnClickListener
论寨,方法中都是簡單的打印log。
為方便大家對照著看log店雅,將最常見的幾個touch事件的action記下來:
MotionEvent.ACTION_DOWN = 0
MotionEvent.ACTION_UP = 1
MotionEvent.ACTION_MOVE = 2
MotionEvent.ACTION_CANCEL = 3
因為以上博客中測試的情況都是View
和ViewGroup
簡單處理事件的結(jié)果政基,比如在onTouchEvent()
方法中處理所有down
,move
和 up
事件都返回相同的的結(jié)果true
闹啦,false
或者系統(tǒng)默認(rèn)的處理super.onTouchEvent
沮明。如果我們在實際開發(fā)中有這樣一種業(yè)務(wù)需求:在一連串的觸摸事件中(按下--移動--抬起)需要根據(jù)不同的情況,返回true
或者false
時窍奋,事件傳遞是怎么樣的呢荐健?
所以,我測試的重點問題為:
如果我們在TestView
中按以下方式重寫View
的onTouchEvent()
方法:
@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ù)的move
和 up
事件由我們手動處理直接返回false窖逗,那么事件從最外層的Activity
至中間的TestViewGroup
再至最里層的TestView
的傳遞處理是怎樣的呢址否。
大家可以先根據(jù)自己的理解,想一下這種情況下面的傳遞流程及結(jié)果。
為了做對照碎紊,我先把最常見的情況羅列出來:
- 普通情況下, 都按照系統(tǒng)默認(rèn), 即
TestView
正常接受所有事件佑附。點擊中間的TestButton
后,可以發(fā)現(xiàn)事件都由onTouchEvent
消費,每個單獨的事件傳到TestView
就結(jié)束了,ViewGroup
和Activtiy
的onTouchEvent()
方法都沒有執(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: 點擊了
- 第二種情況,
Activity
和ViewGroup
的所有方法都用系統(tǒng)默認(rèn)的.TestView
的onTouchEvent
方法中對所有事件均全部返回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)用到Activity
的onTouchEvent
,后續(xù)的move
和 up
事件都只有Activity
接收到并處理了.
- 接下來看上面說到的那種情況:
Activity
和ViewGroup
的所有方法都用系統(tǒng)默認(rèn)的處理方法,TestView
中按以下方式重寫View
的onTouchEvent()
方法:
@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
后, ViewGroup
和Activity
的onTouchEvent
都沒有處理down
事件,而是立即下發(fā)了第二個move
事件樊诺,這里跟第一種情況是一樣的處理結(jié)果. TestView
收到move
事件并返回false
后, ViewGroup
收到TestView
返回的結(jié)果后,并沒有在onTouchEvent
中對move
事件處理,而是繼續(xù)將false
結(jié)果上傳,直至最后上傳給最外層的Activity
,并最終由Activity
的onTouchEvent
處理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
事件在ViewGroup
的 dispatchTouchEvent()
方法中會執(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
, ViewGroup
的 dispatchTouchEvent()
方法沒有做任何處理,最后僅僅是將handled
變量標(biāo)記為false
,再傳遞給了更外層的Activity
. 所以我們看到的結(jié)果就是上面的log打印出來的.