Android事件分发机制之ACTION_DOWN
前言
Android的事件分發機制也是老生常談了,這篇文章并不是籠統的介紹這個機制,而是針對ACTION_DOWN這個事件探討相關的細節。
dispatchTouchEvent
說到Android事件分發,一定繞不開dispatchTouchEvent函數,View和ViewGroup的該函數有很大的不同。
我們來看看ViewGroup的dispatchTouchEvent函數,它的部分源碼如下:
@Override public boolean dispatchTouchEvent(MotionEvent ev) {...if (onFilterTouchEventForSecurity(ev)) {...boolean alreadyDispatchedToNewTouchTarget = false;if (!canceled && !intercepted) {View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()? findChildWithAccessibilityFocus() : null;if (actionMasked == MotionEvent.ACTION_DOWN|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {final int actionIndex = ev.getActionIndex(); // always 0 for downfinal int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex): TouchTarget.ALL_POINTER_IDS;removePointersFromTouchTargets(idBitsToAssign);final int childrenCount = mChildrenCount;if (newTouchTarget == null && childrenCount != 0) {...for (int i = childrenCount - 1; i >= 0; i--) {...if (!child.canReceivePointerEvents()|| !isTransformedTouchPointInView(x, y, child, null)) {ev.setTargetAccessibilityFocus(false);continue;}...if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {...newTouchTarget = addTouchTarget(child, idBitsToAssign);alreadyDispatchedToNewTouchTarget = true;break;}// The accessibility focus didn't handle the event, so clear// the flag and do a normal dispatch to all children.ev.setTargetAccessibilityFocus(false);}if (preorderedList != null) preorderedList.clear();}...}}// Dispatch to touch targets.if (mFirstTouchTarget == null) {// No touch targets so treat this as an ordinary view.handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);} else {// Dispatch to touch targets, excluding the new touch target if we already// dispatched to it. Cancel touch targets if necessary.TouchTarget predecessor = null;TouchTarget target = mFirstTouchTarget;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;}...}predecessor = target;target = next;}}...}if (!handled && mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);}return handled; }可以看到整個分發有幾個關鍵因素:intercepted、canceled、mFirstTouchTarget、alreadyDispatchedToNewTouchTarget。
intercepted、canceled比較好理解,重點來說說后面兩個因素的是如何影響整個分發的。
ACTION_DOWN
一個完整的事件應該包含ACTION_DOWN、ACTION_MOVE、ACTION_UP。其中ACTION_DOWN是開始也是關鍵。
從上面dispatchTouchEvent源碼中可以看到首先單獨對ACTION_DOWN事件進行了處理,對所有child進行遍歷,是從后向前遍歷的,所以在處理上面的也就是最后添加的view會先得到事件
for (int i = childrenCount - 1; i >= 0; i--) {對于每個child,會先判斷事件是不是發生在它的區域內,不是則不處理:
if (!child.canReceivePointerEvents()|| !isTransformedTouchPointInView(x, y, child, null)) {ev.setTargetAccessibilityFocus(false);continue; }如果在區域內,則繼續執行,下面dispatchTransformedTouchEvent這個函數就是下發事件的,我們來看下部分源碼:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) {...if (newPointerIdBits == oldPointerIdBits) {if (child == null || child.hasIdentityMatrix()) {if (child == null) {handled = super.dispatchTouchEvent(event);} else {final float offsetX = mScrollX - child.mLeft;final float offsetY = mScrollY - child.mTop;event.offsetLocation(offsetX, offsetY);handled = child.dispatchTouchEvent(event);event.offsetLocation(-offsetX, -offsetY);}return handled;}transformedEvent = MotionEvent.obtain(event);} else {transformedEvent = event.split(newPointerIdBits);}// Perform any necessary transformations and dispatch.if (child == null) {handled = super.dispatchTouchEvent(transformedEvent);} else {final float offsetX = mScrollX - child.mLeft;final float offsetY = mScrollY - child.mTop;transformedEvent.offsetLocation(offsetX, offsetY);if (! child.hasIdentityMatrix()) {transformedEvent.transform(child.getInverseMatrix());}handled = child.dispatchTouchEvent(transformedEvent);}// Done.transformedEvent.recycle();return handled; }有不少邏輯在里面,但是仔細觀察可以發現,不論那個條件,執行的代碼都比較類似,如下:
if (child == null) {handled = super.dispatchTouchEvent(event); } else {...handled = child.dispatchTouchEvent(event);... }當child不為null的時候,執行child的dispatchTouchEvent;為null時執行父類的dispatchTouchEvent,即View的dispatchTouchEvent函數,這個函數里會執行onTouchEvent等。所以在ViewGroup是沒有onTouchEvent等函數的代碼。
由于這時child不為null,所以執行了child的dispatchTouchEvent函數.
回到之前的ACTION_DOWN流程中,根據dispatchTransformedTouchEvent返回值進行不同的處理:
返回ture
如果返回true,即有一個child消費了ACTION_DOWN事件,可以看到后續執行了addTouchTarget函數,同時將alreadyDispatchedToNewTouchTarget置為true。
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {...newTouchTarget = addTouchTarget(child, idBitsToAssign);alreadyDispatchedToNewTouchTarget = true;break; }addTouchTarget函數源碼如下:
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);target.next = mFirstTouchTarget; //初始mFirstTouchTarget為null,所以這里next是nullmFirstTouchTarget = target;return target; }關鍵的一點是對mFirstTouchTarget進行了賦值。所以說true的處理是為mFirstTouchTarget賦值,將alreadyDispatchedToNewTouchTarget置為true
最后的break則跳出循環,不再遍歷其他child。
返回false
如果返回false,即沒有任何一個child消費ACTION_DOWN事件,直接跳過if代碼,這樣mFirstTouchTarget為null。
mFirstTouchTarget
那么mFirstTouchTarget、alreadyDispatchedToNewTouchTarget這兩個屬性在分發過程中的作用是什么?我們分別來說:
1、mFirstTouchTarget為null
當mFirstTouchTarget為null,進入if語句執行dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS)
if (mFirstTouchTarget == null) {// No touch targets so treat this as an ordinary view.handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS); }由于child是null,在dispatchTransformedTouchEvent代碼中可以看到不再給任何child分發,而是調用了super.dispatchTouchEvent,即ViewGroup自己處理
這樣ACTION_DOWN事件分發完了。其他事件分發時由于不再走ACTION_DOWN的處理過程,所以mFirstTouchTarget會一直為null,所以其他事件也不再向下分發了,直接ViewGroup自己處理
2、mFirstTouchTarget不為null
當mFirstTouchTarget不為null,進入else語句中,會執行一個while循環
else {// Dispatch to touch targets, excluding the new touch target if we already// dispatched to it. Cancel touch targets if necessary.TouchTarget predecessor = null;TouchTarget target = mFirstTouchTarget;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;}...}predecessor = target;target = next;} }這時由于alreadyDispatchedToNewTouchTarget為true,所以直接給handled賦值true并不做任何處理。因為之前代碼中child對ACTION_DOWN事件已經響應,所以這里的alreadyDispatchedToNewTouchTarget是為了防止重復分發ACTION_DOWN事件。
這樣ACTION_DOWN事件分發完成后,分發其他事件時,alreadyDispatchedToNewTouchTarget被重新賦值false,由于不再走ACTION_DOWN的處理過程,所以alreadyDispatchedToNewTouchTarget就一直是false了,而mFirstTouchTarget會一直保持不變。在這個while循環中則會執行else語句,通過執行dispatchTransformedTouchEvent將事件直接分發給mFirstTouchTarget對應的child,即之前消費ACTION_DOWN事件的child。
總結
這樣我們得到幾個結論:
總結
以上是生活随笔為你收集整理的Android事件分发机制之ACTION_DOWN的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 说一说Android事件分发中的requ
- 下一篇: Android中的资源复用小技巧