directory

The source code below is based on version 27 and has been deleted for ease of reading.

Activity –> ViewGroup process analysis

From the user click on the screen to the control in the screen to respond to the operation of the general process is Activity-> ViewGroup-> View. So how does an Activity pass events to the root view we wrote in XML? So what is the Activity-> ViewGroup process?

When a click occurs, it is first passed to the current Activity, which is distributed by the Activity’s dispatchTouchEvent method. Activity dispatchTouchEvent ()

//Activity
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    // Call the Window superDispatchTouchEvent method for event distribution
    // If true is returned, the event is consumed, and the Activity's dispatchTouchEvent returns true without further execution.
    // If false is returned, the event was not consumed and the Activity's onTouchEvent method is called
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}
Copy the code

Continue to look at the superDispatchTouchEvent source for Window, which is an abstract class with a unique implementation of PhoneWindow (this can be seen in the class annotation for Window), So let’s look for PhoneWindow’s superDispatchTouchEvent method.

//PhoneWindow
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    //mDecor 是 DecorView
    // Call the superDispatchTouchEvent method in the DecorView
    return mDecor.superDispatchTouchEvent(event);
}
Copy the code

Continue looking at the superDispatchTouchEvent method of the DecorView

//DecorView
public boolean superDispatchTouchEvent(MotionEvent event) {
    // Call the parent's dispatchTouchEvent method for distribution.
    return super.dispatchTouchEvent(event);
}
Copy the code

GetDecorView ().findViewById(Android.r.i.C.Ontent)).getChildAt(0); The mDecor of the PhoneWindow call is the DecorView obtained by getWindow().getDecorView(). The View we set up with our Activity is a child View of it. DecorView inherits FrameLayout, which does not override the dispatchTouchEvent method, So the end result is the dispatchTouchEvent method of FrameLayout’s parent ViewGroup to distribute events to the page we’re developing.

conclusion

Complete event distribution process: Activity –> PhoneWindow –> DecorView –> ViewGroup –>View

ViewGroup –> View Process analysis

Before we look at the ViewGroup dispatchTouchEvent method, let’s look at one key class: TouchTarget.

TouchTarget is a static inner class in a ViewGroup. The linked list that responds to the Touch event View is logged. In ViewGroup, there is a member variable private TouchTarget mFirstTouchTarget that plays an important role in dispatchTouchEvent distributing Touch events.

private static final class TouchTarget {
    // The maximum length of the list
    private static final int MAX_RECYCLED = 32;
    // Lock lock used to control synchronization
    private static final Object sRecycleLock = new Object[0];
    // Internal header for reuse
    private static TouchTarget sRecycleBin;
    // The length of the reusable instance list
    private static int sRecycledCount;

    public static final int ALL_POINTER_IDS = -1; // all ones

    // The touched child view.
    // Record the View that responds to the Touch event
    public View child;

    // The combined bit mask of pointer ids for all pointers captured by the target.
    // The combined bitmask of pointer ids for all Pointers captured to the target, related to multi-touch.
    public int pointerIdBits;

    // The next target in the target list.
    // The next record in the list is the TouchTarget object of the Touch View
    public TouchTarget next;

    private TouchTarget(a) {}public static TouchTarget obtain(@NonNull View child, int pointerIdBits) {
        if (child == null) {
            throw new IllegalArgumentException("child must be non-null");
        }

        final TouchTarget target;
        synchronized (sRecycleLock) {
            if (sRecycleBin == null) {
                // sRecycleBin is null when touched for the first time. Create a new object
                target = new TouchTarget();
            } else {
                // Assign the object at the top of the list to target, and sRecycleBin points to the next object for reuse.
                target = sRecycleBin;
                // Move the sRecycleBin header down one bit to point to the next object.
                sRecycleBin = target.next;
                // The length of the reusable instance list is reduced by one
                sRecycledCount--;
                // Assign next to target to null
                target.next = null; }}// Record the View that responds to the Touch event.
        target.child = child;
        target.pointerIdBits = pointerIdBits;
        return target;
    }
    // Recycle the TouchTarget object
    public void recycle(a) {
        if (child == null) {
            throw new IllegalStateException("already recycled once");
        }

        synchronized (sRecycleLock) {
            // The length of the reusable list can be no longer than MAX_RECYCLED
            if (sRecycledCount < MAX_RECYCLED) {
                // Add a reusable object to the top of the list. SRecycleBin points to this object.
                next = sRecycleBin;
                sRecycleBin = this;
                sRecycledCount += 1;
            } else {
                next = null;
            }
            child = null; }}}Copy the code

