This article will continue to analyze the distribution and handling of events in viewGroups.

ViewGroup event distribution and processing is extremely complex, reflected in the following aspects

  1. A ViewGroup not only distributes events, but may also truncate and process them.
  2. forACTION_DOWN.ACTION_MOVE.ACTION_UPAnd evenACTION_CANCELEvents, there are different handling situations.
  3. The ViewGroup code is also mixed with the handling of multiple fingers.

Given the complexity of the code, this article will break down the different cases in sections, with a diagram showing how the code works at the end.

Due to lack of space, this article will not cover the multi-touch code, because multi-touch is also a difficult area to cover. If I have time later, and if I feel the need, I’ll do another article on ViewGroup handling of multi-finger events.

Handle ACTION_DOWN events

Checks whether the event is truncated

When the ViewGroup detects an ACTION_DOWN event, the first thing it does is check whether the ACTION_DOWN event is truncated.

    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (onFilterTouchEventForSecurity(ev)) {
            
            // Do some reset actions, including clearing FLAG_DISALLOW_INTERCEPT
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }
            
            // 1. Check whether the event is truncated
            final boolean intercepted;
            if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {
                // Since FLAG_DISALLOW_INTERCEPT was previously cleared, this value is false
                final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
                if(! disallowIntercept) {// Determine if you truncate
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else{}}else{}}Copy the code

For ACTION_DOWN events, the ViewGroup only uses the onInterceptTouchEvent() method to determine whether to truncate.

We first to analyze the ViewGroup. OnInterceptTouchEvent () returns false, also is not truncated ACTION_DOWN, then to analyze the situation of the truncation.

Do not truncate the ACTION_DOWN event

Find the child View that handles the event

If the ViewGroup does not truncate ACTION_DOWN events, the intercepted value is false. That means that the ViewGroup doesn’t truncate the event anymore, so you have to find a child View to handle the event

    public boolean dispatchTouchEvent(MotionEvent ev) {

        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // 1. Check whether the event is truncated
            // ...
            
            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;
            / / not truncated
            if(! canceled && ! intercepted) {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;

                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null&& childrenCount ! =0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        
                        // Get an ordered set of child Views
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                                
                        final View[] children = mChildren;
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            // 2. Loop through to find a child View that can handle ACTION_DOWN events
                            // 2.1 Get a child View
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);

                            // 2.2 Check whether the child View can handle events
                            if(! canViewReceivePointerEvents(child) || ! isTransformedTouchPointInView(x, y, child,null)) {
                                ev.setTargetAccessibilityFocus(false);
                                // If not, go through the next loop to find the child View
                                continue;
                            }

                            // 3. Distribute events to child views
                            // ...
                        }
                    }
                }
            }
        }
        return handled;
    }
Copy the code

First, in step 2.1, get a child View. As for how to get a child View, we don’t need to delve into this, if you later encounter the drawing order, and the child View to receive events in the order, you can go back to analyze the order of the child View here.

After obtaining a child View, step 2.2 determines whether the child View meets the criteria for event processing. There are two criteria

  1. throughcanViewReceivePointerEvents()Determines whether the child View can receive events. The principle is very simple, as long as the View is visible, or as long as the View is animated, then the View can receive events.
  2. throughisTransformedTouchPointInView()Determines whether the coordinates of the event are in the child View. Its principle can be described simply, first to convert the event coordinates to View space coordinates, and then determine whether the converted coordinates are in the View. This is easy to say, but to explain it requires some knowledge of View scrolling and Matrix, so I’m not going to explain it in detail here.

In step 2.2, if you find a child View that does not have the ability to handle events, then you go through the next loop to find the next child View that can handle events. This step will almost always find the child View, because if we want to use a control, we have to press our finger on it.

Events are distributed to child views

Now that we have a child View that can handle events, we’re going to distribute ACTION_DOWN events to it, and we’re going to look at the results and see if it handles ACTION_DOWN events, so let’s look at the code

    public boolean dispatchTouchEvent(MotionEvent ev) {

        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // 1. Check whether the event is truncated
            // ...
            
            // No cancellation, no truncation
            if(! canceled && ! intercepted) {if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    
                    if (newTouchTarget == null&& childrenCount ! =0) {

                        // Look for a View that can handle the event
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            // 2. Find a subview that can handle events
                            // ...

                            // 3. Distribute events to child views
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // 3.1 Child View handles the event and gets a TouchTarget object
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
                        }
                    }
                }
            }

            if (mFirstTouchTarget == null) {}else {
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while(target ! =null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        // 3.2 Found the child View to handle ACTION_DOWN event, set the result
                        handled = true;
                    } else{}}}}// 3.3 Return the result
        return handled;
    }
