android的TouchEvent派發機制的分析

sunhang發表於2019-01-06

android在view樹結構中的touch訊息派發,主要是由類ViewGroup的方法dispatchTouchEvent完成的。最近閱讀了它的原始碼,做了以下分析。

@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) { 
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);

} // If the event targets the accessibility focused view and this is it, start // normal event dispatch. Maybe a descendant is what will handle the click. if (ev.isTargetAccessibilityFocus() &
&
isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);

} boolean handled = false;
// 這裡通常會check通過的,在touch訊息派發時,onFilterTouchEventForSecurity一直返回true(通過單步除錯發現它一直返回true)。 if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action &
MotionEvent.ACTION_MASK;
// Handle an initial down. if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture. // The framework may have dropped the up or cancel event for the previous gesture // due to an app switch, ANR, or some other state change. cancelAndClearTouchTargets(ev);
resetTouchState();

} // Check for interception. final boolean intercepted;
// 假如是down訊息,或者mFirstTouchTarget不為null,那麼就要判斷一下是否要攔截該訊息。 // 那麼mFirstTouchTarget什麼時候不為null呢?是在當子分支上的某個view已經處理了之前的訊息時 // 所說的某個view的onTouchEvent返回了true,進而它的dispatchTouchEvent也返回true的時候 if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags &
FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
// restore action in case it was changed
} else {
intercepted = false;

}
} else {
// There are no touch targets and this action is not an initial down // so this view group continues to intercept touches. // // 當不是down訊息,並且mFirstTouchTarget為null,此時必然要攔截訊息。 // 這也解釋了為什麼某個子view在onTouchEvent中處理down訊息返回false之後,不會接收後續的訊息了。 intercepted = true;

} // If intercepted, start normal event dispatch. Also if there is already // a view that is handling the gesture, do normal event dispatch. if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);

} // Check for cancelation. final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed. final boolean split = (mGroupFlags &
FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
// 當不需要取消訊息派發並且不需要攔截訊息派發時,執行這個if裡的程式碼 if (!canceled &
&
!intercepted) {
// If the event is targeting accessibility focus we give it to the // view that has accessibility focus and if it does not handle it // we clear the flag and dispatch the event to all children as usual. // We are looking up the accessibility focused host to avoid keeping // state since these events are very rare. View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus() ? findChildWithAccessibilityFocus() : null;
// 當是down訊息或者是pointer_down訊息時,執行這個if裡的程式碼 if (actionMasked == MotionEvent.ACTION_DOWN || (split &
&
actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex();
// always 0 for down final int idBitsToAssign = split ? 1 <
<
ev.getPointerId(actionIndex) : TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they // have become out of sync. removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
//newTouchTarget一定是null(從前邊的宣告到這裡,沒有看到對它的賦值操作),同時viewgroup有子節點,執行以下if裡的程式碼。 if (newTouchTarget == null &
&
childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event. // Scan children from front to back. final ArrayList<
View>
preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null &
&
isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
// 迴圈處理每一個子節點(view) for (int i = childrenCount - 1;
i >
= 0;
i--) {
final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex);
// If there is a view that has accessibility focus we want it // to get the event first and if not handled we will perform a // normal dispatch. We may do a double iteration but this is // safer given the timeframe. if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;

} childWithAccessibilityFocus = null;
i = childrenCount - 1;

} // 如果該view不可以接收touch訊息, //或者touch訊息的座標沒有落到該view的位置中, //則跳過這一次迭代, //進而執行下一次的迭代(也就是用同樣的條件檢查下一個view) if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;

} // 終於有個view可以接收touch訊息,同時touch訊息也落在它上面。 //這個時候通過該view來獲取與它對應的touch target。 //touch target是什麼呢? //touch target是一個連結串列中的節點, //這個連結串列中維護的所有touch target表明有多少view可以接收訊息, //每個touch target中不僅有next欄位, // 還有個child欄位來引用可以接收touch訊息的view。 //首先根據該view,檢視它所對應的在連結串列中的TouchTarget。 //如果找到該touch target,則表明之前該view接收過訊息, //還記得這塊程式碼是處理down訊息和pointer_down訊息, //所以此時不要處理,直接break退出for迴圈。 newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds. // Give it the new pointer in addition to the ones it is handling. newTouchTarget.pointerIdBits |= idBitsToAssign;
break;

} resetCancelNextUpFlag(child);
// 該子節點view沒有處理過down或者pointer_down訊息, // 則通過呼叫dispatchTransformedTouchEvent來處理它。 // dispatchTransformedTouchEvent還有可能對pointer_down訊息轉換成down訊息,然後呼叫view的dispatchTouchEvent。 // 假如它返回了true,表明子節點view消費了該訊息(down、pointer_down)。 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();
// 建立一個新的TouchTarget新增到連結串列中,這個新的TouchTarget的child欄位指向該view 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();

} if (newTouchTarget == null &
&
mFirstTouchTarget != null) {
// Did not find a child to receive the event. // Assign the pointer to the least recently added target. newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;

} newTouchTarget.pointerIdBits |= idBitsToAssign;

}
}
} // Dispatch to touch targets. if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view. // 假如沒有子節點view消費該touch訊息,則呼叫方法dispatchTransformedTouchEvent, // 第3個引數傳入null,在該方法中可以呼叫super的dispatchTouchEvent, // 在super的dispatchTouchEvent中呼叫onTouchEvent或者mOnTouchListener來處理該touch訊息。 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;
// 迴圈遍歷每個touch target。假如某個touch target在上邊的程式碼中已經處理過 // (alreadyDispatchedToNewTouchTarget為true,target == newTouchTarget為true), // 就跳過它。假如沒有處理過,就呼叫dispatchTransformedTouchEvent把touch訊息派發給子節點view 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) {
// 把該target從列表中刪除掉 if (predecessor == null) {
// 若target是在列表頭部,則從頭部刪除掉 mFirstTouchTarget = next;

} else {
// 若target是在列表中間,則從列表中間刪除掉 predecessor.next = next;

} target.recycle();
target = next;
continue;

}
} predecessor = target;
target = next;

}
} // Update list of touch targets for pointer up or cancel, if needed. if (canceled || actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
// 如果訊息是取消,則重置狀態,就是銷燬連結串列,mFirstTouchTarget也置為null resetTouchState();

} else if (split &
&
actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 <
<
ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);

}
} if (!handled &
&
mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);

} return handled;

}複製程式碼

來源:https://juejin.im/post/5c31fd056fb9a04a0821d4d8

相關文章