最近因?yàn)閭€(gè)人原因需要準(zhǔn)備一些資料,所以就萌生了把應(yīng)用層所涉及的知識(shí)點(diǎn)做個(gè)基類曹洽,不僅僅是簡(jiǎn)單的知識(shí)結(jié)論鳍置,同時(shí)還希望能從源碼角度上做個(gè)積累,讓自己日后在學(xué)習(xí)中都能有時(shí)間拿出來(lái)翻翻送淆,可能每次花費(fèi)幾分鐘的時(shí)候都會(huì)有新的感悟税产,同時(shí)也是為了鍛煉自己的能力,畢竟不是做純移動(dòng)端的偷崩,技術(shù)還是挺薄弱的辟拷,而且之前去大公司面沒(méi)拿到啥好成績(jī)哈,自嘲一下<..>阐斜,希望大家共同加油吧衫冻。接下來(lái)記錄的是Android事件分發(fā)機(jī)制,其中有些圖片是借鑒別的同學(xué)谒出,大家多多包含哈隅俘。
- 事件分發(fā)流程
-
源碼解析
-View分發(fā)
-ViewGroup分發(fā)
事件分發(fā)流程
事件分發(fā)我們需要關(guān)注主要是三個(gè)方法dispatchTouchEvent、onInterceptTouchEvent笤喳、onTouchEvent为居。但是這三個(gè)方法對(duì)應(yīng)的返回值或執(zhí)行super方法都會(huì)導(dǎo)致最終事件分發(fā)的走向。
若都返回super方法杀狡,則事件分發(fā)的流程如下圖
整個(gè)事件流向應(yīng)該是從Activity---->ViewGroup--->View 從上往下調(diào)用dispatchTouchEvent方法蒙畴,一直到葉子節(jié)點(diǎn)(View)的時(shí)候,再由View--->ViewGroup--->Activity從下往上調(diào)用onTouchEvent方法呜象。
若dispatchTouchEvent和onTouchEvent返回true膳凝,則事件分發(fā)的流程如下圖
當(dāng)dispatchTouchEvent或onTouchEvent返回true碑隆,事件就會(huì)停止傳遞被消耗的。為什么會(huì)這樣呢蹬音,等下我們分析下代碼上煤。
若dispatchTouchEvent和onTouchEvent返回false,則事件分發(fā)的流程如下圖
關(guān)注下標(biāo)注藍(lán)線的部分祟绊,當(dāng)返回false時(shí),事件都會(huì)回傳給父控件的onTouchEvent處理哥捕,同樣這是為什么呢牧抽,我們也可以通過(guò)源碼來(lái)看看哈。
接下來(lái)總結(jié)一下遥赚。我們常見(jiàn)的使用場(chǎng)景如滑動(dòng)沖突控制扬舒,就需要改變?nèi)鏾nIterceptTouchEvent的返回值等。
ViewGroup和View 的dispatchTouchEvent 是做事件分發(fā)凫佛,那么這個(gè)事件可能分發(fā)出去的四個(gè)目標(biāo)
注:------> 后面代表事件目標(biāo)需要怎么做讲坎。
1、 自己消費(fèi)愧薛,終結(jié)傳遞晨炕。------->return true ;
2毫炉、 給自己的onTouchEvent處理-------> 調(diào)用super.dispatchTouchEvent()系統(tǒng)默認(rèn)會(huì)去調(diào)用 onInterceptTouchEvent瓮栗,在onInterceptTouchEvent return true就會(huì)去把事件分給自己的onTouchEvent處理。
3瞄勾、 傳給子View------>調(diào)用super.dispatchTouchEvent()默認(rèn)實(shí)現(xiàn)會(huì)去調(diào)用 onInterceptTouchEvent 在onInterceptTouchEvent return false费奸,就會(huì)把事件傳給子類。
4进陡、 不傳給子View愿阐,事件終止往下傳遞,事件開(kāi)始回溯趾疚,從父View的onTouchEvent開(kāi)始事件從下到上回歸執(zhí)行每個(gè)控件的onTouchEvent------->return false缨历;
注: 由于View沒(méi)有子View所以不需要onInterceptTouchEvent 來(lái)控件是否把事件傳遞給子View還是攔截,所以View的事件分發(fā)調(diào)用super.dispatchTouchEvent()的時(shí)候默認(rèn)把事件傳給自己的onTouchEvent處理(相當(dāng)于攔截)糙麦,對(duì)比ViewGroup的dispatchTouchEvent 事件分發(fā)戈二,View的事件分發(fā)沒(méi)有上面提到的4個(gè)目標(biāo)的第3點(diǎn)。
ViewGroup和View的onTouchEvent方法是做事件處理的喳资,那么這個(gè)事件只能有兩個(gè)處理方式:
1觉吭、自己消費(fèi)掉,事件終結(jié)仆邓,不再傳給誰(shuí)----->return true;
2鲜滩、繼續(xù)從下往上傳伴鳖,不消費(fèi)事件,讓父View也能收到到這個(gè)事件----->return false;View的默認(rèn)實(shí)現(xiàn)是不消費(fèi)的徙硅。所以super==false榜聂。
ViewGroup的onInterceptTouchEvent方法對(duì)于事件有兩種情況:
1、攔截下來(lái)嗓蘑,給自己的onTouchEvent處理--->return true;
2须肆、不攔截,把事件往下傳給子View---->return false,ViewGroup默認(rèn)是不攔截的桩皿,所以super==false豌汇;
關(guān)于ACTION_MOVE 和 ACTION_UP
上面講解的都是針對(duì)ACTION_DOWN的事件傳遞,ACTION_MOVE和ACTION_UP在傳遞的過(guò)程中并不是和ACTION_DOWN 一樣泄隔,你在執(zhí)行ACTION_DOWN的時(shí)候返回了false拒贱,后面一系列其它的action就不會(huì)再得到執(zhí)行了。簡(jiǎn)單的說(shuō)佛嬉,就是當(dāng)dispatchTouchEvent在進(jìn)行事件分發(fā)的時(shí)候逻澳,只有前一個(gè)事件(如ACTION_DOWN)返回true,才會(huì)收到ACTION_MOVE和ACTION_UP的事件暖呕。具體這句話很多博客都說(shuō)了斜做,但是具體含義是什么呢?我們來(lái)看一下下面的具體分析湾揽。
上面提到過(guò)了陨享,事件如果不被打斷的話是會(huì)不斷往下傳到葉子層(View),然后又不斷回傳到Activity钝腺,dispatchTouchEvent 和 onTouchEvent 可以通過(guò)return true 消費(fèi)事件抛姑,終結(jié)事件傳遞,而onInterceptTouchEvent 并不能消費(fèi)事件艳狐,它相當(dāng)于是一個(gè)分叉口起到分流導(dǎo)流的作用定硝,ACTION_MOVE和ACTION_UP 會(huì)在哪些函數(shù)被調(diào)用,之前說(shuō)了并不是哪個(gè)函數(shù)收到了ACTION_DOWN毫目,就會(huì)收到 ACTION_MOVE 等后續(xù)的事件的蔬啡。
下面通過(guò)幾張圖看看不同場(chǎng)景下,ACTION_MOVE事件和ACTION_UP事件的具體走向并總結(jié)一下規(guī)律镀虐。
1箱蟆、我們?cè)赩iewGroup1 的dispatchTouchEvent 方法返回true消費(fèi)這次事件
ACTION_DOWN 事件從(Activity的dispatchTouchEvent)--------> (ViewGroup1 的dispatchTouchEvent) 后結(jié)束傳遞,事件被消費(fèi)(如下圖紅色的箭頭代碼ACTION_DOWN 事件的流向)刮便。
下圖中紅色的箭頭代表ACTION_DOWN 事件的流向链方,藍(lán)色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向
2柔吼、我們?cè)赩iewGroup2 的dispatchTouchEvent 返回true消費(fèi)這次事件
3剂陡、我們?cè)赩iew 的dispatchTouchEvent 返回true消費(fèi)這次事件
這個(gè)我不就畫(huà)圖了获黔,效果和在ViewGroup2 的dispatchTouchEvent return true的差不多淹仑,同樣的收到ACTION_DOWN 的dispatchTouchEvent函數(shù)都能收到 ACTION_MOVE和ACTION_UP。
所以我們就基本可以得出結(jié)論如果在某個(gè)控件的dispatchTouchEvent 返回true消費(fèi)終結(jié)事件,那么收到ACTION_DOWN 的函數(shù)也能收到 ACTION_MOVE和ACTION_UP。
4钝凶、我們?cè)赩iew 的onTouchEvent 返回true消費(fèi)這次事件
紅色的箭頭代表ACTION_DOWN 事件的流向
藍(lán)色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向
5、我們?cè)赩iewGroup 2 的onTouchEvent 返回true消費(fèi)這次事件
紅色的箭頭代表ACTION_DOWN 事件的流向
藍(lán)色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向
6唁影、我們?cè)赩iewGroup 1 的onTouchEvent 返回true消費(fèi)這次事件
紅色的箭頭代表ACTION_DOWN 事件的流向
藍(lán)色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向
7耕陷、我們?cè)贏ctivity 的onTouchEvent 返回true消費(fèi)這次事件
紅色的箭頭代表ACTION_DOWN 事件的流向
藍(lán)色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向
8、我們?cè)赩iew的dispatchTouchEvent 返回false并且Activity 的onTouchEvent 返回true消費(fèi)這次事件
紅色的箭頭代表ACTION_DOWN 事件的流向
藍(lán)色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向
9据沈、我們?cè)赩iew的dispatchTouchEvent 返回false并且ViewGroup 1 的onTouchEvent 返回true消費(fèi)這次事件
紅色的箭頭代表ACTION_DOWN 事件的流向
藍(lán)色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向
10哟沫、我們?cè)赩iew的dispatchTouchEvent 返回false并且在ViewGroup 2 的onTouchEvent 返回true消費(fèi)這次事件
紅色的箭頭代表ACTION_DOWN 事件的流向
藍(lán)色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向
11、我們?cè)赩iewGroup2的dispatchTouchEvent 返回false并且在ViewGroup1 的onTouchEvent返回true消費(fèi)這次事件
紅色的箭頭代表ACTION_DOWN 事件的流向
藍(lán)色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向
12卓舵、我們?cè)赩iewGroup2的onInterceptTouchEvent 返回true攔截此次事件并且在ViewGroup 1 的onTouchEvent返回true消費(fèi)這次事件南用。
紅色的箭頭代表ACTION_DOWN 事件的流向
藍(lán)色的箭頭代表ACTION_MOVE 和 ACTION_UP 事件的流向
對(duì)于在onTouchEvent消費(fèi)事件的情況:在哪個(gè)View的onTouchEvent 返回true膀钠,那么ACTION_MOVE和ACTION_UP的事件從上往下傳到這個(gè)View后就不再往下傳遞了掏湾,而直接傳給自己的onTouchEvent 并結(jié)束本次事件傳遞過(guò)程。
對(duì)于ACTION_MOVE肿嘲、ACTION_UP總結(jié):ACTION_DOWN事件在哪個(gè)控件消費(fèi)了(return true)融击, 那么ACTION_MOVE和ACTION_UP就會(huì)從上往下(通過(guò)dispatchTouchEvent)做事件分發(fā)往下傳,就只會(huì)傳到這個(gè)控件雳窟,不會(huì)繼續(xù)往下傳尊浪,如果ACTION_DOWN事件是在dispatchTouchEvent消費(fèi),那么事件到此為止停止傳遞封救,如果ACTION_DOWN事件是在onTouchEvent消費(fèi)的拇涤,那么會(huì)把ACTION_MOVE或ACTION_UP事件傳給該控件的onTouchEvent處理并結(jié)束傳遞。
分發(fā)機(jī)制源碼解析
其實(shí)在上述的結(jié)論中誉结,都是可以從源碼上一探究竟的鹅士。那我們先從View的事件分發(fā)開(kāi)始吧,主要還是看dispatchTouchEvent和onTouchEvent方法惩坑。
- View事件分發(fā)源碼
1. public boolean dispatchTouchEvent(MotionEvent event) {
2. if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
3. mOnTouchListener.onTouch(this, event)) {
4. return true;
5. }
6. return onTouchEvent(event);
7. }
1. public boolean onTouchEvent(MotionEvent event) {
2. final int viewFlags = mViewFlags;
3. if ((viewFlags & ENABLED_MASK) == DISABLED) {
4. // A disabled view that is clickable still consumes the touch
5. // events, it just doesn't respond to them.
6. return (((viewFlags & CLICKABLE) == CLICKABLE ||
7. (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
8. }
9. if (mTouchDelegate != null) {
10. if (mTouchDelegate.onTouchEvent(event)) {
11. return true;
12. }
13. }
14. if (((viewFlags & CLICKABLE) == CLICKABLE ||
15. (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
16. switch (event.getAction()) {
17. case MotionEvent.ACTION_UP:
18. boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
19. if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
20. // take focus if we don't have it already and we should in
21. // touch mode.
22. boolean focusTaken = false;
23. if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
24. focusTaken = requestFocus();
25. }
26. if (!mHasPerformedLongPress) {
27. // This is a tap, so remove the longpress check
28. removeLongPressCallback();
29. // Only perform take click actions if we were in the pressed state
30. if (!focusTaken) {
31. // Use a Runnable and post this rather than calling
32. // performClick directly. This lets other visual state
33. // of the view update before click actions start.
34. if (mPerformClick == null) {
35. mPerformClick = new PerformClick();
36. }
37. if (!post(mPerformClick)) {
38. performClick();
39. }
40. }
41. }
42. if (mUnsetPressedState == null) {
43. mUnsetPressedState = new UnsetPressedState();
44. }
45. if (prepressed) {
46. mPrivateFlags |= PRESSED;
47. refreshDrawableState();
48. postDelayed(mUnsetPressedState,
49. ViewConfiguration.getPressedStateDuration());
50. } else if (!post(mUnsetPressedState)) {
51. // If the post failed, unpress right now
52. mUnsetPressedState.run();
53. }
54. removeTapCallback();
55. }
56. break;
57. case MotionEvent.ACTION_DOWN:
58. if (mPendingCheckForTap == null) {
59. mPendingCheckForTap = new CheckForTap();
60. }
61. mPrivateFlags |= PREPRESSED;
62. mHasPerformedLongPress = false;
63. postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
64. break;
65. case MotionEvent.ACTION_CANCEL:
66. mPrivateFlags &= ~PRESSED;
67. refreshDrawableState();
68. removeTapCallback();
69. break;
70. case MotionEvent.ACTION_MOVE:
71. final int x = (int) event.getX();
72. final int y = (int) event.getY();
73. // Be lenient about moving outside of buttons
74. int slop = mTouchSlop;
75. if ((x < 0 - slop) || (x >= getWidth() + slop) ||
76. (y < 0 - slop) || (y >= getHeight() + slop)) {
77. // Outside button
78. removeTapCallback();
79. if ((mPrivateFlags & PRESSED) != 0) {
80. // Remove any future long press/tap checks
81. removeLongPressCallback();
82. // Need to switch from pressed to not pressed
83. mPrivateFlags &= ~PRESSED;
84. refreshDrawableState();
85. }
86. }
87. break;
88. }
89. return true;
90. }
91. return false;
92. }
如果要分析到上述情況掉盅,我們還要借助于ViewGroup的分發(fā)事件的源碼,馬上供上哈以舒。
dispatchTouchEvent
1. public boolean dispatchTouchEvent(MotionEvent ev) {
2. final int action = ev.getAction();
3. final float xf = ev.getX();
4. final float yf = ev.getY();
5. final float scrolledXFloat = xf + mScrollX;
6. final float scrolledYFloat = yf + mScrollY;
7. final Rect frame = mTempRect;
8. boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
9. if (action == MotionEvent.ACTION_DOWN) {
10. if (mMotionTarget != null) {
11. mMotionTarget = null;
12. }
13. if (disallowIntercept || !onInterceptTouchEvent(ev)) {
14. ev.setAction(MotionEvent.ACTION_DOWN);
15. final int scrolledXInt = (int) scrolledXFloat;
16. final int scrolledYInt = (int) scrolledYFloat;
17. final View[] children = mChildren;
18. final int count = mChildrenCount;
19. for (int i = count - 1; i >= 0; i--) {
20. final View child = children[i];
21. if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
22. || child.getAnimation() != null) {
23. child.getHitRect(frame);
24. if (frame.contains(scrolledXInt, scrolledYInt)) {
25. final float xc = scrolledXFloat - child.mLeft;
26. final float yc = scrolledYFloat - child.mTop;
27. ev.setLocation(xc, yc);
28. child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
29. if (child.dispatchTouchEvent(ev)) {
30. mMotionTarget = child;
31. return true;
32. }
33. }
34. }
35. }
36. }
37. }
38. boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
39. (action == MotionEvent.ACTION_CANCEL);
40. if (isUpOrCancel) {
41. mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
42. }
43. final View target = mMotionTarget;
44. if (target == null) {
45. ev.setLocation(xf, yf);
46. if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
47. ev.setAction(MotionEvent.ACTION_CANCEL);
48. mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
49. }
50. return super.dispatchTouchEvent(ev);
51. }
52. if (!disallowIntercept && onInterceptTouchEvent(ev)) {
53. final float xc = scrolledXFloat - (float) target.mLeft;
54. final float yc = scrolledYFloat - (float) target.mTop;
55. mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
56. ev.setAction(MotionEvent.ACTION_CANCEL);
57. ev.setLocation(xc, yc);
58. if (!target.dispatchTouchEvent(ev)) {
59. }
60. mMotionTarget = null;
61. return true;
62. }
63. if (isUpOrCancel) {
64. mMotionTarget = null;
65. }
66. final float xc = scrolledXFloat - (float) target.mLeft;
67. final float yc = scrolledYFloat - (float) target.mTop;
68. ev.setLocation(xc, yc);
69. if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
70. ev.setAction(MotionEvent.ACTION_CANCEL);
71. target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
72. mMotionTarget = null;
73. }
74. return target.dispatchTouchEvent(ev);
75. }
如果我們重寫(xiě)View的dispatchTouchEvent并返回true趾痘,我們跟蹤到ViewGroup中第29行代碼,你看執(zhí)行child.dispatchTouchEvent返回true后ViewGroup的該方法也直接return true了蔓钟,接下來(lái)ACTION_UP其他事件類型永票,我們定位到代碼74行,最終會(huì)執(zhí)行target.dispatchTouchEvent,但view的dispatchTouchEvent返回true瓦侮,說(shuō)明事件被消費(fèi)了艰赞,不會(huì)再進(jìn)入到ViewGroup的onTouchEvent里。
如果我們重寫(xiě)View的dispatchTouchEvent并返回false或super肚吏,那么在ViewGroup代碼29行里方妖,我么看到它返回false還會(huì)繼續(xù)走ViewGroup后續(xù)代碼,target為null罚攀,執(zhí)行super.dispatchTouchEvent方法(ViewGroup的父類是View)這里調(diào)用View的dispatchTouchEvent方法->內(nèi)部調(diào)用onTouchEvent方法党觅。
如果我們重寫(xiě)ViewGroup的dispatchTouchEvent方法,直接返回false斋泄,我特意弄了這樣的場(chǎng)景杯瞻,看了Activity的dispatchTouchEvent的源碼,會(huì)發(fā)現(xiàn)會(huì)執(zhí)行Activity的onTouchEvent方法
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
其他場(chǎng)景也是差不多的炫掐,都是可以套入代碼中做分析的魁莉,這里要我繼續(xù)分析也感覺(jué)無(wú)從入手,大家可以看看源碼募胃,找到關(guān)鍵代碼分析分析場(chǎng)景旗唁。
附上別人分析的源碼解析