Copy the code

Step 3, through dispatchTransformedTouchEvent () method to send events to the View, and through the return value to determine the View of the results

    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        final int oldPointerIdBits = event.getPointerIdBits();
        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

        final MotionEvent transformedEvent;
        // The hand index has not changed
        if (newPointerIdBits == oldPointerIdBits) {
            // 1. Child has identity matrix
            if (child == null || child.hasIdentityMatrix()) {
                if (child == null) {}else {
                    // Convert the event coordinates to the coordinates of the child space
                    final float offsetX = mScrollX - child.mLeft;
                    final float offsetY = mScrollY - child.mTop;
                    event.offsetLocation(offsetX, offsetY);

                    // Send the event to child for processing
                    handled = child.dispatchTouchEvent(event);

                    event.offsetLocation(-offsetX, -offsetY);
                }
                // Return the processing result
                return handled;
            }
            transformedEvent = MotionEvent.obtain(event);
        } else {
            transformedEvent = event.split(newPointerIdBits);
        }

        if (child == null) {}else {
            // 2. Handle child without identity matrix
            // Convert the event coordinates to the coordinates of the child space
            final float offsetX = mScrollX - child.mLeft;
            final float offsetY = mScrollY - child.mTop;
            transformedEvent.offsetLocation(offsetX, offsetY);
            // Then convert the converted coordinates through the inverse matrix again
            if (! child.hasIdentityMatrix()) {
                transformedEvent.transform(child.getInverseMatrix());
            }
            // Finally pass to child to handle the event after the coordinate transformation
            handled = child.dispatchTouchEvent(transformedEvent);
        }
        // Return the processing result
        return handled;
    }
Copy the code

Although the process here is divided into two steps depending on whether the child View has the identity matrix or not, the process here is roughly the same, the event coordinates are first converted, and then handed to the child View’s dispatchTouchEvent() processing.

Can be seen from the dispatchTransformedTouchEvent () implementation, it returns the result is a child View dispatchTouchEvent (). If it returns true, it means that the child View handled ACTION_DOWN, and then it has reached step 3.1, fetching a TouchTarget object by addTouchTarget()

    private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        // Get a TouchTarget from the object pool
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        // Insert into the head of the list
        target.next = mFirstTouchTarget;
        // mFirstTouchTarget points to the beginning of the single list
        mFirstTouchTarget = target;
        return target;
    }
Copy the code

Note that mFirstTarget refers to the head of the single linked list, and mFirstTouchTarget.child refers to the child View that handled the ACTION_DOWN event.

This means that the child View of the ACTION_DOWN event has been found and processed. After that, we go to 3.2 and 3.3 and return true.

Let’s use a diagram to illustrate the process of not truncating ACTION_DOWN events

The ViewGroup handles the ACTION_DOWN event itself

The ViewGroup can handle ACTION_DOWN events on its own, and there are two situations that make this possible

  1. The ViewGroup itself is truncatedACTION_DOWNThe event
  2. ViewGroup could not be found to processACTION_DOWNThe child View of the event

Since the code handles both cases the same way, I’ll put them together, and the code looks like this

    public boolean dispatchTouchEvent(MotionEvent ev) {

        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // Check whether the event is truncated
            final boolean intercepted;
            if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {
                // When ACTION_DOWN, disallowIntercept value is always false
                final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
                if(! disallowIntercept) {// Returns true to truncate the event
                    intercepted = onInterceptTouchEvent(ev);
                } else{}}else{}// 1. If the ViewGroup truncates the event, go to step 3
            if(! canceled && ! intercepted) {if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    
                    if (newTouchTarget == null&& childrenCount ! =0) {

                        // 2. If all child views do not handle ACTION_DOWN events, go to step 3
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            // Find a subview that can handle events
                            // ...

                            // View handles events
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {

                            }
                        }
                    }
                }
            }

            if (mFirstTouchTarget == null) {
                // 3. ViewGroup handles ACTION_DOWN events by itself
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else{}}// 4. Return the processing result
        return handled;
    }
Copy the code

