preface

Before looking at the event distribution mechanism, let’s take a broad look at the role of several functions

The dispatchTouchEvent function can be thought of as the general entry point for event distribution;

OnInterceptTouchEvent Event intercepting function, only in the ViewGroup, view does not have this function;

OnTouchEvent can be thought of as a function for event handling;

DispatchTransformedTouchEvent event distribution function, usually in the dispatchTouchEvent call, this function is mainly deal with two cases of distribution, one kind is distributed to the view, one kind is distributed to the current view/viewgroup;

RequestDisallowInterceptTouchEvent request parent viewgroup don’t intercept events, typically in child view call, The general method of use is in the child call getParent. In view of the dispatchTouchEvent requestDisallowInterceptTouchEvent (true);

1. Event distribution process –ViewGroup

Activity -> dispatchTouchEvent

As shown below, the entry for event distribution starts with the Activity’s dispatchTouchEvent method;

If it is a Down event, the onUserInteraction method is first executed. This method is an empty implementation in the Activity. We can override this method in our own Activity and then listen for down events.

The call to getWindow in comment 2 results in PhoneWindow, so the actual call is PhoneWindow superDispatchTouchEvent. If comment 2 returns false, meaning that the event was not handled, comment 3 calls the Activity’s onTouchEvent;

public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); //1 } if (getWindow().superDispatchTouchEvent(ev)) { //2 return true; } return onTouchEvent(ev); / / 3}Copy the code

PhoneWindow -> superDispatchTouchEvent

The phoneWindow superDispatchTouchEvent is called again in DecorView at comment 1. Comment 2 indicates that the dispatchTouchEvent of the ViewGroup is called again;

public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event); //1 } public boolean superDispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); / / 2}Copy the code

ViewGroup -> dispatchTouchEvent

Then the ViewGroup dispatchTouchEvent method, which is longer and contains the main distribution logic;

Comment 1 indicates that when the event is a click event, the TouchTarget is cleared and the TouchState is reset.

The disallowIntercept variable at comment 2 is set by the FLAG_DISALLOW_INTERCEPT flag bit. Where is this flag bit set? Is set in the requestDisallowInterceptTouchEvent method, when the view request parent view don’t intercept calls this method, will not enter the comment 3 onInterceptTouchEvent method, thus it won’t be stopped;

Here we may have a question, event distribution from the upper layer to the lower layer, the parent view directly blocks the event, how can the child view request the parent view not to block? In fact, after the event is distributed, the Down event is usually not intercepted, and then it goes into the View’s diapatchTouchEvent, and we can call a method in this method and ask the parent view not to intercept, and then when the move event comes in, because the flag bit is set to true, Even if it is intercepted in the onInterceptTouchEvent method of the viewGroup, it does not enter the onInterceptTouchEvent method of the viewGroup, so it cannot intercept the event.

The onInterceptTouchEvent in comment 3 is used by the parent viewGroup to intercept events. Generally, the parent viewGroup does not intercept down events. Then, when the parent viewGroup receives a move event, it checks whether the event needs to be intercepted. It also sends a Cancel event to the child view, and subsequent move events do not continue to the child view;

DispatchTransformedTouchEvent for comments 4 and 5 points to the child view or distribute their own events; This will be analyzed later;

Public Boolean dispatchTouchEvent(MotionEvent ev) {,,, 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) { cancelAndClearTouchTargets(ev); //1 resetTouchState(); } // Check for interception. final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! = null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! = 0; if (! disallowIntercept) { //2 intercepted = onInterceptTouchEvent(ev); //3 ev.setAction(action); } else { intercepted = false; } } else { intercepted = true; } TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; if (! canceled && ! intercepted) { if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)  || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount ! = 0) { final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; I -) {,,,, / / 4 the if (dispatchTransformedTouchEvent (ev, false, child, idBitsToAssign)) {,,,,}} the if (preorderedList! = null) preorderedList.clear(); } } } // Dispatch to touch targets. if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); //5} else {,,,}} return handled; }Copy the code

4, ViewGroup – > dispatchTransformedTouchEvent

Comments 1 and 2 deal mainly with the dispatch of the Cancel event; Call comment 2 to distribute the Cancel event to the child view when child is not null;

Comments 3 and 4 are for distribution of normal event flow; When child is empty, comment 3 is called, where super stands for View; When child is not null, comment 4 is called. If the child is of type ViewGroup, then the dispatchTouchEvent of type ViewGroup will be sent again. If the child is of type View, It goes to dispatchTouchEvent of the View;

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) { event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { handled = super.dispatchTouchEvent(event); //1 } else { handled = child.dispatchTouchEvent(event); //2 } event.setAction(oldAction); return handled; } // Perform any necessary transformations and dispatch. if (child == null) { handled = super.dispatchTouchEvent(transformedEvent); //3} else {handled = child.dispatchTouchEvent(transformedEvent); //4 } // Done. transformedEvent.recycle(); return handled; }Copy the code

5, ViewGroup – > requestDisallowInterceptTouchEvent

When a child view requests the ViewGroup not to intercept an event, the following method is called: Set the mGroupFlags variable to the corresponding flag bit;

public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! = 0)) { // We're already in this state, assume our ancestors are too return; } if (disallowIntercept) { mGroupFlags |= FLAG_DISALLOW_INTERCEPT; //1 } else { mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; / / 2}}Copy the code

ViewGroup -> onInterceptTouchEvent

The onInterceptTouchEvent method of the ViewGroup is shown as follows. It can be seen that the ViewGroup does not intercept events. So RecyclerView, ScrollView, etc., are generally used to rewrite the onInterceptTouchEvent, and then determine whether to intercept in the move event;

public boolean onInterceptTouchEvent(MotionEvent ev) {
    if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
            && ev.getAction() == MotionEvent.ACTION_DOWN
            && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
            && isOnScrollbarThumb(ev.getX(), ev.getY())) {
        return true;
    }
    return false;
}
Copy the code

