The dispatch event code of ViewGroup is mainly implemented by the dispatchTouchEvent(MotionEvent ev) method, as follows
@Override public 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; 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; 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. 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 isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE; final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0 && !isMouseEvent; TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; 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; 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; if (newTouchTarget == null && childrenCount != 0) { final float x = isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex); final float y = isMouseEvent ? ev.getYCursorPosition() : 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; for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex); if (!child.canReceivePointerEvents() || !isTransformedTouchPointInView(x, y, child, null)) { continue; } 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); 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(); 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. 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; } if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { 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) { 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; }
mInputEventConsistencyVerifier is used for debugging and is null in the official version
Lines 9-10 deal with auxiliary functions. This takes effect only when the auxiliary function is enabled in the setting. The logic of this part will not be analyzed for the time being.
The variable declared in line 13 represents whether the event is consumed. The initial value is false.
In line 14, the onfiltertoucheeventforsecurity method performs security check to prevent malicious software from misleading users. If the check fails, the control will discard the event.
/** * Filter the touch event to apply security policies. * * @param event The motion event to be filtered. * @return True if the event should be dispatched, false if the event should be dropped. * * @see #getFilterTouchesWhenObscured */ public boolean onFilterTouchEventForSecurity(MotionEvent event) { //noinspection RedundantIfStatement if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0 && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) { // Window is obscured, drop this touch. return false; } return true; }
The onFilterTouchEventForSecurity method mainly filters the touch events when the current Window is blocked.
then go back to the event processing in ViewGroup, get the event action, and compare it with MotionEvent.ACTION_MASK does & operation because ACTION_POINTER_DOWN and action_ POINTER_ The up action includes pointer index.
The event starts and initializes
Lines 19 to 24, where the event is action_ In the case of down, do some initialization processing. Normal events may be caused by ACTION_DOWN,ACTION_ MOVE,ACTION_ POINTER_ Down (more than one finger), action_ POINTER_ Up (when more than one finger), ACTION_UP composition. And ACTION_DOWN is the beginning of the event
At this time, the state and initialization are set. cancelAndClearTouchTargets() and resetTouchState() are called
/** * Cancels and clears all touch targets. */ private void cancelAndClearTouchTargets(MotionEvent event) { if (mFirstTouchTarget != null) { boolean syntheticEvent = false; if (event == null) { final long now = SystemClock.uptimeMillis(); event = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); event.setSource(InputDevice.SOURCE_TOUCHSCREEN); syntheticEvent = true; } for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) { resetCancelNextUpFlag(target.child); dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits); } clearTouchTargets(); if (syntheticEvent) { event.recycle(); } } }
the cancelAndClearTouchTargets method can know the of canceling and cleaning up the touch object by name. It starts to judge that the member variable mFirstTouchTarget is not equal to null. To focus on mFirstTouchTarget, it is a linked list that connects all the child views of the current View Group that need to process the event. This linked list is when the event is ACTION_DOWN and action_ POINTER_ It is determined when down, and subsequent events will be handled by the object connected by mFirstTouchTarget. Normally, mFirstTouchTarget should be null at the end of an event. The cancelAndClearTouchTargets method synthesizes an action when mFirstTouchTarget is not null_ The cancel event is sent to the object. Then the clearTouchTargets() method is called.
/** * Clears all touch targets. */ private void clearTouchTargets() { TouchTarget target = mFirstTouchTarget; if (target != null) { do { TouchTarget next = target.next; target.recycle(); target = next; } while (target != null); mFirstTouchTarget = null; } }
clearTouchTargets() is used to clear all touch objects. All touch objects will call recycle(), and mFirstTouchTarget will be set to null at the end.
public void recycle() { if (child == null) { throw new IllegalStateException("already recycled once"); } synchronized (sRecycleLock) { if (sRecycledCount < MAX_RECYCLED) { next = sRecycleBin; sRecycleBin = this; sRecycledCount += 1; } else { next = null; } child = null; } } }
the touch object has a cache, and the maximum capacity of the cache is MAX_RECYCLED, its value is 32, and the recycle() method will be used when the cache capacity is less than max_ When recursive, put the TouchTarget object into the cache capacity, and set the child member variable of the object to null.
cancelAndClearTouchTargets will finally check the Motion object. If it is synthetic, call the recycle() of the Motion object.
The dispatchTouchEvent() method is in action_ Next, call resetTouchState() under down,
The resetTouchState() code is as follows
/** * Resets all touch state in preparation for a new cycle. */ private void resetTouchState() { clearTouchTargets(); resetCancelNextUpFlag(this); mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; mNestedScrollAxes = SCROLL_AXIS_NONE; }
this method resets all touch states for a new event cycle. clearTouchTargets() will also be called, followed by the resetCancelNextUpFlag method,
/** * Resets the cancel next up flag. * Returns true if the flag was previously set. */ private static boolean resetCancelNextUpFlag(@NonNull View view) { if ((view.mPrivateFlags & PFLAG_CANCEL_NEXT_UP_EVENT) != 0) { view.mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT; return true; } return false; }
this method checks the cancel next up flag of the View. If the View is set, the flag is cleared and returns true. If there is no such flag, false is returned. Cancel next up flag will be used to judge the action in the subsequent processing of the event_ Cancel event.
By searching for pflag in the code of View_ CANCEL_ NEXT_ UP_ The setting position of event flag. It is found that this flag will be set in performButtonActionOnTouchDown() and onStartTemporaryDetach(). performButtonActionOnTouchDown() is executed when the right mouse button is pressed, and onStartTemporaryDetach() is executed when the View needs to be temporarily detached from the parent control.
resetTouchState() then clears the flag_ DISALLOW_ The intercept flag also sets mNestedScrollAxes to SCROLL_AXIS_NONE.
Get intercept status
then go back to the dispatchTouchEvent() method. Lines 28 to 42 are used to judge whether to intercept the event. Judge whether to intercept the event when the event is ACTION_DOWN or when the linked list of touch objects is not null. If the current control is set to prohibit interception, the obtained interception status will be false. If it is not set, onInterceptTouchEvent() will be called to get the interception status intercepted. The onintercepttouchevent () method is used to set whether to intercept events, and this method exists only in ViewGroup type controls. This method does not intercept touch events by default.
the next 46 to 48 lines, according to the comments, mean that if the current control is in the blocking state or there is already a View for handling gestures, clear the state of the auxiliary function flag and dispatch normal events.
next, judge whether to cancel the status and set the value of the cancelled variable through the resetCancelNextUpFlag function mentioned above, or the current event Action is ACTION_CANCEL.
55 to 57 lines of code to determine whether the event is decomposed. If the mGroupFlags of the current control have flags_ SPLIT_ MOTION_ Events flag, and the current event is not a mouse event, it can be decomposed. Event decomposition is used to handle multi touch. This flag is added in mGroupFlags by default in Honeywell version and later.
next, define two variables newTouchTarget and alreadyDispatchedToNewTouchTarget. newTouchTarget is the newly found touch object, and alreadyDispatchedToNewTouchTarget is a boolean representing whether the current event has been distributed to the new touch object.
Find the distribution object of touch event
The code for finding the distribution object of touch events is proposed separately as follows:
TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; 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; 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; if (newTouchTarget == null && childrenCount != 0) { final float x = isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex); final float y = isMouseEvent ? ev.getYCursorPosition() : 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; for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex); if (!child.canReceivePointerEvents() || !isTransformedTouchPointInView(x, y, child, null)) { continue; } 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); 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(); 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; } } }
the above code is mainly used to find the distribution object of touch events.
when it is not in the cancel state and not in the intercept state, start looking for the touch object of the current event. Note that these two states are major prerequisites, which should be considered when analyzing code.
9 lines of code. If the event is isTargetAccessibilityFocus, first find the View of the accessibility focus.
then go down 12 to 14 lines of code to find touch events in three cases. 1. The event type is ACTION_DOWN 2, split is true and the event type is ACTION_POINTER_DOWN 3. The event type is ACTION_HOVER_MOVE.
the first corresponds to the first finger pressing, and the second corresponds to the second finger pressing (when ViewGroup is initialized, the targetSdkVersion configured in the AndroidManifest file is greater than or equal to API 11. By default, FLAG_SPLIT_MOTION_EVENTS flag is set, so the split upward of target targetSdkVersion API 11 is true), The third corresponds to the mouse hovering movement. Considering only touch events, you can ignore the third case first.
get the current event pointer index first, and then get the bit value of id through it. Because split is true, the bit of the corresponding id in idBitsToAssign is 1. Call removePointersFromTouchTargets() to clear the flag bits of the receiving id of the object in the touch event distribution object chain. If the flag bits of the receiving id of the distribution object are all 0, the touch object will be deleted from the object chain.
the pointer index of the event starts from 0. If the event is ACTION_DOWN, the pointer index is 0, and in multi touch, if the finger is raised, the value will change. The id of the event also starts from 0, and the value will not change with the lifting of the finger. Therefore, finding the event of a specific finger needs to be based on the id of the event.
newTouchTarget represents a new touch object, which is currently null. If the number of child controls of the current View Group object is not 0, go to find it. First get the x and y coordinate values. You can see the x and y coordinate values obtained by using getXCursorPosition() and getYCursorPosition() if it is a mouse event. Then call buildTouchDispatchChildList() to obtain the preorderedList set of child views, which is sorted from small to large according to the position of the Z coordinate of the child views. Take a look at the code for this method
public ArrayList<View> buildTouchDispatchChildList() { return buildOrderedChildList(); } ............ ArrayList<View> buildOrderedChildList() { final int childrenCount = mChildrenCount; if (childrenCount <= 1 || !hasChildWithZ()) return null; if (mPreSortedChildren == null) { mPreSortedChildren = new ArrayList<>(childrenCount); } else { // callers should clear, so clear shouldn't be necessary, but for safety... mPreSortedChildren.clear(); mPreSortedChildren.ensureCapacity(childrenCount); } final boolean customOrder = isChildrenDrawingOrderEnabled(); for (int i = 0; i < childrenCount; i++) { // add next child (in child order) to end of list final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); final View nextChild = mChildren[childIndex]; final float currentZ = nextChild.getZ(); // insert ahead of any Views with greater Z int insertIndex = i; while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) { insertIndex--; } mPreSortedChildren.add(insertIndex, nextChild); } return mPreSortedChildren; }
this method first calls another function builderedchildlist(), and continues to enter the method. If the number of child views is less than or equal to 1 or the Z coordinate value of child views is 0, the method returns null. This means that regardless of the Z coordinate value, you can either find the touch object in the order of adding the sub views or in the custom order. Here, to define the ViewGroup custom order, you need to call the setchildredrawingordered() function and override the getChildDrawingOrder(int childCount, int drawingPosition) function. The former is used to set the flag of mGroupFlags_ USE_ CHILD_ DRAWING_ Order flag, which is used to get the sequence number. buildOrderedChildList() then puts the child View Z coordinate values into the member variable mPreSortedChildren in descending order and returns them as the result.
return to line 32 of the previous function. If the result preorderedList returned by buildTouchDispatchChildList() is not null, the value of the variable customOrder is false, because the result set has been sorted according to the Z-axis coordinate size, and then it will be searched directly according to the order in the variable preorderedList set.
if the result preorderedList returned by buildTouchDispatchChildList() is null and setchildredrawingorderenabled() has been called, the result returned by isChildrenDrawingOrderEnabled() will be true and the value of the natural variable customOrder will be true. Next, call the getAndVerifyPreorderedIndex() function to get the search sequence number of the corresponding child View through the rewritten getChildDrawingOrder(int childCount, int drawingPosition). In this case, we do not set the Z coordinate value of the sub View, and then want to change the search order of the sub View.
if the returned result preorderedList is null and there is no setchildredrawingordered(), the touch object will be found in the reverse order of the addition order of the child views.
the cycle starting from line 35 is to find the touch object according to the three situations I described above. Notice that the order you see is looking forward from childrenCount - 1. How to judge whether the sub view can handle this event? From 40 to 43 lines of code, you can know that view is canreceivepointevents() and isTransformedTouchPointInView(x, y, child, null).
/** * Returns whether this view can receive pointer events. * * @return {@code true} if this view can receive pointer events. * @hide */ protected boolean canReceivePointerEvents() { return (mViewFlags & VISIBILITY_MASK) == VISIBLE || getAnimation() != null; }
1. View is canReceivePointerEvents(). It can be seen from the code that the view must be visible or the related animation is planned or being played, and is not null.
/** * Returns true if a child view contains the specified point when transformed * into its coordinate space. * Child must not be null. * @hide */ @UnsupportedAppUsage protected boolean isTransformedTouchPointInView(float x, float y, View child, PointF outLocalPoint) { final float[] point = getTempLocationF(); point[0] = x; point[1] = y; transformPointToViewLocal(point, child); final boolean isInView = child.pointInView(point[0], point[1]); if (isInView && outLocalPoint != null) { outLocalPoint.set(point[0], point[1]); } return isInView; } .................................... public void transformPointToViewLocal(float[] point, View child) { point[0] += mScrollX - child.mLeft; point[1] += mScrollY - child.mTop; if (!child.hasIdentityMatrix()) { child.getInverseMatrix().mapPoints(point); } }
2. isTransformedTouchPointInView is used to judge whether the coordinate point of the touch event falls within the range of the View. Call the transformPointToViewLocal() method to convert the coordinate value into the coordinate value in the child View, and then judge whether the touch point is within the range of the View through the pointInView() method.
if these two conditions are met, the touch object is found. After finding the touch object, return to line 45 and call getTouchTarget(child) to find it in the linked list of touch objects.
/** * Gets the touch target for specified child view. * Returns null if not found. */ private TouchTarget getTouchTarget(@NonNull View child) { for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) { if (target.child == child) { return target; } } return null; }
the touch distribution object is described by the TouchTarget class, and a linked list is maintained through the internal member next of the TouchTarget class. If the obtained touch distribution object is not null, it indicates that the object has been added to the linked list. This should correspond to MotionEvent.ACTION_POINTER_DOWN (not when the first finger is pressed), and it is pressed on the same control.
code lines 46 to 51. If it is found that the control is already in the touch object linked list, set the id bit bit of the control to be processed to the touch object to jump out of the loop. The processing of this event needs to go to the following code.
line 53 of the code calls resetCancelNextUpFlag(child) to remove the pflag of the control_ CANCEL_ NEXT_ UP_ Event flag.
then the dispatchTransformedTouchEvent() method will be called to handle the event of the control. The method dispatchTransformedTouchEvent() will also be used later, and we will talk about it later. Here, if this method returns true, it means that the control consumes the current event and sets the value of the member variable mLastTouchDownTime. Lines 57 to 67 are used to set mLastTouchDownIndex. preorderedList is a collection of child views arranged from small to large according to the Z-axis coordinate size. If preorderedList is not null, look at the code comments to find the order of child views of the current consumption event in mChildren. Because the variable childIndex is in the order of the preorderedList set, but there may be some problems in its code. Because the variable children itself is mChildren, I feel that this child should be written as preorderedList. Then set mLastTouchDownX and mLastTouchDownY, and then call addTouchTarget() method to add the control to the touch event object linked list. Subsequent related events will be handled by the control, and the variable alreadyDispatchedToNewTouchTarget will be set to true, and the loop will jump out. Setting the value of the variable alreadyDispatchedToNewTouchTarget indicates that this event has been processed, and the following processing code will skip. If the dispatchTransformedTouchEvent() method returns false, it will then loop through the next control and look for the touch event object.
after the for loop for finding the touch event object is completed, if the preorderedList is not null, it will be cleared because it is no longer needed.
find the last code of the touch object, i.e. line 82 to line 90 if. newTouchTarget == null && mFirstTouchTarget != In this case, what is it? This situation should be multi touch. When the touch event object has been found, the other finger also pressed, but the touch event of this finger did not find the corresponding child View for processing, that is, the dispatchtransformatedtouchevent() method of each child View above returned false. At this time, newtouchtarget = = null, mfirsttouchtarget= null. Take a look at this process, and the newtouchtarget will be set to the last one in the mfirsttouchtarget list. The last touch object in the mfirsttouchtarget linked list corresponds to the touch object first added to mfirsttouchtarget, and the later added objects will be listed in front of the linked list. After setting the newtouchtarget, the id bit of the event corresponding to the pointerIdBits of the newtouchtarget indicates that the touch object receives the event.
Dispatch touch object processing events
this code is as follows:
// 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; } if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } }
after finding the touch object in front, the corresponding event will be sent. The code is mainly processed through the dispatchTransformedTouchEvent() event. In front of looking for the touch event object, the action is also executed through this method_ Down and action_ POINTER_ Of the down event.
the previous code for finding the touch object is when the event type is ACTION_DOWN and action_ POINTER_ It is executed when down. The code for dispatching events handles all event types. It's just that if you've dealt with action before_ Down and action_ POINTER_ After down, this code avoids repeated processing through the variable alreadyDispatchedToNewTouchTarget.
if the mFirstTouchTarget object is null, it means that the corresponding touch object was not found in the child View of the ViewGroup. If the touch object is not found, the parameter child will be set to null, which is left to the ViewGroup itself.
if the mFirstTouchTarget object is not null, you need to dispatch the touch event to it for processing. Loop the linked list of touch objects and let each touch object handle events. You can see that if the alreadyDispatchedToNewTouchTarget variable is true and the touch object is the same as the newTouchTarget, you can directly set the value of the variable handled to true, indicating that it has been processed. For other touch objects, the dispatchtransformedtouchevent () method will be called for processing. As long as the dispatchTransformedTouchEvent() of an object returns true, the value of the variable handled will be set to true.
you can also see the control if pflag is set_ CANCEL_ NEXT_ UP_ Event ID or variable intercepted is true. At this time, cancelChild will be assigned true, and this value will be passed to the parameter cancel of dispatchTransformedTouchEvent() method for processing. Intercepted is in the preceding paragraph Get intercept status The acquisition of this value is described in. If the parameter cancel of the dispatchTransformedTouchEvent() method is true, an action will be generated_ Transmit the cancel event for subsequent analysis. If the value of cancelChild is true, the touch object will also be removed from the touch object chain. Lines 22 to 31 of the code are to remove the touch object. After it is removed from the linked list, the recycle() method will be called for recycling. The predecessor variable is used to remove the current touch object from the linked list.
dispatchTouchEvent() completes the cleanup
The code of the last part is as follows:
// Update list of touch targets for pointer up or cancel, if needed. if (canceled || actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { 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;
if the variable cancelled is equal to true or the event is ACTION_UP or ACTION_HOVER_MOVE, the resetTouchState() method is executed. The assignment of cancelled is in the front Get intercept status In that part, if pflag is set in the current View_ CANCEL_ NEXT_ UP_ Event or event is action_ When cancel, the variable is equal to true. The event is ACTION_UP is the last event in the event flow, ACTION_HOVER_MOVE is a mouse related event. In these cases, resetTouchState() Method has also analyzed the relevant code before. It can be seen from here that after the event flow ends, the mFirstTouchTarget touch event linked list will be set to null, that is, all touch objects will be cleared.
when the event type is ACTION_POINTER_UP, which means lifting a finger in multi touch, will get the id bit bit idBitsToRemove of the finger, then call removePointersFromTouchTargets(), the code is as follows:
private void removePointersFromTouchTargets(int pointerIdBits) { TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; if ((target.pointerIdBits & pointerIdBits) != 0) { target.pointerIdBits &= ~pointerIdBits; if (target.pointerIdBits == 0) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } }
This method is to clear the Id bits saved in the pointerIdBits of all touch objects related to the raised finger. If the pointerIdBits == 0, the touch object will be cleared from the touch object list.
at the end of the ending, the result of the variable handled will be returned, which is the processing result of the event.
The above describes the dispatchTouchEvent() method of VIewGroup class from front to back, but the important dispatchTransformedTouchEvent() has not been analyzed. Let's start now.
Important dispatchTransformedTouchEvent()
the code of the dispatchTransformedTouchEvent() method is very important, including other event types later. It is also handled through this method, as follows
/** * Transforms a motion event into the coordinate space of a particular child view, * filters out irrelevant pointer ids, and overrides its action if necessary. * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead. */ private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; // Canceling motions is a special case. We don't need to perform any transformations // or filtering. The important part is the action, not the contents. final int oldAction = event.getAction(); if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; } // Calculate the number of pointers to deliver. final int oldPointerIdBits = event.getPointerIdBits(); final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits; // If for some reason we ended up in an inconsistent state where it looks like we // might produce a motion event with no pointers in it, then drop the event. if (newPointerIdBits == 0) { return false; } // If the number of pointers is the same and we don't need to perform any fancy // irreversible transformations, then we can reuse the motion event for this // dispatch as long as we are careful to revert any changes we make. // Otherwise we need to make a copy. final MotionEvent transformedEvent; 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; }
the method parameter event is the event dispatched; The parameter cancel represents the cancellation status. If the parameter is true, the event type will be set to ACTION_CANCEL; The parameter child is the control that needs to handle the event; The parameter desiredPointerIdBits is the bit of the id of the event that child wants to process.
the main treatment of this method is as follows:
I. if the event is an action_ The cancel or cancel parameter is true, and the event type is set to MotionEvent.ACTION_CANCEL, process, restore the original event type after processing, and then return the result. You can see from the code that if child==true, super.dispatchTouchEvent(event) will be called, that is, the dispatchTouchEvent() method of the parent class (here refers to the View class) will be executed. Otherwise, child.dispatchTouchEvent(event) will be called.
2. Judge whether the event is decomposed. If decomposition is needed, call the split() method of MotionEvent to decompose it, and then pass the new event; If the event does not need to be decomposed and child == null, super.dispatchTouchEvent(event) will be executed, that is, the dispatchTouchEvent() method of the parent class (here refers to the View class) will be executed, and the execution result will be returned; If the event does not need to be decomposed, and the child is not null, and the child.hasIdentityMatrix(), you need to simply convert the event, then send it to the child, execute the child.dispatchTouchEvent(event), recover the event, and return the execution result handled; If the event does not need to be decomposed, but it does not meet the above two situations, you need to call MotionEvent.obtain(event) to obtain a new event transformedEvent, copy the attribute information of the event, and then convert the attribute of the new event for distribution.
3. Process the newly generated event. If child is equal to null, call super.dispatchtouchevent (transformed event); If child is not equal to null, it will be converted first, and then it will call child. Dispatchtouchevent (transformed event) for processing. Before the final result is returned, the newly generated transformaedevent will be recycled and the final result will be returned.
you can see under what circumstances action will be generated_ Cancel event? When the value of the parameter cancel of dispatchTransformedTouchEvent is true, the current event type will be set to ACTION_CANCEL, dispatch. Under what circumstances, set the parameter cancel to true, as described earlier Dispatch touch object processing events It has been explained
if the parameter child is null, the child View is not found. At this time, the ViewGroup itself needs to be processed (call the method dispatchTouchEvent() of the parent class View for processing, and the code is super.dispatchTouchEvent(event)); if the parameter child is not null, call child.dispatchTouchEvent(event) If this child is also of ViewGroup type, it will execute dispatchTouchEvent() nested.
the complexity of dispatchTouchEvent() lies in the nested execution. You can't worry about the logic of this piece. You need to sort it out slowly to find a solution when you encounter a problem, especially in the steps mentioned above Find the distribution object of touch event It also involves calling dispatchTouchEvent(), which is nested layer by layer, and the results are returned layer by layer, which is easy to be confused.
the parameter desiredPointerIdBits refers to the bit bits of the ID of the event received and processed by the child View. The oldPointerIdBits temporary variable is the bit bits of all IDS in the event. The two perform bit operations &, and the variable newPointerIdBits is obtained. If newPointerIdBits and oldPointerIdBits are not equal, the event is decomposed. How can this be understood? For ex amp le, for multi touch control, A control needs to be processed Handle the event with id=0. The B control handles the event with id=1. The ID of the MotionEvent event object includes 0 and 1, that is, oldPointerIdBits=0x00000003. When the event is distributed to the A control, desiredPointerIdBits=0x00000001. At this time, it is necessary to decompose the event, put the relevant properties of the event with id=0 into A new MotionEvent object, and then give the new event to the A control Similarly, event decomposition is also required when distributing events to B controls, except that desiredPointerIdBits=0x00000002. See the event decomposition code for details https://blog.csdn.net/q1165328963/article/details/120032644
you can see that the event needs to be transformed before being processed by the child view. Also note that when the event does not need to be decomposed, the child View child.hasIdentityMatrix(), after the transformed event is processed by the child view, you also need to restore the attribute, calling event.offsetLocation(-offsetX, -offsetY). The hasIdentityMatrix() of the view What does it mean? There is an initialization matrix. How do you understand this? View sometimes makes complex changes relative to the parent control, including displacement, rotation, etc. these complex changes will be saved in a matrix. In the future, if the properties of the parent control are to be changed to those of the corresponding child view, they can be transformed through this matrix.
dispatchTransformedTouchEvent() method if a child View is found, the dispatchTouchEvent() will be nested. If the child View type found is a View type, the dispatchtouchevent (motionevent) method of the View class will be executed, including the dispatchTouchEvent(MotionEvent event) of the View class when the touch object is not found and the child is set to null Method, enter it and take a look at the code:
dispatchTouchEvent(MotionEvent event) method of View class
/** * Pass the touch screen motion event down to the target view, or this * view if it is the target. * * @param event The motion event to be dispatched. * @return True if the event was handled by the view, false otherwise. */ public boolean dispatchTouchEvent(MotionEvent event) { // If the event should be handled by accessibility focus first. if (event.isTargetAccessibilityFocus()) { // We don't have focus or no virtual descendant has it, do not handle the event. if (!isAccessibilityFocusedViewOrHost()) { return false; } // We have focus and got the event, then use normal event dispatch. event.setTargetAccessibilityFocus(false); } boolean result = false; if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(event, 0); } final int actionMasked = event.getActionMasked(); if (actionMasked == MotionEvent.ACTION_DOWN) { // Defensive cleanup for new gesture stopNestedScroll(); } if (onFilterTouchEventForSecurity(event)) { if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) { result = true; } //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } if (!result && onTouchEvent(event)) { result = true; } } if (!result && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } // Clean up after nested scrolls if this is the end of a gesture; // also cancel it if we tried an ACTION_DOWN but we didn't want the rest // of the gesture. if (actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL || (actionMasked == MotionEvent.ACTION_DOWN && !result)) { stopNestedScroll(); } return result; }
the logic of this method is still very clear
1. When the event is ACTION_DOWN, call stopNestedScroll()
2. When the event is in the ENABLED state of the control, if the mnontouchlistener interface is set, execute the onTouch() method of the interface first, and the interface method returns true, then set the variable result to true.
3. If result is false, execute the onTouchEvent() method of the control, which returns true, and set the variable result to true.
4. If the event is ACTION_UP or ACTION_CANCEL or ACTION_DOWN and the result is false, call the stopNestedScroll() method. Finally, the value of the variable result will be returned.
as you can see from here, if the control is in ENABLED state and the mOnTouchListener interface is set, the ontouchlistener () method will not be executed until the interface is called. If the mOnTouchListener interface returns true, the onTouchEvent() method of the control will not be executed. The ontouchlistener () method will be discussed in the next article.
here is a summary of the main contents of dispatchTouchEvent() of ViewGroup:
first, in the non blocking and non canceling state transition, look for the sub View touch object when the finger is pressed
then, if the touch object is found, subsequent events such as ACTION_MOVE and ACTION_UP will be distributed to the touch object; If the touch object is not found, the dispatchTouchEvent() method of the parent class View will be called for execution.
it's very simple to say, but it's really complex in practice. There are many details, because it involves nested execution and multi touch. The following is a flow diagram, which contains the main processes.