MFirstTouchTarget is assigned using the TouchTarget.obtain method, which is evaluated by new object when the application is first touched, and reuse the first list object when the application is touched. Finally, recycle() is used to recycle itself for future reuse.

First look at the ViewGroup dispatchTouchEvent method:

Before we analyze this, let’s confirm two questions:

  • How is a ViewGroup distributed to child views?
  • Problem two: about requestDisallowInterceptTouchEvent
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) {
            // Cancel and clear the touch target with mFirstTouchTarget set to NULL.
            // mFirstTouchTarget is a list of views that respond to touch events.
            cancelAndClearTouchTargets(ev);
            // If it is a DOWN event, the touch state is reset.
            / / will reset mGroupFlags FLAG_DISALLOW_INTERCEPT sign, this is associated with requestDisallowInterceptTouchEvent, later explained.
            resetTouchState();
            // Both methods internally call the clearTouchTargets() method to retrieve objects on the linked list pointed to by mFirstTouchTarget and set mFirstTouchTarget to NULL.} (1) -- -- -- -- -- -- -- -- -- -- -- --// Check for interception
        // Check for interception.
        final boolean intercepted;
        if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {
            // Whether interception is not allowed
            / / (add mGroupFlags requestDisallowInterceptTouchEvent is through FLAG_DISALLOW_INTERCEPT sign to tell the parent don't intercept)
            final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
            if(! disallowIntercept) {// If intercepting is allowed, call onInterceptTouchEvent and record the return value
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was changed
            } else {
                // If interception is not allowed, simply assign intercepted to false
                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 a DOWN event and mFirstTouchTarget is null, the ViewGroup continues to intercept the event
            // If this is the case, the event after DOWN is passed 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.
        // Whether it was intercepted, or whether it passed the CANCEL event
        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;

        // Update list of touch targets for pointer down, if needed.
        final booleansplit = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) ! =0;
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
        if(! canceled && ! intercepted) {// If the event is targeting accessiiblity 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 the event is DOWN.
            // (ACTION_POINTER_DOWN is a DOWN event when one finger is pressed and another finger is pressed)
            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 = ev.getX(actionIndex);
                    final float y = ev.getY(actionIndex);
                    // Find a child that can receive the event.
                    // Scan children from front to back.
                    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;
                    
                    for (int i = childrenCount - 1; i >= 0; I -) {(2) -- -- -- -- -- -- -- -- -- -- -- --// Iterate over the subview
                    // See if the top View responds to the action event, which is also intuitive.
                        final int childIndex = getAndVerifyPreorderedIndex(
                                childrenCount, i, customOrder);
                        final View child = getAndVerifyPreorderedView(
                                preorderedList, children, childIndex);

                        // If there is a view that has accessibility focus we want it
                        // to get the event first and if not handled we will perform a
                        // normal dispatch. We may do a double iteration but this is
                        // safer given the timeframe.
                        if(childWithAccessibilityFocus ! =null) {
                            if(childWithAccessibilityFocus ! = child) {continue;
                            }
                            childWithAccessibilityFocus = null;
                            i = childrenCount - 1; } (3) -- -- -- -- -- -- -- -- -- -- -- --// Check if the subview is in the current click area, if not, skip the loop
                        if(! canViewReceivePointerEvents(child) || ! isTransformedTouchPointInView(x, y, child,null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }
                        // Check whether the View is already in the list of related events. If so, break out of the loop.
                        For example, when one finger is pressed on a button, another finger is pressed on the same button, which does not respond to events.
                        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); (4) -- -- -- -- -- -- -- -- -- -- -- --/ / call dispatchTransformedTouchEvent methods here,
                        // This method calls the child View's dispatchTouchEvent. (Explained later)
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            / / if dispatchTransformedTouchEvent returns true, on behalf of the child consumer of the event
                            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(); 5. -- -- -- -- -- -- -- -- -- -- -- --// Call the addTouchTarget method to assign a value to mFirstTouchTarget
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }
                        ev.setTargetAccessibilityFocus(false);
                    }
                    if(preorderedList ! =null) preorderedList.clear();
                }
                // No View was found for the response control, but mFirstTouchTarget is not null.
                // For example, one finger on a button and one finger on another control (no consumption event)
                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;
                    // Iterate through the list and set newTouchTarget to the object at the end of the list.
                    while(newTouchTarget.next ! =null) { newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |= idBitsToAssign; }}}// Dispatch to touch targets.
        if (mFirstTouchTarget == null) {6 -- -- -- -- -- -- -- -- -- -- -- --// If mFirstTouchTarget is null, there is no child View consumption event
            / / passed dispatchTransformedTouchEvent onTouchEvent calls itself.
            // 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) {
                    // When the DOWN event is passed, it goes here. This is reflected in the previous process.
                    handled = true;
                } else {
                    // Target. child is set to a CANCEL event or is passed to a CANCEL event.
                    final booleancancelChild = resetCancelNextUpFlag(target.child) || intercepted; 7 -- -- -- -- -- -- -- -- -- -- -- --/ / for the DOWN event, also call dispatchTransformedTouchEvent method for distribution here.
                    CancelChild is true if the parent control intercepts the event intercepted. * in the call to distribute events, dispatchTransformedTouchEvent son passed a CANCEL events to View (behind) * /
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                        handled = true;
                    }
                    if (cancelChild) {
                        if (predecessor == null) {today -- -- -- -- -- -- -- -- -- -- -- --If the header is reclaimed, mFirstTouchTarget points to the next object.
                            mFirstTouchTarget = next;
                        } else {
                            predecessor.next = next;
                        }
                        // Reclaim the object
                        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) {
            // Resets the touch state during the UP event
            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);
    }
    returnhandled; } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -/ / ViewGroup class
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
    // Create a TouchTarget object
    final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
    // Assign mFirstTouchTarget to Target's next. The first assignment, mFirstTouchTarget must be null.
    target.next = mFirstTouchTarget ;
    //mFirstTouchTarget assigns target
    mFirstTouchTarget = target;
    returntarget; } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -/ / ViewGroup class