As you can see from the code, if the ViewGroup truncates an ACTION_DOWN event or can’t find a child View that can handle an ACTION_DOWN event, it eventually gets to step 3, Through dispatchTransformedTouchEvent ACTION_DOWN events to their processing () method, pay attention to the third parameter is null, the incoming said there is not handle events of the View

    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {

        final int oldPointerIdBits = event.getPointerIdBits();
        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
        
        // Hand index unchanged
        if (newPointerIdBits == oldPointerIdBits) {
            if (child == null || child.hasIdentityMatrix()) {
                if (child == null) {
                    / / call the dispatchTouchEvent ()
                    handled = super.dispatchTouchEvent(event);
                } else{}// Returns the result of the processing
                returnhandled; }}else{}return handled;
    }
Copy the code

Simply call the parent View’s diaptchTouchEvent() method, which is passed to the onTouchEvent() method as the View event handler knows from the event distribution.

There’s actually an OnTouchListener loop for View event handling, but you don’t normally set this listener for a ViewGroup, so it’s ignored here.

Can be seen from the whole process of analysis, if ViewGroup processing ACTION_DOWN event, then ViewGroup. DispatchTouchEvent (), the return value is and ViewGroup. OnTouchEvent () returns the same value.

Now we also have a picture of the ViewGroup handling ACTION_DOWN events on its own, and there are two sets of processes, but again I want to emphasize the ViewGroup handling ACTION_DOWN events on its own

  1. ViewGroup truncationACTION_DOWNThe event
  2. ViewGroup could not be found to processACTION_DOWNThe child View of the event

Handle ACTION_DOWN summary

The ViewGroup handling of ACTION_DOWN is critical, and it’s always important to remember that it’s going to find mFirstTouchTarget, because mFirstTouchTarget.child points to the child View that handled the ACTION_DOWN event.

Why mFirstTouchTarget is so critical is that all subsequent events are handled around mFirstTouchTarget, such as passing subsequent events to mFirstTouchTarget.Child.

Handle ACTION_MOVE events

Checks whether the ACTION_MOVE event is truncated

For ACTION_MOVE events, ViewGroup will also determine whether to truncate, as shown in the following code snippet

    public boolean dispatchTouchEvent(MotionEvent ev) {

        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // 1. Check whether truncation occurs
            final boolean intercepted;
            // 1.2 If there is a child View to handle ACTION_DOWN events
            if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {
                // Determine whether the child View requests that the parent View not be allowed to truncate the event
                final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
                if(! disallowIntercept) {// Subviews allow truncation of events
                    // Determine if you truncate
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else { // Subviews are not allowed to truncate events
                    intercepted = false; }}else {
                // truncate ACTION_MOVE event if there is no child ACTION_DOWN View
                intercepted = true; }}}Copy the code

As you can see from the code, mFirstTouchTarget becomes the criterion for truncating the ACTION_MOVE event. Now you can see how important ACTION_DOWN event handling is. It directly affects the handling of ACTION_MOVE events, as well as ACTION_UP and ACTION_CANCEL events.

