Android事件分发机制之ACTION_DOWN
前言
Android的事件分發(fā)機制也是老生常談了,這篇文章并不是籠統(tǒng)的介紹這個機制,而是針對ACTION_DOWN這個事件探討相關(guān)的細(xì)節(jié)。
dispatchTouchEvent
說到Android事件分發(fā),一定繞不開dispatchTouchEvent函數(shù),View和ViewGroup的該函數(shù)有很大的不同。
我們來看看ViewGroup的dispatchTouchEvent函數(shù),它的部分源碼如下:
@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; }可以看到整個分發(fā)有幾個關(guān)鍵因素:intercepted、canceled、mFirstTouchTarget、alreadyDispatchedToNewTouchTarget。
intercepted、canceled比較好理解,重點來說說后面兩個因素的是如何影響整個分發(fā)的。
ACTION_DOWN
一個完整的事件應(yīng)該包含ACTION_DOWN、ACTION_MOVE、ACTION_UP。其中ACTION_DOWN是開始也是關(guān)鍵。
從上面dispatchTouchEvent源碼中可以看到首先單獨對ACTION_DOWN事件進(jìn)行了處理,對所有child進(jìn)行遍歷,是從后向前遍歷的,所以在處理上面的也就是最后添加的view會先得到事件
for (int i = childrenCount - 1; i >= 0; i--) {對于每個child,會先判斷事件是不是發(fā)生在它的區(qū)域內(nèi),不是則不處理:
if (!child.canReceivePointerEvents()|| !isTransformedTouchPointInView(x, y, child, null)) {ev.setTargetAccessibilityFocus(false);continue; }如果在區(qū)域內(nèi),則繼續(xù)執(zhí)行,下面dispatchTransformedTouchEvent這個函數(shù)就是下發(fā)事件的,我們來看下部分源碼:
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; }有不少邏輯在里面,但是仔細(xì)觀察可以發(fā)現(xiàn),不論那個條件,執(zhí)行的代碼都比較類似,如下:
if (child == null) {handled = super.dispatchTouchEvent(event); } else {...handled = child.dispatchTouchEvent(event);... }當(dāng)child不為null的時候,執(zhí)行child的dispatchTouchEvent;為null時執(zhí)行父類的dispatchTouchEvent,即View的dispatchTouchEvent函數(shù),這個函數(shù)里會執(zhí)行onTouchEvent等。所以在ViewGroup是沒有onTouchEvent等函數(shù)的代碼。
由于這時child不為null,所以執(zhí)行了child的dispatchTouchEvent函數(shù).
回到之前的ACTION_DOWN流程中,根據(jù)dispatchTransformedTouchEvent返回值進(jìn)行不同的處理:
返回ture
如果返回true,即有一個child消費了ACTION_DOWN事件,可以看到后續(xù)執(zhí)行了addTouchTarget函數(shù),同時將alreadyDispatchedToNewTouchTarget置為true。
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {...newTouchTarget = addTouchTarget(child, idBitsToAssign);alreadyDispatchedToNewTouchTarget = true;break; }addTouchTarget函數(shù)源碼如下:
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; }關(guān)鍵的一點是對mFirstTouchTarget進(jìn)行了賦值。所以說true的處理是為mFirstTouchTarget賦值,將alreadyDispatchedToNewTouchTarget置為true
最后的break則跳出循環(huán),不再遍歷其他child。
返回false
如果返回false,即沒有任何一個child消費ACTION_DOWN事件,直接跳過if代碼,這樣mFirstTouchTarget為null。
mFirstTouchTarget
那么mFirstTouchTarget、alreadyDispatchedToNewTouchTarget這兩個屬性在分發(fā)過程中的作用是什么?我們分別來說:
1、mFirstTouchTarget為null
當(dāng)mFirstTouchTarget為null,進(jìn)入if語句執(zhí)行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分發(fā),而是調(diào)用了super.dispatchTouchEvent,即ViewGroup自己處理
這樣ACTION_DOWN事件分發(fā)完了。其他事件分發(fā)時由于不再走ACTION_DOWN的處理過程,所以mFirstTouchTarget會一直為null,所以其他事件也不再向下分發(fā)了,直接ViewGroup自己處理
2、mFirstTouchTarget不為null
當(dāng)mFirstTouchTarget不為null,進(jìn)入else語句中,會執(zhí)行一個while循環(huán)
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事件已經(jīng)響應(yīng),所以這里的alreadyDispatchedToNewTouchTarget是為了防止重復(fù)分發(fā)ACTION_DOWN事件。
這樣ACTION_DOWN事件分發(fā)完成后,分發(fā)其他事件時,alreadyDispatchedToNewTouchTarget被重新賦值false,由于不再走ACTION_DOWN的處理過程,所以alreadyDispatchedToNewTouchTarget就一直是false了,而mFirstTouchTarget會一直保持不變。在這個while循環(huán)中則會執(zhí)行else語句,通過執(zhí)行dispatchTransformedTouchEvent將事件直接分發(fā)給mFirstTouchTarget對應(yīng)的child,即之前消費ACTION_DOWN事件的child。
總結(jié)
這樣我們得到幾個結(jié)論:
總結(jié)
以上是生活随笔為你收集整理的Android事件分发机制之ACTION_DOWN的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 说一说Android事件分发中的requ
- 下一篇: Android中的资源复用小技巧