private TouchTarget getTouchTarget(@NonNull View child) {
    for(TouchTarget target = mFirstTouchTarget; target ! =null; target = target.next) {
        // Iterate through the list to find the TouchTarget object where the Child is located.
        if (target.child == child) {
            returntarget; }}return null; } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -private void clearTouchTargets(a) {
    TouchTarget target = mFirstTouchTarget;
    // Iterate through the list, calling the Recycle method of the TouchTarget object on the list.
    if(target ! =null) {
        do {
            TouchTarget next = target.next;
            target.recycle();
            target = next;
        } while(target ! =null);
        // Set mFirstTouchTarget to null.
        mFirstTouchTarget = null; }}Copy the code

DispatchTransformedTouchEvent method:

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

    final int oldAction = event.getAction();
    if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
        // Cancel is true, or a Cancel event is passed, setting the Cancel event
        event.setAction(MotionEvent.ACTION_CANCEL);
        if (child == null) {
            // If child is empty, the View's dispatchTouchEvent method is called to distribute it
            // call onTouchEvent ();
            handled = super.dispatchTouchEvent(event);
        } else {
            // Call subclass distribution if child is not null
            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 (newPointerIdBits == 0) {
        return false;
    }

    final MotionEvent transformedEvent;
    if (newPointerIdBits == oldPointerIdBits) {
        if (child == null || child.hasIdentityMatrix()) {
            if (child == null) {
                // Distribute events to yourself
                handled = super.dispatchTouchEvent(event);
            } else {
                final float offsetX = mScrollX - child.mLeft;
                final float offsetY = mScrollY - child.mTop;
                event.offsetLocation(offsetX, offsetY);
                // Distribute events to child views
                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;
}
Copy the code
  • How is a ViewGroup distributed to child views?

    The DOWN event: According to the above source code analysis, the ViewGroup in the event distribution, first in the DOWN event reset some state, and then in the code ① to ask whether to intercept, because the first incoming event is ACTION_DOWN event, And mFirstTouchTarget is reset to NULL, so onInterceptTouchEvent is called to ask if it is intercepted. If you don’t intercept, go to code ②, iterate through the subview, and look for subclasses in the click area (code ③), And then through the code (4) dispatchTransformedTouchEvent method call dispatchTouchEvent method for distribution of the child. The event is then distributed to the child View.

    At this point, if the child View consumption the DOWN event (child dispatchTouchEvent returns true – > dispatchTransformedTouchEvent method returns true). Then code ⑤ is executed to bind the View to the TouchTarget list by addTouchTarget at the table header and assign both newTouchTarget and mFristTouchTarget to the table header. If not, the next loop looks for a subview that consumes the event.

    So what if the ViewGroup does event interception? How should event distribution be performed? There are two cases: 1. Intercept the DOWN event directly; 2. 2. Intercept MOVE and UP events.

    1. If the DOWN event is directly intercepted, mFirstTouchTarget is null and intercepted is true. So direct execution to code 6, through dispatchTransformedTouchEvent source, directly execute the super. DispatchTouchEvent (event). The onTouchEvent method is called inside the dispatchTouchEvent method of the View (the ViewGroup parent is View), and the event is passed to the onTouchEvent method of the ViewGroup.
    2. Intercept starts when MOVE and UP events arrive, and CANCEL events are generated. Specific analysis in extended knowledge 1: CANCEL event generation internal implementation.

    MOVE and UP events: If MOVE and UP events can be passed, then a child View consumes DOWN events. MFristTouchTarget is not null. Code execution to all landowners, through dispatchTransformedTouchEvent method will be distributed to the consumer the DOWN events of the View.

  • Extension 1: Internal implementation of CANCEL event generation.

    The condition for the CANCEL event is that the child View consumes the DOWN event, and in MOVE and UP events, the parent View intercepts the event. ==CANCEL event simulation == In the first MOVE event, the child View receives the CANCEL event, and in the second MOVE event, the onTouchEvent of the ViewGroup receives the event. So let’s analyze the process:

    Since the child View consumes the DOWN event, mFirstTouchTarget is not null. When the first MOVE event comes, the code executes into code ①, where onInterceptTouchEvent returns true, so intercepted is assigned true. The code then executes to code ⑦. If intercepted is true, cancelChild is true. By dispatchTransformedTouchEvent source, whether the cancel is true, and set up a cancel events to give the child. In code 8, the TouchTarget is set to null because it is a single touch and only the TouchTarget object is bound to the View. Next is null, so mFirstTouchTarget is assigned null. Thus, the child receives a CANCEL event when the parent controls intercept.

    The second MOVE events: because the mFirstTouchTarget is null, direct call the code 6, through dispatchTransformedTouchEvent calls its own onTouchEvent method.

    As you can also see, once the ViewGroup intercepts, mFirstTouchTarget is reset to NULL, and subsequent events are directly set to true without calling the onInterceptTouchEvent method.

  • Problem two: about requestDisallowInterceptTouchEvent

    RequestDisallowInterceptTouchEvent itself is the method in ViewParent, ViewParent is an interface, ViewGroup implements it. We subclass View by calling getParent () requestDisallowInterceptTouchEvent (true); To achieve the request parent control does not intercept.

    / / ViewGroup class
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
    
        if(disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0)) {
            // We're already in this state, assume our ancestors are too
            return;
        }
        // Set the mGroupFlags flag.
        if (disallowIntercept) {
            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
        } else {
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }
    
        // Pass it up to our parent
        if(mParent ! =null) { mParent.requestDisallowInterceptTouchEvent(disallowIntercept); }}Copy the code

    Final Boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT)! = 0; To determine whether the subclass View is set to not allow intercepting flags, and then to determine whether it itself intercepts. Set intercepted to false if interception is not allowed.

    In a DOWN event, the ViewGroup resets the mGroupFlags flag first. So it is not as if the child View does not block once, and the parent control never blocks.

    private void resetTouchState(a) {
        clearTouchTargets();
        resetCancelNextUpFlag(this);
        / / reset mGroupFlags
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        mNestedScrollAxes = SCROLL_AXIS_NONE;
    }
    Copy the code