MFirstTouchTarget == NULL (ACTION_DOWN) {ViewGroup == null (ACTION_DOWN) {ViewGroup == null;

Step 1.2, if there is a child View that handles the ACTION_DOWN event, that is, mFirstTouchTarget! = null. Before distributing the event to mFirstTouchTarget.Child, the ViewGroup needs to see if it is truncated, which can happen in two ways

  1. If the child View allows the parent View to truncate the event, then it passesonInterceptTouchEvent()To see if the ViewGroup itself is truncated
  2. If the child View does not allow the parent View to truncate the event, then the ViewGroup must not truncate.

Now, there are two cases where a ViewGroup does not truncate an ACTION_MOVE event

  1. mFirstTouchTarget ! = null, the child View allows the parent ViewGroup to truncate the event, and the ViewGroup’sonInterceptTouchEvent()returnfalse
  2. mFirstTouchTarget ! = nullThe child View does not allow the parent ViewGroup to truncate the event

So, let’s first analyze the case where the ViewGroup does not truncate the ACTION_MOVE event

Don’t cut ACTION_MOVE

The event is distributed to mFirstTouchTarget.child

If the ViewGroup does not truncate the event, which means mFirstTouchTarget is not null, then the ACTION_MOVE event will be distributed to mFirstTouchTarget.Child. Let’s look at the code

    public boolean dispatchTouchEvent(MotionEvent ev) {

        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {

            // 1. Check whether ACTION_MOVE is truncated
            final boolean intercepted;
            if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {
                final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
                if(! disallowIntercept) {1.1 My son allowed me to truncate, but I decided not to truncate
                    intercepted = onInterceptTouchEvent(ev);
                } else {
                    If my son does not allow it, I will not do it
                    intercepted = false; }}else{}if(! canceled && ! intercepted) {if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    
                }
            }

            if (mFirstTouchTarget == null) {
                // Truncate the event
            } else {
                while(target ! =null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        
                    } else {
                        CancelChild is false without truncating the event
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        // 3. Pass the event to the child View pointed to by mFirstTouchTarget
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                    // ...
                    }
                    // ...}}}return handled;
    }
Copy the code

ViewGroup truncation ACTION_MOVE events, call dispatchTransformedTouchEvent () the event to mFirstTouchTarget. Chid processing

    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        final int oldPointerIdBits = event.getPointerIdBits();
        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

        final MotionEvent transformedEvent;
        // 1. Child has identity matrix
        if (newPointerIdBits == oldPointerIdBits) { // The hand index has not changed
            if (child == null || child.hasIdentityMatrix()) {
                if (child == null) {}else {
                    // Convert the event coordinates
                    final float offsetX = mScrollX - child.mLeft;
                    final float offsetY = mScrollY - child.mTop;
                    event.offsetLocation(offsetX, offsetY);
                    
                    // Pass the event to child
                    handled = child.dispatchTouchEvent(event);

                    event.offsetLocation(-offsetX, -offsetY);
                }
                return handled;
            }
            transformedEvent = MotionEvent.obtain(event);
        } else {
            transformedEvent = event.split(newPointerIdBits);
        }
        
        if (child == null) {}// 2. Child no identity matrix
        else {
            // Convert the event coordinates
            final float offsetX = mScrollX - child.mLeft;
            final float offsetY = mScrollY - child.mTop;
            transformedEvent.offsetLocation(offsetX, offsetY);
            if (! child.hasIdentityMatrix()) {
                transformedEvent.transform(child.getInverseMatrix());
            }
            // Pass the event to child
            handled = child.dispatchTouchEvent(transformedEvent);
        }

        return handled;
    }
Copy the code

We can see that in either case, the child.dispatchTouchEvent() method is eventually called to pass the ACTION_MOVE event to the child. That is, the child View that handles the ACTION_DOWN event will eventually receive the ACTION_MOVE event.

Let’s use a diagram to summarize how ViewGroup does not truncate ACTION_MOVE events

Truncation ACTION_MOVE

As you can see from the previous analysis, if a ViewGroup truncates an ACTION_MOVE event, there are two scenarios

  1. mFirstTouchTarget == nullThen the ViewGroup will truncate the event and handle it itself.
  2. mFirstTouchTarget ! = nullAnd the child View is allowed to truncate the event of the ViewGrouponInterceptTouchEvent()Returns true.

However, the code processing flow in these two cases is different, which undoubtedly makes code analysis more difficult. Let’s look at the first case, without mFirstTouchTarget

    public boolean dispatchTouchEvent(MotionEvent ev) {

        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {

            final boolean intercepted;
            if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {}else {
                // 1. MFirstTouchTarget is null, truncating the event
                intercepted = true;
            }
            
            if(! canceled && ! intercepted) {if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    
                }
            }

            if (mFirstTouchTarget == null) {
                // 2. Truncate and give the event to the ViewGroup itself
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                // ...}}return handled;
    }
Copy the code

From the code you can see, when mFirstTouchTarget = = null, ViewGroup truncation, call dispatchTransformedTouchEvent () method to their processing, this method analysis before, Note that the third argument here is null, which means that there is no child View to handle the event

    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
        
        // No change in hand index
        if (newPointerIdBits == oldPointerIdBits) {
            if (child == null || child.hasIdentityMatrix()) {
                if (child == null) {
                    // Call the dispatchTouchEvent() method of the parent View
                    handled = super.dispatchTouchEvent(event);
                } else{}returnhandled; }}Copy the code

It’s a simple call to the parent View’s dispatchTouchEvent() method, which is viewGroup.onTouchEvent (), And ViewGroup. DispatchTouchEvent () returns a value and ViewGroup. OnTouchEvent () is the same.

Now let’s look at the second truncation case, which is mFirstTouchTarget! = null and ViewGroup. OnInterceptTouchEvent () returns true.

    public boolean dispatchTouchEvent(MotionEvent ev) {

        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {

            // Check for truncation
            final boolean intercepted;
            if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {
                final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
                if(! disallowIntercept) {// 1. Subviews are allowed to truncate, and viewgroups are also truncated. intercepted is true
                    intercepted = onInterceptTouchEvent(ev);
                } else {
                    intercepted = false; }}else{}if(! canceled && ! intercepted) {// ...
            }

            if (mFirstTouchTarget == null) {
                // ...
            } else {
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while(target ! =null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        // ...
                    } else {
                        // if intercepted is true, cancelChild is true to cancelChild processing
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        // 2. Send ACTION_CANCEL event to child
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        
                        // Cancel child to handle the event
                        if (cancelChild) {
                            if (predecessor == null) {
                                // 3. Set mFirstTouchTarget to null
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue; } } predecessor = target; target = next; }}}return handled;
    }
Copy the code

Step 1, when mFirstTouchTarget! = null, child View allows the parent ViewGroup truncation ACTION_MOVE events, and ViewGroup. OnInterceptTouchEvent () returns true, is also the parent ViewGroup truncation.

Step 2, ViewGroup will still call dispatchTransformedTouchEvent () method sends event mFirstTouchTarget, only this time mFisrtTouchTarget received ACTION_CANCEL event, Instead of an ACTION_MOVE event. Note that the second argument cancelChild has a value of true, so let’s look at the implementation

    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        final int oldAction = event.getAction();
        // Cancel is true
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            // Set the event type to ACTION_CANCEL
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                // Send the ACTION_CANCEL event to child
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            // Return the result of child processing
            returnhandled; }}Copy the code

Mfirsttouchtarget. child receives ACTION_CANCEL when ViewGroup truncates the ACTION_MOVE event. Now you know how a View receives an ACTION_CANCEL event!!

After sending the ACTION_CANCEL event to the mFirstTouchTarget, I did the third step, setting the mFirstTouchTarget to NULL. So this is going too far, ViewGroup truncates the ACTION_MOVE event that was mFirstTouchTarget, turns ACTION_MOVE into ACTION_CANCEL and sends mFirstTouchTarget, Finally, disqualify mFirstTouchTarget.child from receiving subsequent events.

Since a large number of ACTION_MOVE events will be generated during sliding, how to handle subsequent ACTION_MOVE events after ViewGroup truncates ACTION_MOVE events? If mFirstTouchTarget == null, call viewGroup.onTouchEvent ().

Now, let’s use another diagram to show how the ViewGroup truncates the ACTION_MOVE event

This graph doesn’t list the results of sending ACTION_CANCEL and doesn’t seem to care about the results of ACTION_CANCEL processing.

Handles ACTION_UP and ACTION_CANCEL events

A View/ViewGroup processes a sequence of events each time, starting with ACTON_DOWN and ending with ACTION_UP/ACTION_CANCEL, with zero or more ACTION_MOVE events in between.

ACTION_UP and ACTION_CANCEL are theoretically one or the other.

ViewGroup handles ACTION_UP and ACTION_CANCEL events in the same way ACTION_MOVE events are handled. You can analyze it again from the source code.

Proper use of requestDisallowInterceptTouchEvent ()

A child View can request that its parent View not allow truncation of events. How can a child View do that

        final ViewParent parent = getParent();
        if(parent ! =null) {
            parent.requestDisallowInterceptTouchEvent(true);
        }
Copy the code

Access to the parent View, and calls the requestDisallowInterceptTouchEvent (true) method, which does not allow the parent View truncation.

The parent View is commonly ViewGroup, we take a look at ViewGroup. RequestDisallowInterceptTouchEvent () method of implementation

    // ViewGroup.java

    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
        // FLAG_DISALLOW_INTERCEPT is set
        if(disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0)) {
            // We're already in this state, assume our ancestors are too
            return;
        }

        // FLAG_DISALLOW_INTERCEPT is set based on the parameter value
        if (disallowIntercept) {
            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
        } else {
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }

        // Pass the request up to the parent View
        if(mParent ! =null) { mParent.requestDisallowInterceptTouchEvent(disallowIntercept); }}Copy the code