2. Event distribution process –View part

1, View -> dispatchTouchEvent

When the event is sent to the View, the View’s dispatchTouchEvent method will be entered. The onTouch method will be called at comment 1 first. If onTouch returns true, the result will be judged to be true at comment 2, so the onTouchEvent method will not be entered. So where is this onTouch method set up? Call the View’s setOnTouchListener method, as shown in comment 3;

Public Boolean dispatchTouchEvent(MotionEvent event) {,,, final int actionMasked = event.getactionmasked (); if (actionMasked == MotionEvent.ACTION_DOWN) { // Defensive cleanup for new gesture stopNestedScroll(); } if (onFilterTouchEventForSecurity(event)) { if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) { result = true; } //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li ! = null && li.mOnTouchListener ! = null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { //1 result = true; } if (! result && onTouchEvent(event)) { //2 result = true; }},,, return result; } public void setOnTouchListener(OnTouchListener l) { getListenerInfo().mOnTouchListener = l; / / 3}Copy the code

2, View -> onTouchEvent

The View’s onTouchEvent looks like this, and we’ll focus on the performClickInternal method;

The clickable is set to true by the setOnClickListener method, so if we set the view’s setOnClickListener, It goes to the performClickInternal method in comment 2, which executes the onClick method, which we’ll examine later;

Then comment 3 returns true to indicate that the event was consumed; Another important point to note is that the performClickInternal method is executed within the MotionEvent.ACTION_UP branch, meaning that only up events are triggered;

Public Boolean onTouchEvent (MotionEvent event) {,,,, if (clickable | | (viewFlags & TOOLTIP) = = TOOLTIP) {/ / 1 switch (action) {case MotionEvent.ACTION_UP:,,, if ((mPrivateFlags & PFLAG_PRESSED)! = 0 | | prepressed) {/ / take focus if we don 't have it already and we should / / in touch mode,,,,, if (! mHasPerformedLongPress && ! mIgnoreNextUpEvent) { // Only perform take click actions if we were in the pressed state if (! focusTaken) { // Use a Runnable and post this rather than calling // performClick directly. This lets other visual state  // of the view update before click actions start. if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (! post(mPerformClick)) { performClickInternal(); //2 } } } } mIgnoreNextUpEvent = false; break; Case MotionEvent.ACTION_DOWN:,,, break; Case MotionEvent.ACTION_CANCEL:,,, break; Case MotionEvent.ACTION_MOVE:,,, break; } return true; //3 } return false; } public void setOnClickListener(@Nullable OnClickListener l) { if (! isClickable()) { setClickable(true); } getListenerInfo().mOnClickListener = l; }Copy the code

3, View -> performClickInternal

The call chain for the View’s performClickInternal is shown below, and in comment 2 we can see that the onClick method is finally called, which is the listener passed in from our usual call to setOnClickListener. Then call onClick;

private boolean performClickInternal() { // Must notify autofill manager before performing the click actions to avoid scenarios where // the app has a click listener that changes the state of views the autofill service might // be interested on. notifyAutofillManagerOnClick(); return performClick(); //1 } public boolean performClick() { final boolean result; final ListenerInfo li = mListenerInfo; if (li ! = null && li.mOnClickListener ! = null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this); //2 result = true; } else { result = false; },,, return result; }Copy the code