View’s dispatchTouchEvent method

/ / the View class
public boolean dispatchTouchEvent(MotionEvent event) {
    boolean result = false;

    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)) {
            // If mOnTouchListener is set and onTouch returns true
            // return true
            result = true;
        }
        // Otherwise, call the View's onTouchEvent method
        if(! result && onTouchEvent(event)) { result =true; }}return result;
}
Copy the code

View’s onTouchEvent method.

public boolean onTouchEvent(MotionEvent event) {
    //------ code omitted -------
    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
                
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        if(action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) ! =0) {
            setPressed(false);
        }
        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
        // An unavailable View can still consume the event, but it has no response.
        // Decide whether to consume the event based on the value of clickable.
        return clickable;
    }
    // Whether the touch proxy is set (if you are trying to expand the View's touch range, you can use the touch proxy)
    if(mTouchDelegate ! =null) {
        // If the touch proxy is set, the event is passed to the touch proxy's onTouchEvent method.
        
        if (mTouchDelegate.onTouchEvent(event)) {
            return true; }}// If clickable is true, execute the following code and return a true consumption event.
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                //------ code omitted -------
                break;
            case MotionEvent.ACTION_DOWN:
                //------ code omitted -------
                break;
            case MotionEvent.ACTION_CANCEL:
                //------ code omitted -------
                break;
            case MotionEvent.ACTION_MOVE:
                //------ code omitted -------
                break;
                
        }
        return true;
    }
    return false;
}
Copy the code