RequestDisallowInterceptTouchEvent (Boolean disallowIntercept) will decide whether to set up according to the value of the parameter disallowIntercept FLAG_DISALLOW_INTERCEPT tag, Then ask the parent View to do the same thing.

Now, we can imagine one thing, if a child View invokes the getParent. RequestDisallowInterceptTouchEvent (true), All parent views on top of the child View will set a FLAG_DISALLOW_INTERCEPT flag. Once this flag is set, all parent views do not truncate any subsequent events. This method is really overbearing and should be used with caution, otherwise it may affect the functionality of a parent View.

However requestDisallowInterceptTouchEvent () method call are not effective at any time, please see the following code

    private void resetTouchState(a) {
        // Clear FLAG_DISALLOW_INTERCEPT
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    }
    
    
    public boolean dispatchTouchEvent(MotionEvent ev) {

        if (onFilterTouchEventForSecurity(ev)) {
            
            ACTION_DOWN Clears FLAG_DISALLOW_INTERCEPT
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                resetTouchState();
            }


            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

            // omit the ACTION_DOWM, ACTION_MOVE, ACTIOON_UP code

            
            // ACTION_CANCEL or ACTION_UP also clears FLAG_DISALLOW_INTERCEPT
            if (canceled
                    || actionMasked == MotionEvent.ACTION_UP
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                resetTouchState();
            } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {

            }
        }
        
        return handled;
    }

