Event Distribution Flow Chart

(The picture is adapted from Gityuan’s article and repainted)Android event distribution mechanism

The graph call logic can be represented by the following pseudocode:

public booleanDispatchTouchEvent (MotionEvent ev) {boolean consume = false;

    if (onInterceptTouchEvent(ev)) {
        consume = onTouchEvent(ev);
    } else {
        consume = child.dispatchTouchEvent(ev);
    }
    return consume;
}
Copy the code

Here are some conclusions:

  • If a view does not consume an ACTION_DOWN event, then ACTION_MOVE and ACTION_UP subsequent to the same event sequence will not be assigned to the view

  • If a view consumes an event in ACTION_DOWN, the ACTION_MOVE ACTION_UP that follows the same sequence of events is handed directly to the view

  • If a ViewGroup blocks an ACTION_MOVE event under certain conditions, the child view that consumed the event will receive an ACTION_CANCEL event and the onInteceptTouchEvent will not be called in the event sequence

  • .

But do you really understand where these conclusions come from? I’m afraid there is no in-depth understanding of the source code analysis, is not to remember!

This time, we combine the View View with the source code to analyze the call flow of a touch screen (from press -> lift). The source code analysis will be presented in the form of annotations.

Application View Hierarchy

Here is the simplest view, the layout set up in the MainActivity, the outermost ConstraintLayout, and MyViewgroup MyView that we added ourselves.

In addition, there is an outer ViewGroup added by the system.

Sequence diagram of ACTION_DOWN call:

The ACTION_DOWN event goes through:

DecorView -> Activity -> PhoneWindow -> DecorView -> LinearLayout -> FrameLayout -> ActionBarOverlayLayout -> ContentFrameLayout -> ConstraintLayout -> MyViewgroup -> MyView

The calling process is a recursive process and can be represented as follows:

We will combine the source code to analyze the call process of ACTION_DOWN ACTION_MOVE two events, in which we focus on the analysis of ViewGroup dispatchTouchEvent method,

Activity.java

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        // This method is called when you touch the screen, press home, back, etc
        onUserInteraction();
    }
    // Call PhoneWindow's superDispatchTouchEvent method
    // If superDispatchTouchEvent returns true (meaning the event was consumed by the View below),
    // Return true without executing the Activity's onTouchEvent
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}
Copy the code

PhoneWindow.java

public boolean superDispatchTouchEvent(MotionEvent event) {
    // Call superDispatchTouchEvent directly to the DecorView
    return mDecor.superDispatchTouchEvent(event);
}

Copy the code

DecorView.java

public boolean superDispatchTouchEvent(MotionEvent event) {
    // Call the parent dispatchTouchEvent, although the DecorView inherits from FrameLayout
    // But FrameLayout doesn't override dispatchTouchEvent, so look at ViewGroup
    return super.dispatchTouchEvent(event);
}
Copy the code

We’ll post the ViewGroup dispatchTouchEvent() method twice, except the comment is only relevant to the current event

ACTION_DOWN

In the MyView area press, we omit the previous call to MyViewgroup, which is the same recursive call from LinearLayout-constraintLayout, and set our recursive call to MyViewgroup

ViewGroup.java

