版權聲明:本文出自汪磊的部落格,轉載請務必注明出處。
在上一篇《Android事件傳遞機制詳解及最新源碼分析——View篇》中,詳細講解了View事件的傳遞機制,沒掌握或者掌握不紮實的小夥伴,強烈建議先閱讀上一篇。
好了,廢話還是少說,直奔主題,開始本篇的ViewGroup事件傳遞機制探索之旅。
依然從簡單的Demo例子現象開始分析
建立安卓工程,首先自定義一個Button以及一個RelativeLayout,很簡單,隻是重寫了主要與事件傳遞機制相關的方法,代碼如下:
自定義WLButton類:
1 public class WLButton extends Button {
2
3 private static final String TAG = "WL";
4
5 public WLButton(Context context, AttributeSet attrs) {
6 super(context, attrs);
7 }
8
9 @Override
10 public boolean dispatchTouchEvent(MotionEvent event) {
11 Log.i(TAG, "WLButton dispatchTouchEvent : "+event.getAction());
12 return super.dispatchTouchEvent(event);
13 }
14
15 @Override
16 public boolean onTouchEvent(MotionEvent event) {
17 Log.i(TAG, "WLButton onTouchEvent : "+event.getAction());
18 return super.onTouchEvent(event);
19 }
20
21 }
自定義WLRelativeLayout類:
1 public class WLRelativeLayout extends RelativeLayout {
2
3 private static final String TAG = "WL";
4
5 public WLRelativeLayout(Context context, AttributeSet attrs) {
6 super(context, attrs);
7 }
8
9 @Override
10 public boolean dispatchTouchEvent(MotionEvent ev) {
11 Log.i(TAG, "WLRelativeLayout dispatchTouchEvent : "+ev.getAction());
12 return super.dispatchTouchEvent(ev);
13 }
14
15 @Override
16 public boolean onInterceptTouchEvent(MotionEvent ev) {
17 Log.i(TAG, "WLRelativeLayout onInterceptTouchEvent : "+ev.getAction());
18 return super.onInterceptTouchEvent(ev);
19 }
20
21 @Override
22 public boolean onTouchEvent(MotionEvent event) {
23 Log.i(TAG, "WLRelativeLayout onTouchEvent : "+event.getAction());
24 return super.onTouchEvent(event);
25 }
26 }
對于WLRelativeLayout會額外注意到我們重寫父類onInterceptTouchEvent方法,這裡目前隻是列印一下資訊,追蹤什麼時候調用的,至于這個方法具體有什麼作用先别着急,後續分析源碼的時候咱們會着重分析。
接下來我們編寫Acticity的代碼,如下:
1 public class MainActivity extends Activity implements OnTouchListener, OnClickListener {
2
3 private static final String TAG = "WL";
4 private Button mButton;
5 private WLRelativeLayout myRelativeLayout;
6
7 @Override
8 protected void onCreate(Bundle savedInstanceState) {
9 super.onCreate(savedInstanceState);
10 setContentView(R.layout.activity_main);
11
12 mButton = (Button) findViewById(R.id.click);
13 mButton.setOnTouchListener(this);
14 mButton.setOnClickListener(this);
15
16 myRelativeLayout = (WLRelativeLayout) findViewById(R.id.myRelativeLayout);
17 myRelativeLayout.setOnTouchListener(this);
18 myRelativeLayout.setOnClickListener(this);
19
20 }
21
22 @Override
23 public void onClick(View v) {
24 //
25 Log.i(TAG, "onClick :"+v);
26 }
27
28 @Override
29 public boolean onTouch(View v, MotionEvent event) {
30 //
31 Log.i(TAG, "onTouch :"+",...action :"+event.getAction()+"...View :"+v);
32 return false;
33 }
34
35 }
沒什麼好解釋的,稍有經驗的應該都能看懂,我們看下來不同操作列印資訊:
首先我們正常點選Button,列印資訊如下:
我們看到首先執行的是WLRelativeLayout的dispatchTouchEvent方法,然後執行onInterceptTouchEvent方法,接下來執行WLButton的dispatchTouchEvent方法,之後就不用多餘解釋了吧,就是上篇講到的View的事件分發流程。
接下來,我們Button外部區域部分看看列印資訊,如下:
點選外部區域有沒有發現和View事件分發流程特别像,隻不過多了一個onInterceptTouchEvent方法,但是我們發現在我們按下手指的時候(action為0的時候)會觸發onInterceptTouchEvent方法,然而擡起手指的時候(action為1的時候)卻沒有觸發onInterceptTouchEvent方法,這是為什麼呢?别急,等我們分析完源碼就會有答案。
我們注意到onInterceptTouchEvent 是有傳回值的,預設我們是調用父類的onInterceptTouchEvent方法,那我們看看父類中onInterceptTouchEvent是什麼邏輯呢,點進去看一下源碼,我了個叉,真簡單,如下:
1 public boolean onInterceptTouchEvent(MotionEvent ev) {
2 return false;
3 }
就直接傳回一個false,那好,我們人為在子類中傳回true,然後點選button看看列印情況。
接下來我們在WLRelativeLayout中将onInterceptTouchEvent傳回值設定為true,然後運作程式會發現點選button内外列印資訊都如下:
是不是有種觸摸或者點選事件和button沒關系了的感覺,就WLRelativeLayout自己玩的樣子,這又是為什麼呢?好了,帶着這樣疑問我們檢視一下源碼,看看源碼層是怎麼處理的。
ViewGroup事件傳遞機制源碼分析(源碼版本為API23)
通過上面demo現象,我們得知,當我們點選Button首先觸發的是WLRelativeLayout中的dispatchTouchEvent,我們向上尋找最終在ViewGroup中找到dispatchTouchEvent這個方法,我們知道ViewGroup是繼承自View,不用過多解釋了,ViewGroup隻是将這個方法重寫了而已。
ViewGroup中dispatchTouchEvent方法源碼如下:
1 @Override
2 public boolean dispatchTouchEvent(MotionEvent ev) {
3 if (mInputEventConsistencyVerifier != null) {
4 mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
5 }
6
7 // If the event targets the accessibility focused view and this is it, start
8 // normal event dispatch. Maybe a descendant is what will handle the click.
9 if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
10 ev.setTargetAccessibilityFocus(false);
11 }
12
13 boolean handled = false;
14 if (onFilterTouchEventForSecurity(ev)) {
15 final int action = ev.getAction();
16 final int actionMasked = action & MotionEvent.ACTION_MASK;
17
18 // Handle an initial down.
19 if (actionMasked == MotionEvent.ACTION_DOWN) {
20 // Throw away all previous state when starting a new touch gesture.
21 // The framework may have dropped the up or cancel event for the previous gesture
22 // due to an app switch, ANR, or some other state change.
23 cancelAndClearTouchTargets(ev);
24 resetTouchState();
25 }
26
27 // Check for interception.
28 final boolean intercepted;
29 if (actionMasked == MotionEvent.ACTION_DOWN
30 || mFirstTouchTarget != null) {
31 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
32 if (!disallowIntercept) {
33 intercepted = onInterceptTouchEvent(ev);
34 ev.setAction(action); // restore action in case it was changed
35 } else {
36 intercepted = false;
37 }
38 } else {
39 // There are no touch targets and this action is not an initial down
40 // so this view group continues to intercept touches.
41 intercepted = true;
42 }
43
44 // If intercepted, start normal event dispatch. Also if there is already
45 // a view that is handling the gesture, do normal event dispatch.
46 if (intercepted || mFirstTouchTarget != null) {
47 ev.setTargetAccessibilityFocus(false);
48 }
49
50 // Check for cancelation.
51 final boolean canceled = resetCancelNextUpFlag(this)
52 || actionMasked == MotionEvent.ACTION_CANCEL;
53
54 // Update list of touch targets for pointer down, if needed.
55 final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
56 TouchTarget newTouchTarget = null;
57 boolean alreadyDispatchedToNewTouchTarget = false;
58 if (!canceled && !intercepted) {
59
60 // If the event is targeting accessiiblity focus we give it to the
61 // view that has accessibility focus and if it does not handle it
62 // we clear the flag and dispatch the event to all children as usual.
63 // We are looking up the accessibility focused host to avoid keeping
64 // state since these events are very rare.
65 View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
66 ? findChildWithAccessibilityFocus() : null;
67
68 if (actionMasked == MotionEvent.ACTION_DOWN
69 || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
70 || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
71 final int actionIndex = ev.getActionIndex(); // always 0 for down
72 final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
73 : TouchTarget.ALL_POINTER_IDS;
74
75 // Clean up earlier touch targets for this pointer id in case they
76 // have become out of sync.
77 removePointersFromTouchTargets(idBitsToAssign);
78
79 final int childrenCount = mChildrenCount;
80 if (newTouchTarget == null && childrenCount != 0) {
81 final float x = ev.getX(actionIndex);
82 final float y = ev.getY(actionIndex);
83 // Find a child that can receive the event.
84 // Scan children from front to back.
85 final ArrayList<View> preorderedList = buildOrderedChildList();
86 final boolean customOrder = preorderedList == null
87 && isChildrenDrawingOrderEnabled();
88 final View[] children = mChildren;
89 for (int i = childrenCount - 1; i >= 0; i--) {
90 final int childIndex = customOrder
91 ? getChildDrawingOrder(childrenCount, i) : i;
92 final View child = (preorderedList == null)
93 ? children[childIndex] : preorderedList.get(childIndex);
94
95 // If there is a view that has accessibility focus we want it
96 // to get the event first and if not handled we will perform a
97 // normal dispatch. We may do a double iteration but this is
98 // safer given the timeframe.
99 if (childWithAccessibilityFocus != null) {
100 if (childWithAccessibilityFocus != child) {
101 continue;
102 }
103 childWithAccessibilityFocus = null;
104 i = childrenCount - 1;
105 }
106
107 if (!canViewReceivePointerEvents(child)
108 || !isTransformedTouchPointInView(x, y, child, null)) {
109 ev.setTargetAccessibilityFocus(false);
110 continue;
111 }
112
113 newTouchTarget = getTouchTarget(child);
114 if (newTouchTarget != null) {
115 // Child is already receiving touch within its bounds.
116 // Give it the new pointer in addition to the ones it is handling.
117 newTouchTarget.pointerIdBits |= idBitsToAssign;
118 break;
119 }
120
121 resetCancelNextUpFlag(child);
122 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
123 // Child wants to receive touch within its bounds.
124 mLastTouchDownTime = ev.getDownTime();
125 if (preorderedList != null) {
126 // childIndex points into presorted list, find original index
127 for (int j = 0; j < childrenCount; j++) {
128 if (children[childIndex] == mChildren[j]) {
129 mLastTouchDownIndex = j;
130 break;
131 }
132 }
133 } else {
134 mLastTouchDownIndex = childIndex;
135 }
136 mLastTouchDownX = ev.getX();
137 mLastTouchDownY = ev.getY();
138 newTouchTarget = addTouchTarget(child, idBitsToAssign);
139 alreadyDispatchedToNewTouchTarget = true;
140 break;
141 }
142
143 // The accessibility focus didn't handle the event, so clear
144 // the flag and do a normal dispatch to all children.
145 ev.setTargetAccessibilityFocus(false);
146 }
147 if (preorderedList != null) preorderedList.clear();
148 }
149
150 if (newTouchTarget == null && mFirstTouchTarget != null) {
151 // Did not find a child to receive the event.
152 // Assign the pointer to the least recently added target.
153 newTouchTarget = mFirstTouchTarget;
154 while (newTouchTarget.next != null) {
155 newTouchTarget = newTouchTarget.next;
156 }
157 newTouchTarget.pointerIdBits |= idBitsToAssign;
158 }
159 }
160 }
161
162 // Dispatch to touch targets.
163 if (mFirstTouchTarget == null) {
164 // No touch targets so treat this as an ordinary view.
165 handled = dispatchTransformedTouchEvent(ev, canceled, null,
166 TouchTarget.ALL_POINTER_IDS);
167 } else {
168 // Dispatch to touch targets, excluding the new touch target if we already
169 // dispatched to it. Cancel touch targets if necessary.
170 TouchTarget predecessor = null;
171 TouchTarget target = mFirstTouchTarget;
172 while (target != null) {
173 final TouchTarget next = target.next;
174 if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
175 handled = true;
176 } else {
177 final boolean cancelChild = resetCancelNextUpFlag(target.child)
178 || intercepted;
179 if (dispatchTransformedTouchEvent(ev, cancelChild,
180 target.child, target.pointerIdBits)) {
181 handled = true;
182 }
183 if (cancelChild) {
184 if (predecessor == null) {
185 mFirstTouchTarget = next;
186 } else {
187 predecessor.next = next;
188 }
189 target.recycle();
190 target = next;
191 continue;
192 }
193 }
194 predecessor = target;
195 target = next;
196 }
197 }
198
199 // Update list of touch targets for pointer up or cancel, if needed.
200 if (canceled
201 || actionMasked == MotionEvent.ACTION_UP
202 || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
203 resetTouchState();
204 } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
205 final int actionIndex = ev.getActionIndex();
206 final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
207 removePointersFromTouchTargets(idBitsToRemove);
208 }
209 }
210
211 if (!handled && mInputEventConsistencyVerifier != null) {
212 mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
213 }
214 return handled;
215 }
我了個叉,和View中這個方法比起來是不是量級瞬間增加不少,不過不用怕,我們着重分析重點部分,一點點來分析,再難也能攻破。
第13行定義一個變量handled,這個變量值在後續會改變,最後作為整個函數傳回值傳回。
第19-24行如果是手指按下的操作則執行cancelAndClearTouchTargets(ev)與resetTouchState()方法邏輯,這兩個方法都幹了什麼呢?看上面的注釋以及方法注釋就知道差不多了,就是清楚一些之前的标記,狀态。不過在cancelAndClearTouchTargets方法裡面有個需要注意的點就是在其方法内調用了clearTouchTargets()方法,這個方法源碼如下:
1 /**
2 * Clears all touch targets.
3 */
4 private void clearTouchTargets() {
5 TouchTarget target = mFirstTouchTarget;
6 if (target != null) {
7 do {
8 TouchTarget next = target.next;
9 target.recycle();
10 target = next;
11 } while (target != null);
12 mFirstTouchTarget = null;
13 }
14 }
第12行将mFirstTouchTarget置為null,這裡我們需要特别注意,其實也是将之前記錄的狀态清空的操作,但是後續會對mFirstTouchTarget進行多次判斷,貫穿整個主線,需要特别注意。
好了,回到dispatchTouchEvent方法中,我們繼續向下分析:
第28-42行。
第28行定義一個intercepted變量,預設false。
第29,30行進行if判斷,如果是ACTION_DOWN事件或者mFirstTouchTarget != null則會進入判斷,在我們第一次按下手指點選的時候顯然執行的是ACTION_DOWN事件,但是到這裡mFirstTouchTarget依然為 null,是以第一次按下手指的時候是可以進入判斷的,我們繼續向下分析。
第31行 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0這又是什麼鬼玩意呢?其實在ViewGroup中有個方法可供外部調用設定mGroupFlags的值(mGroupFlags是ViewGruop中定義的一個變量)。源碼如下:
1 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
2
3 if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
4 // We're already in this state, assume our ancestors are too
5 return;
6 }
7
8 if (disallowIntercept) {
9 mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
10 } else {
11 mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
12 }
13
14 // Pass it up to our parent
15 if (mParent != null) {
16 mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
17 }
18 }
核心就是8-12行代碼,如果我們設定為true,再結合dispatchTouchEvent方法31行代碼 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;會得出disallowIntercept最終為true。同樣我們設定為false,會得出disallowIntercept最終為false。預設情況下disallowIntercept經過位運算為false。(這種按位操作進行判斷的方式很多同學看起來可能不習慣,沒辦法基本功而已,源碼中很多這種判斷方式,希望大家都私下搞明白)
我們繼續向下探究,32行代碼,不用多餘解釋了吧,預設情況下是成立的,除非我們調用requestDisallowInterceptTouchEvent方法設定為false。
33行代碼,調用onInterceptTouchEvent将其傳回值指派給intercepted,onInterceptTouchEvent這個方法大家還記得吧,上面提到過的。系統預設傳回false
好了,到這裡28-42行代碼邏輯我們可以總結一下了:
這部分代碼主要做了一件事就是根據不同設定改變intercepted變量的值。
如果我們調用requestDisallowInterceptTouchEvent設定為true,那麼就不會執行到33行,而是執行36行代碼将intercepted置為false。
預設情況下,是會執行33行代碼,也就是說預設情況下,intercepted值是由onInterceptTouchEvent方法傳回值決定的。
其實onInterceptTouchEvent傳回值主要用來決定ViewGroup是否将觸摸事件傳遞給具體View,起到事件攔截的作用。而如果我們調用requestDisallowInterceptTouchEvent設定為true,則無論onInterceptTouchEvent傳回什麼值,都會将觸摸事件繼續向下傳遞,起到禁止父布局攔截事件的作用。
好了,接下來我們繼續向下分析,51-52行代碼就是檢測分發事件是否取消,然後指派給canceled變量,預設為false,不取消。
56-57行代碼定義newTouchTarget 與alreadyDispatchedToNewTouchTarget 變量,newTouchTarget後續用于紀錄最終接收Touch事件的View。alreadyDispatchedToNewTouchTarget 用于紀錄事件是否傳遞給子View,或者說是否有子View成功處理了Touch事件,有則置為True.
58行代碼判斷如果事件沒被取消或者攔截則if判斷成立進入58-160行代碼邏輯判斷。預設情況下if判斷是成立的。
68-70行代碼判斷是否是各種ACTION_DOWN事件。
80-85行主要做了子View個數的判斷以及擷取子View的集合preorderedList 。
89-94行,for循環preorderedList 集合擷取每個子View。這裡需要說明一下:ViewGroup在addView的時候後添加的會顯示在上層,我們點選View的時候肯定是想先讓浮于上層的View響應觸摸事件,從集合preorderedList 中取View的時候預設情況下是倒序擷取的。(這裡隻是簡單說一下,不是本文重點)
接下來,107-111行判斷目前View是否能相應觸摸事件以及觸摸點是否在View所在區域内,如果均不成立則跳過此次循環繼續下次循環。
122行,這裡就是重點了,if判斷調用dispatchTransformedTouchEvent方法,這個方法是做什麼呢?源碼如下:
1 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
2 View child, int desiredPointerIdBits) {
3 final boolean handled;
4
5 // Canceling motions is a special case. We don't need to perform any transformations
6 // or filtering. The important part is the action, not the contents.
7 final int oldAction = event.getAction();
8 if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
9 event.setAction(MotionEvent.ACTION_CANCEL);
10 if (child == null) {
11 handled = super.dispatchTouchEvent(event);
12 } else {
13 handled = child.dispatchTouchEvent(event);
14 }
15 event.setAction(oldAction);
16 return handled;
17 }
18
19 // Calculate the number of pointers to deliver.
20 final int oldPointerIdBits = event.getPointerIdBits();
21 final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
22
23 // If for some reason we ended up in an inconsistent state where it looks like we
24 // might produce a motion event with no pointers in it, then drop the event.
25 if (newPointerIdBits == 0) {
26 return false;
27 }
28
29 // If the number of pointers is the same and we don't need to perform any fancy
30 // irreversible transformations, then we can reuse the motion event for this
31 // dispatch as long as we are careful to revert any changes we make.
32 // Otherwise we need to make a copy.
33 final MotionEvent transformedEvent;
34 if (newPointerIdBits == oldPointerIdBits) {
35 if (child == null || child.hasIdentityMatrix()) {
36 if (child == null) {
37 handled = super.dispatchTouchEvent(event);
38 } else {
39 final float offsetX = mScrollX - child.mLeft;
40 final float offsetY = mScrollY - child.mTop;
41 event.offsetLocation(offsetX, offsetY);
42
43 handled = child.dispatchTouchEvent(event);
44
45 event.offsetLocation(-offsetX, -offsetY);
46 }
47 return handled;
48 }
49 transformedEvent = MotionEvent.obtain(event);
50 } else {
51 transformedEvent = event.split(newPointerIdBits);
52 }
53
54 // Perform any necessary transformations and dispatch.
55 if (child == null) {
56 handled = super.dispatchTouchEvent(transformedEvent);
57 } else {
58 final float offsetX = mScrollX - child.mLeft;
59 final float offsetY = mScrollY - child.mTop;
60 transformedEvent.offsetLocation(offsetX, offsetY);
61 if (! child.hasIdentityMatrix()) {
62 transformedEvent.transform(child.getInverseMatrix());
63 }
64
65 handled = child.dispatchTouchEvent(transformedEvent);
66 }
67
68 // Done.
69 transformedEvent.recycle();
70 return handled;
71 }
這個方法起初我是看了好長時間,其實核心就是55-66行代碼,對第三個參數child是否為空的判斷,如果為空,則調用父類的dispatchTouchEvent方法,我們知道ViewGroup繼承自View,也就是調用View中的dispatchTouchEvent方法。如果child不為空則調用child自身的dispatchTouchEvent方法,但是要知道child可能是View也可能是ViewGroup。對的,其實這裡是有遞歸的思想的,先分析到這,稍後再回頭來看這個方法。
回到122行代碼,if判斷調用dispatchTransformedTouchEvent方法,按照我們的Demo來分析,這裡第三個參數傳入的是我們自定義的WLButton ,按照上面的分析child不為空,會調用child自身的dispatchTouchEvent方法,這裡也就是View中的dispatchTouchEvent方法,對于View的dispatchTouchEvent方法上篇已經分析過,這裡不再多說。預設情況下,WLButton中dispatchTransformedTouchEvent會傳回true,是以122行中dispatchTransformedTouchEvent方法也會傳回true,表明目前child已經消耗掉此觸摸事件,if判斷成立。
繼續向下看,138-139行代碼為newTouchTarget指派,并将alreadyDispatchedToNewTouchTarget 指派為true,表明已經找到子View并且子View已經消耗此事件。
138行代碼有個需要特别注意的地方,addTouchTarget方法裡面為mFirstTouchTarget同樣也指派了,如下第四行代碼:
1 private TouchTarget addTouchTarget(View child, int pointerIdBits) {
2 TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
3 target.next = mFirstTouchTarget;
4 mFirstTouchTarget = target;
5 return target;
6 }
140行執行break,跳出整個循環。
好了,58-160行代碼到此為止主要邏輯已經講完,我們回頭總結一下主要做了什麼。
其實58-160行代碼主要就做了一件事,在事件為ACTION_DOWN的時候查找有沒有能消耗觸摸事件的子View,周遊每一個子View,如果此View能消耗目前事件則跳出整個循環并且為mFirstTouchTarget 指派。
以上主要是在ACTION_DOWN情況下進行的邏輯判斷,163行以後無論什麼情況下都會執行。
163行對mFirstTouchTarget 進行是否為null判斷,我們知道在ACTION_DOWN情況下會查找是否有子View能消耗目前觸摸事件那麼mFirstTouchTarget 則不為null,如果沒有則為null。
如果沒有找到子View能處理目前觸摸事件,則進入if判斷,165行我們看到又會執行dispatchTransformedTouchEvent方法,注意此時傳入的child為null,還記得我們上面分析的dispatchTransformedTouchEvent方法嗎,當child為null的時候會調用View的dispatchTouchEvent方法,将目前ViewGroup當作普通View對待,依次調用onTouch以及onTouchEvent方法。
mFirstTouchTarget不為null時,繼續傳遞給子View進行處理,依然是遞歸調用dispatchTransformedTouchEvent()方法來實作。
到你應該明白為什麼事件傳遞是先傳給子View處理,如果子View沒有能處理的那麼在傳遞給父類處理了吧,對的源碼中關鍵就是dispatchTransformedTouchEvent方法中第三個參數是否為null。
好了,到此為止ViewGroup中我想說的也就講解完了。如果你能将上述都了解了,那麼開頭的那幾個小問題應該都不是問題了。
ViewGroup的事件傳遞機制有些地方還是挺繞的,不過确實很重要,希望大家靜下心來好好了解其中核心點。
最後便于了解,附上一張流程圖,如果圖上每一個關鍵點你都能回憶起源碼中相關核心代碼那麼你已經掌握差不多了:
下一篇,我們讨論一下Activity中的事件傳遞機制,過了ViewGroup這關,其餘都是毛毛雨了。
轉載于:https://www.cnblogs.com/leipDao/p/7451692.html