Copy the code

We can see that the FLAG_DISALLOW_INTERCEPT flag is cleared first when an ACTION_DOWN event is processed, which means, Before the child View if the parent View to handle ACTION_DOWN calls the getParent (). RequestDisallowInterceptTouchEvent (true) method, is invalid.

ACTION_UP or ACTION_CANCEL events indicate the termination of the event sequence, and we can see that FLAG_DISALLOW_INTERCEPT is unflagged after ACTION_UP or ACTION_CANCEL events are processed. This is obviously understandable, because when one sequence of events is finished, the state must be restored and the next sequence of events must be processed.

Now, we can now get a corollary, getParent () requestDisallowInterceptTouchEvent (true) after receiving ACTION_DOWN, It is called until ACTION_UP or ACTION_CANCEL events are received. Obviously this method is only for ACTION_MOVE events.

So, when does a subview request that the parent View not truncate the ACTION_MOVE event? Let me use the ViewPager example to give you a sense of what it is.

In the first case, the ViewPager receives the ACTION_MOVE event in onInterceptTouchEvent() and is ready to truncate the ACTION_MOVE event. Before executing the sliding code, Call getParent (). RequestDisallowInterceptTouchEvent (true), request the parent View does not allow truncation subsequent ACTION_MOVE events. Why make this request to the parent View? Since the ViewPager has already started sliding with ACTION_MOVE, it doesn’t make sense for the parent View to truncate the ViewPager’s ACTION_MOVE.

The second case is that the ViewPager is still in the sliding state after the finger is quickly sliding and lifted. At this time, if the finger is pressed again, the ViewPager thinks that this is an action to terminate the current sliding and start sliding again. So the ViewPager will ask the parent View not to truncate the ACTION_MOVE event because it will start sliding with the ACTION_MOVE immediately.

If you can understand these two articles, you should have no problem analyzing ViewPager.

One conclusion from both cases is that if the current control is about to use ACTION_MOVE to perform some ongoing action, such as sliding, it can ask the parent View not to allow subsequent ACTION_MOVE events to be truncated.

conclusion

The article is very long, but each process has been clearly analyzed. However, in practice, whether it is custom View event processing or event conflict resolution, we tend to feel intimidated and confused. I now summarize the key points of this article, which I hope you will keep in mind in your practical application

  1. Be sure to knowViewGroup.dispatchTouchEvent()When to returntrue, when to returnfalse. Returned because the event was processedtrueIs returned because no event was handledfalse.
  2. To deal withACTION_DOWNWhen, there is a key variable, namelymFirstTouchTarget, be sure to remember that only in consumption outACTION_DOWNEvents have values.
  3. ACTION_MOVEUnder normal circumstances it would be transmittedmFirstTouchTarget.childIf truncated by the ViewGroup, it will receiveACTION_MOVEintoACTION_CANCELEvent sent tomFirstTouchTarget.childAnd themFirstTouchTargetEmpty, subsequentACTION_MOVEThe event will be passed to the ViewGrouponTouchEvent().
  4. ACTION_UP.ACTION_CANCELEvent processing flow andACTION_MOVEThe same.
  5. Note that the child View request does not allow the parent View to truncate the call timing.