public boolean dispatchTouchEvent(MotionEvent ev) {...boolean handled = false;
    // Filter events, if the window is not blocked, continue event distribution
    if (onFilterTouchEventForSecurity(ev)) {
	final int action = ev.getAction();
    
        ACTION_MASK (8 bits lower) because multi-touch is supported, events are divided more finely:
        // The first finger press generates ACTION_DOWN
        // Press the second finger to generate ACTION_POINTER_DOWN
        // Lift a finger at this point to generate an event: ACTION_POINTER_UP
        // Lift another finger to generate the ACTION_UP event
        // Ev.getAction () contains actions (lower 8 bits), touch index (9 to 16 bits), etc., not just the actions mentioned above.
        // You cannot compare if (ev.getaction () == ACTION_POINTER_DOWN), because ACTION_POINTER_DOWN is only part of ev.getaction ()
        Ev.getaction () = ev.getAction();
        // The value returned is stripped away, so &action_mask is added to filter out the other information, only 8 bits lower, which is equivalent to ev.getactionmasked
	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.
            // Here is ACTION_DOWN event, clear all TouchTargets, reset flag bit, new start
	    cancelAndClearTouchTargets(ev);
	    resetTouchState();
	}

	// Check for interception.
	final boolean intercepted;
        // ACTION_DOWN, mFirstTouchTarget is null, because the return is not complete
        FirstTouchTarget = firstTouchTarget; firstTouchTarget = firstTouchTarget
        TouchTarget is a list structure that links multiple sub-views that can all consume events
	if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {
	    final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
	    if(! disallowIntercept) {/ / if the ViewGroup child did not call requestDisallowInterceptTouchEvent, judge whether to intercept
		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.
            // If it is not ACTION_DOWN and the child view does not consume (mFirstTouchTarget == NULL), it will intercept directly, this is ACTION_DOWN event, it will not go here
	    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.
        // Check whether the current canceled event is ACTION_DOWN and canceled is false
	final boolean canceled = resetCancelNextUpFlag(this)
			|| actionMasked == MotionEvent.ACTION_CANCEL;

	// Update list of touch targets for pointer down, if needed.
        // Split the event, because it may be multi-touch, you need to split the event to different TouchTargets
	final booleansplit = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) ! =0;
	TouchTarget newTouchTarget = null;
	boolean alreadyDispatchedToNewTouchTarget = false;
	if(! canceled && ! intercepted) {// Not cancelling the event and not intercepting it
            / / here childWithAccessibilityFocus is null
	    View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
		    ? findChildWithAccessibilityFocus() : null;
            
            // Search for consumable subviews only in the following cases, ACTION_DOWN, in the if
	    if (actionMasked == MotionEvent.ACTION_DOWN
		    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
		    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                // Get the index of the current MotionEvent.
                // Since multi-touch is supported, you need to know which touch generated the current MotionEvent
                // Then the touch point information must be included in the MotionEvent, which is in the 9-16 bits of the action.
                // Action is a 32 bit int. Take out 9 to 16 bits and move it 8 bits to the right to get the actionIndex
                ActionIndex = 0; if the second finger is pressed, actionIndex = 1
		final int actionIndex = ev.getActionIndex(); // always 0 for down
                // idBitsToAssign = 1
		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 = ev.getX(actionIndex);
		    final float y = ev.getY(actionIndex);
		    // Find a child that can receive the event.
		    // Scan children from front to back.
                    // The child view is sorted according to its z-axis value. The larger the z-axis is, the scan is arranged first
                    // If there is no z-axis view set in the child view, then the preorderedList is null
		    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    // The current preorderedList is null, where customOrder is false, that is, there is no custom scan order
		    final boolean customOrder = preorderedList == null
			    && isChildrenDrawingOrderEnabled();
		    final View[] children = mChildren;
                    
                    // Loop search, scan from outside to inside, can be viewed as, a ViewGroup
                    // There are multiple sub-views, starting from the view written after the layout, because the area of the view written after may be
                    // Overwrite the view written first, such as in a FrameLayout, the view written below will be overwritten
                    // Go to the top view, so that the overlaid view is scanned to fit our visual model
		    for (int i = childrenCount - 1; i >= 0; i--) {
			final int childIndex = getAndVerifyPreorderedIndex(
				childrenCount, i, customOrder);
                        // Add preorderedList childIndex to get the view that is scanned first
			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.
                        / / here childWithAccessibilityFocus is null
			if(childWithAccessibilityFocus ! =null) {
			    if(childWithAccessibilityFocus ! = child) {continue;
			    }
			    childWithAccessibilityFocus = null;
			    i = childrenCount - 1;
			}
                        // Determine whether the current child can accept the event, and the finger points fall in the child area, if any of the points do not match, then continue to loop the next child view, we are eligible
			if(! canViewReceivePointerEvents(child) || ! isTransformedTouchPointInView(x, y, child,null)) {
			    ev.setTargetAccessibilityFocus(false);
		            continue;
			}
                        // If a child view has already consumed the event, then mFirstTouchTarget is not null,
                        // The current event is multi-touch (second finger pressed), so compare the Child attribute in mFirstTouchTarget
                        // Use the same pointerIdBits as the current child
                        // newTouchTarget is null, ACTION_DOWN is not complete, mFirstTouchTarget must be null
			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);
                        // This is where events are actually distributed, and it is in this method that the next recursion is performed, which will be analyzed in the following sections
			if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
			    // Child wants to receive touch within its bounds.
                            // The subview has already consumed the event, which we set up earlier, and is now recursing to MyViewgroup,
                            / / so the above dispatchTransformedTouchEvent it leaves one call, that is called MyView dispatchTouchEvent,
                            // MyView consumes the ACTION_DOWN event. At this point, it has broken out of the last recursion. The current ViewGroup is MyViewGroup.
                            // 也即当前 this 对象 是 MyViewGroup
			    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();
                            // mFirstTouchTarget is assigned to MyView,
                            // mFirstTouchTarget's Child points to MyView
                            // Now we have the view for the final consumption event: MyView
			    newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            // the consumption flag has been set to true
			    alreadyDispatchedToNewTouchTarget = true;
                            // The child view of the consumption is found
                            // Note that we are currently in a loop through MyViewgroup, so keep an eye on the current recursive call stack
			    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.
        // mFirstTouchTarget is null if a subView is not consumable
	if (mFirstTouchTarget == null) {
	    // No touch targets so treat this as an ordinary view.
            // Think of yourself as a normal view, not a ViewGroup, and allocate events to yourself to see if you consume
	    handled = dispatchTransformedTouchEvent(ev, canceled, null,
		    TouchTarget.ALL_POINTER_IDS);
	} else {
            
            // If you already have a child view to consume, distribute it to that child view
	    // 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;
                / / we have seen above alreadyDispatchedToNewTouchTarget to true
                // mFirstTouchTarget in addTouchTarget () above
                // newTouchTarget is the same object as newTouchTarget
                
		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; }}...ConstraintLayout returns the result to the parent, ConstraintLayout, and again, out of recursion
    return handled;
}
Copy the code

ACTION_MOVE

public boolean dispatchTouchEvent(MotionEvent ev) {
    // We are going to recurse to MyViewgroup. The current ViewGroup is MyViewgroup.boolean handled = false;
    // Filter events, if the window is not blocked, continue event distribution
    if (onFilterTouchEventForSecurity(ev)) {
	final int action = ev.getAction();
    
	final int actionMasked = action & MotionEvent.ACTION_MASK;

	// Handle an initial down.
        // Now it's ACTION_MOVE
	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;
        // in ACTION_MOVE, mFirstTouchTarget is not null, because in ACTION_DOWN,
        // The mFirstTouchTarget of MyViewgroup has been assigned a value, which is MyView's wrapper TouchTarget object, so the if is still going to go
	if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {
	    final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
	    if(! disallowIntercept) {/ / if the ViewGroup child did not call requestDisallowInterceptTouchEvent, judge whether to intercept
		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.
        // Check whether the current canceled event is ACTION_MOVE with false
	final boolean canceled = resetCancelNextUpFlag(this)
			|| actionMasked == MotionEvent.ACTION_CANCEL;

	// Update list of touch targets for pointer down, if needed.
        // Split the event, because it may be multi-touch, you need to split the event to different TouchTargets
	final booleansplit = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) ! =0;
	TouchTarget newTouchTarget = null;
	boolean alreadyDispatchedToNewTouchTarget = false;
	if(! canceled && ! intercepted) {// Not cancelling the event and not intercepting it
            / / here childWithAccessibilityFocus is null
	    View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
		    ? findChildWithAccessibilityFocus() : null;
            
            // The function of the if block is to find the child view of the consuming event in ACTION_DOWN
            // ACTION_MOVE does not move 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
                // idBitsToAssign = 1
		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 = ev.getX(actionIndex);
		    final float y = ev.getY(actionIndex);
		    // Find a child that can receive the event.
		    // Scan children from front to back.
            
		    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 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.
                        / / here childWithAccessibilityFocus is null
			if(childWithAccessibilityFocus ! =null) {
			    if(childWithAccessibilityFocus ! = child) {continue;
			    }
			    childWithAccessibilityFocus = null;
			    i = childrenCount - 1;
			}
			if(! canViewReceivePointerEvents(child) || ! isTransformedTouchPointInView(x, y, child,null)) {
			    ev.setTargetAccessibilityFocus(false);
		            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();
			    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; }}}// mFirstTouchTarget is null if ACTION_DOWN does not find a consumable subView
        // Note: according to our current view
        // The mFirstTouchTarget of the DecorView is a LinearLayout
        // The mFirstTouchTarget of the LinearLayout is FrameLayout
        // FrameLayout's mFirstTouchTarget is ActionBarOverlayLayout
        ActionBarOverlayLayout's mFirstTouchTarget is ContentFrameLayout
        // ContentFrameLayout's mFirstTouchTarget is ConstraintLayout
        // Interpret tLayout's mFirstTouchTarget is MyViewGroup
        // MyViewGroup mFirstTouchTarget is MyView
	if (mFirstTouchTarget == null) {
	    // No touch targets so treat this as an ordinary view.
	    handled = dispatchTransformedTouchEvent(ev, canceled, null,
		    TouchTarget.ALL_POINTER_IDS);
	} else {
            
            // If you already have a child view to consume, distribute it to that child view
	    // 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;
                / / alreadyDispatchedToNewTouchTarget to false, newTouchTarget is null,
                // Because this is the ACTION_MOVE event distribution process, the above assignment is not gone
		if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
		    handled = true;
		} else {
        
                    / / go here
                    // Normally when the ViewGroup intercepts an event, cancelChild is true, and the child view that previously consumed the event receives the ACTION_CANCEL event.
                    // Here cancelChild is false because we did not intercept
		    final boolean cancelChild = resetCancelNextUpFlag(target.child)
			    || intercepted;
                    // For the actual event distribution, the third parameter is the TouchTargets recorded on ACTION_DOWN. In this case, it is a single touch, so it is mFirstTouchTarget
                    // So there is a conclusion: in ACTION_DOWN, who consumes the event, then the MOVE, UP and other events are assigned to it
		    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; }}...ConstraintLayout returns the result to the caller.
    return handled;
}
Copy the code

ACTION_UP similar

Analysis dispatchTransformedTouchEvent

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
        
        // This method is where events are actually distributed

        // 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();
        / / cancel
        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.
        // Multi-touch related, where oldPointerIdBits = 1, desiredPointerIdBits = 1, newPointerIdBits = 1, the event does not need to be split, because there is no multi-touch
        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 the current ViewGroup does not have a child view, the parent class dispatchTouchEvent is called
                // The View dispatchTouchEvent method
                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
                    final float offsetX = mScrollX - child.mLeft;
                    final float offsetY = mScrollY - child.mTop;
                    event.offsetLocation(offsetX, offsetY);

                    // Call the dispatchTouchEvent method of the child view, which is the entry to the next level of recursion,
                    // If the child view is a ViewGroup, the call will continue. If the child View is a view, the call will be the last level of recursion
                    handled = child.dispatchTouchEvent(event);

                    event.offsetLocation(-offsetX, -offsetY);
                }
                return handled;
            }
            transformedEvent = MotionEvent.obtain(event);
        } else {
            // With multi-touch, events can be split up. There is not much research here
            transformedEvent = event.split(newPointerIdBits);
        }

        // Perform any necessary transformations and dispatch.
        // If there is no return above, the transformedEvent will be distributed again
        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;
}
Copy the code