As you can see, the dispatchTouchEvent of the View first checks whether mOnTouchListener is set, If mOnTouchListener is set and monTouchListener. onTouch returns true, the View’s dispatchTouchEvent method returns true, The onTouchEvent method of the View is not called. Otherwise, call the View’s onTouchEvent method.

In the View onTouchEvent method, we can see that if the click event and the long press event are set, the onTouchEvent must return true. Ultimately, this is the end of the story.

Since the OnLongClickListener and OnClickListener callback are called in the onTouchEvent method, when we set setOnTouchListener and onTouch returns true, SetOnLongClickListener and setOnClickListener are not called back.

View TouchEvent detailed process: View event distribution (three) source analysis

conclusion

  1. The complete event dispatch process is: Activity > PhoneWindow > DecorView > ViewGroup > View.
  2. A ViewGroup finds a child View that handles a DOWN event by traversing its child View in reverse order, Through dispatchTransformedTouchEvent method call child. DispatchTouchEvent will be distributed to childView event. The View that consumes the DOWN event is then bound to the linked list of touches. MFirstTouchTarget points to the header.
  3. Once the ViewGroup starts intercepting events, it issues a CANCEL event to the View that previously handled the event if a child View previously handled the event. Subsequent events are no longer distributed to child views and the onInterceptTouchEvent method is no longer called.
  4. Each DOWN event resets mGroupFlags and mFirstTouchTarget. The child View call requestDisallowInterceptTouchEvent method, the scope is limited to the current flow of events.
  5. If OnTouchListener is set and onTouch returns true, the OnLongClickListener and OnClickListener callbacks do not take effect.

This article is used to record the understanding and notes in the learning process. If there are any mistakes, please correct them. Thank you very much!

Reference documentation

Chapter 3 of Exploring the Art of Android Development

Android ViewGroup event distribution mechanism

View – Event distribution