About TouchTarget

See the ViewGroup event distribution summary -TouchTarget

conclusion

The dispatchTouchEvent method is the onInterceptTouchEvent method. OnInterceptTouchEvent is just used to handle the event, through recursive calls, to determine the event distribution object.

Now with this example, let’s look at the origin of the previous conclusions:

  • If a view does not consume an ACTION_DOWN event, then ACTION_MOVE and ACTION_UP subsequent to the same event sequence will not be assigned to the view

For example, MyView has no consumption event in ACTION_DOWN, so when the recursive call returns to MyViewgroup, the mFirstTouchTarget of MyViewgroup will be null. When ACTION_MOVE arrives, If (mFirstTouchTarget == NULL), if (mFirstTouchTarget == null), if MyViewgroup handles ACTION_DOWN, If ACTION_DOWN does not consume a view, then subsequent ACTION_MOVE ACTION_UP stops distributing to the DecorView. Because ACTION_DOWN is not consumed, the mFirstTouchTarget for each layer of ViewGroup is null. When ACTION_MOVE is in the DecorView, Since mFirstTouchTarget == NULL is still handled by itself, these events never reach the view below

  • If a view consumes an event in ACTION_DOWN, the ACTION_MOVE ACTION_UP that follows the same sequence of events is handed directly to the view

Walk the if, contrary to the above, (mFirstTouchTarget = = null) else statements is fast, and, when the distribution dispatchTransformedTouchEvent third parameter directly travels are the record mFirstTouchTarget

  • If a ViewGroup blocks an ACTION_MOVE event under certain conditions, the child view that consumed the event will receive an ACTION_CANCEL event and the onInteceptTouchEvent will not be called in the event sequence

Refer to the previous article

  • .