[learning with questions] android event distribution 8 questions

Posted by lynx2003 on Tue, 21 Dec 2021 22:21:19 +0100

preface

android event distribution is a necessary skill in our development, but the knowledge about event distribution is also a little complicated.
If we read the source code from the beginning, we are often confused and can't grasp the essentials.
We can master this knowledge from the following questions.
Finally, readers can judge whether they have really mastered this knowledge point by judging whether their problems have been solved

First of all, we can think about what happened from our touch screen to App response events, and which parts can be divided into?
We can decompose the whole Touch event into the following parts

  • 1. How do touch events from the screen to our App
  • 2. How to transfer touch events to the corresponding page after they arrive at the App
  • 3. How to distribute touch events internally after they reach the corresponding page

Article 3, which is closely related to the upper software development, is also our most concerned. It can also be disassembled into the following issues

  • Whether the ViewGroup intercepts events and how to deal with them after intercepting and not intercepting?
  • Whether the sub View intercepts events, and how to deal with them after intercepting and not intercepting?
  • View Group and child views are not intercepted. How to deal with the final event?
  • How to handle event conflicts?

The above problems are summarized as the mind map as follows:

Next, it will be analyzed in detail

1. How do touch events from the screen to our App

1.1 hardware and kernel

When we touch the screen or key operation, the first trigger is the hardware driver
After the driver receives the event, it writes the corresponding event to the input device node, which produces the most original kernel event
When the screen is touched, the Linux kernel will package the touch Event generated by the hardware as an Event and save it in the / dev/input/event[x] directory

The purpose of this is to encapsulate the input Event into a general Event for subsequent processing

1.2 system server

We know that when the system starts, a series of system services will be started in the system server process, such as AMS,WMS, etc
Another one is InputManagerService, which manages event input

This service is used to communicate with hardware and accept screen input events.
Internally, a read thread, that is, InputReader, will be started. It will get tasks from the system, that is, the / dev/input / directory, distribute them to the InputDispatcher thread, and then conduct unified event distribution scheduling.

1.3 cross process communication to App

Now the system process has received the input event, but it still needs to be passed to the App process, which involves cross process communication
The communication between Window and InputManagerService in our App actually uses InputChannel
InputChannel is a pipe, and the bottom layer actually communicates through socket.
We know that viewrootimpl. Is called when the Activity starts setView()
In viewrootimpl During setview(), InputChannel will also be registered:

public final class ViewRootImpl {
  public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
      requestLayout();
      // ...
      // Create InputChannel
      mInputChannel = new InputChannel();
      // Complete the registration of InputChannel in the system server process through Binder
      mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
  }
}
Copy code

This involves the cross process communication between WindowManagerService and Binder. Readers don't need to worry about the details
Just know that in the SystemServer process, WindowManagerService creates SocketPair according to the current Window for cross process communication, and registers the InputChannel passed from the App process
After that, the InputChannel in ViewRootImpl points to the correct InputChannel. As the Client side, its fd and the fd on the Server side of the system Server process form a SocketPair, and they can communicate in both directions.

Then, the main thread of our App process will listen to the socket client. After receiving the message (input event), call back the NativeInputEventReceiver.handleEvent() method, and finally go to inputeventreceiver Dispachinputevent method.

After the above operations, the App finally gets the input event, and then passes it to the corresponding page

1.4 summary

Generally speaking, the part of kernel processing input events and cross process communication is not the most concerned part of application developers, nor the focus of this article, so it is only an overview
For details, please refer to: Input system - whole process of event handling

2. How to transfer touch events to the corresponding page after they arrive at the App

Now that we have got the input event in the App process, let's see how the event is distributed to the page
Let's follow the source code

2.1 event return to ViewRootImpl

//InputEventReceiver.java
private void dispatchInputEvent(int seq, InputEvent event) {
    mSeqMap.put(event.getSequenceNumber(), seq);
    onInputEvent(event); 
}
//ViewRootImpl.java ::WindowInputEventReceiver
final class WindowInputEventReceiver extends InputEventReceiver {
    public void onInputEvent(InputEvent event) {
       enqueueInputEvent(event, this, 0, true); 
    }
}
//ViewRootImpl.java
void enqueueInputEvent(InputEvent event,
        InputEventReceiver receiver, int flags, boolean processImmediately) {
    adjustInputEventForCompatibility(event);
    QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
    QueuedInputEvent last = mPendingInputEventTail;
    if (last == null) {
        mPendingInputEventHead = q;
        mPendingInputEventTail = q;
    } else {
        last.mNext = q;
        mPendingInputEventTail = q;
    }
    mPendingInputEventCount += 1;
    if (processImmediately) {
        doProcessInputEvents(); 
    } else {
        scheduleProcessInputEvents();
    }
}
Copy code

You can see that the event still returns to ViewRootImpl. It can be seen that ViewRootImpl is not only responsible for drawing the interface, but also responsible for passing events

2.2 first responsibility chain distribution

Next, go to doProcessInputEvents, which involves the first responsibility chain distribution in event distribution

   void doProcessInputEvents() {
   		...
        // Deliver all pending input events in the queue.
        while (mPendingInputEventHead != null) {
            QueuedInputEvent q = mPendingInputEventHead;
            mPendingInputEventHead = q.mNext;
            deliverInputEvent(q);
        }
        ....
    }
    private void deliverInputEvent(QueuedInputEvent q) {
        InputStage stage;
        ....
        //stage assignment operation
        ....
        if (stage != null) {
            stage.deliver(q);
        } else {
            finishInputEvent(q);
        }
    }
    abstract class InputStage {
        private final InputStage mNext;
        public InputStage(InputStage next) {
            mNext = next;
        }
        public final void deliver(QueuedInputEvent q) {
            if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
                forward(q);
            } else if (shouldDropInputEvent(q)) {
                finish(q, false);
            } else {
                traceEvent(q, Trace.TRACE_TAG_VIEW);
                final int result;
                try {
                    result = onProcess(q);
                } finally {
                    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
                }
                apply(q, result);
            }
        }
    }
Copy code

As shown above:
1.QueuedInputEvent is an input event, which is a linked list structure, traversed and passed to InputStage
2.InputStage is the responsibility chain for processing input. When calling deliver y, it will traverse the responsibility chain and pass events
3. After the event is distributed, the finishInputEvent will be called to inform the InputDispatcher thread of the SystemServer process. Finally, the event will be removed to complete the distribution and consumption of this event.

So the question is, when is the responsibility chain of InputStage?

2.3 assembly responsibility chain

We have to go back to viewrootimpl In setview method

	public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
        	...
	        // Set up the input pipeline.
	        mSyntheticInputStage = new SyntheticInputStage();
	        InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
	        InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
	                 "aq:native-post-ime:" + counterSuffix);
	        InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
	        InputStage imeStage = new ImeInputStage(earlyPostImeStage,
	                "aq:ime:" + counterSuffix);
	        InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
	        InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
	                        "aq:native-pre-ime:" + counterSuffix);
	        mFirstInputStage = nativePreImeStage;
	        mFirstPostImeInputStage = earlyPostImeStage;
	        ....
	   }
    }
Copy code

You can see that in the setView method, the responsibility chain of input event processing is spliced. Different InputStage subclasses are connected one by one through the construction method. What are these inputstages doing?

  • SyntheticInputStage. Comprehensive event processing stage, such as handling navigation panel, joystick and other events.
  • ViewPostImeInputStage. View input processing stage, such as key press, finger touch and other motion events. The well-known view event distribution occurs in this stage.
  • NativePostImeInputStage. In the local method processing phase, the delay queue is mainly constructed.
  • EarlyPostImeInputStage. Early processing stage of input method.
  • ImeInputStage. The input method event processing stage processes input method characters.
  • ViewPreImeInputStage. In the view preprocessing input method event stage, call the dispatchKeyEventPreIme method of view.
  • NativePreImeInputStage. The local method preprocesses the input method event phase.

To summarize, when an event reaches the main thread of the application side, it will process a series of InputStage events through ViewRootImpl. This stage is actually a simple classification of events, such as view input events, input method events, navigation panel events, etc.
Our View touch event occurs in the ViewPostImeInputStage stage

    final class ViewPostImeInputStage extends InputStage {
        @Override
        protected int onProcess(QueuedInputEvent q) {
            if (q.mEvent instanceof KeyEvent) {
                return processKeyEvent(q);
            } else {
                final int source = q.mEvent.getSource();
                if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                    return processPointerEvent(q);
                } 
            }
        }
    private int processPointerEvent(QueuedInputEvent q) {
            final MotionEvent event = (MotionEvent)q.mEvent;
            boolean handled = mView.dispatchPointerEvent(event)
            return handled ? FINISH_HANDLED : FORWARD;
        }
//View.java
    public final boolean dispatchPointerEvent(MotionEvent event) {
            if (event.isTouchEvent()) {
                return dispatchTouchEvent(event);
            } else {
                return dispatchGenericMotionEvent(event);
        }
    }
Copy code

1. After layers of callback, it will call Mview dispatchPointerEvent
2. We know that mView in ViewRootImpl is DecorView

Now the event has been passed to DecorView, which is the root layout of our interface
The next step is the transfer of events in activity, window and decorview

2.4 event transfer in activity, window and decorview

//DecorView.java
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //cb is actually the corresponding Activity/Dialog
        final Window.Callback cb = mWindow.getCallback();
        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
    }
//Activity.java
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
//PhoneWindow.java
    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }
//DecorView.java
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }    
Copy code

You can see the event distribution process: decorview - > activity - > phonewindow - > decorview
It seems to be a very strange event flow. The event starts from DecorView and finally returns to DecorView. Why do you do this?

2.4. 1 why doesn't ViewRootImpl directly hand over the event to the Activity?

Mainly for lotus root
ViewRootImpl doesn't know that there is an Activity! It just holds DecorView. Therefore, the touch event cannot be directly sent to the Activity dispatchTouchEvent()

2.4. 2. After handing over to Acitivity, why not directly hand over to DecorView to start distributing events?

Because Activity doesn't know there is a DecorView!
However, the Activity holds the PhoneWindow. Of course, the PhoneWindow knows what is in its own window, so it can send events to DecorView.
In Android, the Activity does not know what is in its Window, so the coupling is very low. The Activity does not need to know the specific content in the Window

2.5 summary

After the above process, the event finally came to the familiar ViewGroup dispatchTouchEvent
The flow chart is as follows:

3. How to distribute touch events internally after they arrive at the page

The following is the most commonly used and common event distribution

3.1 whether ViewGroup intercepts events

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        final boolean intercepted;
        //Only when ActionDown or mFirstTouchTarget is empty will it be judged whether to intercept
        if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                intercepted = onInterceptTouchEvent(ev);
            } 
        } 
        if (!canceled && !intercepted) {
            //Event passed to child view
            ....
            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
            	...
            	//If the child View is consumed, assign a value to mFirstTouchTarget
            	newTouchTarget = addTouchTarget(child, idBitsToAssign);
            	...
            }
        }
        //Dispatchtransformendouchevent is called when mFirstTouchTarget is not empty
        if (mFirstTouchTarget == null) {
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
        }
    }
    private boolean dispatchTransformedTouchEvent(View child) {
        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else { 
            handled = child.dispatchTouchEvent(event);
        }
    }
Copy code

As can be seen from the above
1. Only when action_ Whether to intercept is determined only when down or mFirstTouchTarget is not empty
2.mFirstTouchTarget is a linked list structure, which represents that a child View consumes events. If it is null, it means that no child View consumes events
3. There is a disallowaintercept field before judging whether to intercept, which will be used in the internal interception method of subsequent event conflicts
4. Next comes onintercepttouchevent. This method controls whether the ViewGroup intercepts events

3.1. 2 what happens after ViewGroup intercepts?

1. After interception, the event will not be sent to the child View
2. next if mFirstTouchTarget is null, it will be called to dispatchTransformedTouchEvent and then called to super.. Dispatchtouchevent, and finally to ViewGroup onTouchEvent
3. Why use mFirstTouchTarget==null to judge whether it is processed by ViewGroup? There are two situations: one is ViewGroup interception, the other is that the child View does not process events, and both cases are finally called back to ViewGroup onTouchEvent

Through the above analysis, we can get the pseudo code intercepted by ViewGroup:

public boolean dispatchTouchEvent(MotionEvent event) {
    boolean isConsume = false;
    if (isViewGroup) {
        if (onInterceptTouchEvent(event)) {
            isConsume = super.dispatchTouchEvent(event);
        } 
    } 
    return isConsume;
}
Copy code

If it is a ViewGroup, it will first execute the onInterceptTouchEvent method to determine whether to intercept it. If it is intercepted, it will execute the dispatchTouchEvent method of the parent class View.

3.1. 3 what happens if the ViewGroup does not intercept?

If the ViewGroup does not intercept, it is passed to the child View

    if (!canceled && !intercepted) {
        if (actionMasked == MotionEvent.ACTION_DOWN
                || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            final int childrenCount = mChildrenCount;
        	//Traversal subview
            if (newTouchTarget == null && childrenCount != 0) {
                for (int i = childrenCount - 1; i >= 0; i--) {
                    final int childIndex = getAndVerifyPreorderedIndex(
                            childrenCount, i, customOrder);
                    final View child = getAndVerifyPreorderedView(
                            preorderedList, children, childIndex);
                    //2. Determine event coordinates
                    if (!child.canReceivePointerEvents()
                            || !isTransformedTouchPointInView(x, y, child, null)) {
                        ev.setTargetAccessibilityFocus(false);
                        continue;
                    }
                    //3. Deliver events
                    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                        newTouchTarget = addTouchTarget(child, idBitsToAssign);
                        alreadyDispatchedToNewTouchTarget = true;
                        break;
                    }
                }
            }
        }
    }
    private boolean dispatchTransformedTouchEvent(View child) {
        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else { 
            handled = child.dispatchTouchEvent(event);
        }
    }
Copy code

If it is not intercepted, the ViewGroup mainly does the following things
1. Traverse all child views of the current View Group
2. Judge whether the current View is within the coordinate range of the current sub View. If it is not within the range, events cannot be received. Skip directly
3. Use dispatchTransformedTouchEvent. If it returns true, assign a value to mFirstTouchTarget through addTouchTarget
4.dispatchTransformedTouchEvent mainly does two things. If the child is not null, the event will be distributed to the child, otherwise super Dispatchtouchevent and finally return the result
5.mFirstTouchTarget is a single linked list structure that records the consumption chain. However, this feature is not used at the time of single touch. It is just an ordinary TouchTarget object

3.2 block subview

public boolean dispatchTouchEvent(MotionEvent event) {
        if (onFilterTouchEventForSecurity(event)) {
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
        return result;
    }
Copy code

The diapatchTouchEvent logic of the subview is relatively simple
1. If setOnTouchListener is set and returns true, onTouchEvent will not be executed
2. Otherwise, execute onTouchEvent, and our commonly used onclicklistener is triggered in onTouchEvent

Therefore, onTouchEvent will be executed directly by default. If setOnClickListener or setLongClickListener is set, it will be triggered normally

3.2. 1. What will happen if there is a consumer event?

As mentioned above, if the child View consumes an event, the dispatchTouchEvent method returns true
Indicates that I have handled this event, and the event will end from then on, and the dispatchTouchEvent of ViewGroup will also return true
Finally, return to the Activity's dispatchTouchEvent, which also returns true directly

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

Summary: if the child View consumes the event, the event is over

3.2. 2 what happens if you don't consume the event?

The sub View does not intercept the event, then mFirstTouchTarget is null. After the loop is dropped, the dispatchTransformedTouchEvent method is called.

    if (mFirstTouchTarget == null) {
        handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
    }
Copy code

To summarize:
1. The child View does not intercept the event, but calls back to the dispatchTransformedTouchEvent
2. Then it was transferred to super dispatchTouchEvent
3. Next, the logic of the ViewGroup is the same as that of the child View. onTouchEvent is executed by default. If setOnTouchLister is set, onTouch is executed

3.3 what happens if neither ViewGroup nor child View is intercepted

If neither View Group nor child views are intercepted, that is, mFirstTouchTarget == null,dispatchTouchEvent also returns false
Let's look at the source code of Activity

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

The answer is obvious: the onTouchEvent method of the Activity will be executed

3.4 how to distribute subsequent events?

The handler of the event distribution has been found, and it seems that the task has been completed.
But in fact, event distribution includes action_ DOWN,ACTION_MOVE,ACTION_UP,ACTION_ For a series of events of cancel, the actions we analyzed above are all actions_ Down process
How to handle subsequent events?

	public boolean dispatchTouchEvent(MotionEvent ev) {
		    if (!canceled && !intercepted) {
            if (actionMasked == MotionEvent.ACTION_DOWN
                             || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                             || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {   
                             ...
                         	//1. Traverse subview
                         	//2. Judge whether it is within the coordinate range
                            //3. Distribute events and assign values to mFirstTouchTarget
                            //4. If the distribution is successful, alreadyDispatchedToNewTouchTarget is assigned to true
                            ...      
            	}
            if (mFirstTouchTarget == null) {
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                               handled = true;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }
	}
Copy code

It can be seen from the above
1. Subsequent events will not follow the method of circular judgment of sub views, because the target View has been found and distributed directly through mFirstTouchTarget
2. If a View starts to process intercepted events, subsequent event sequences can only be processed by it

3.5 summary

  • The essence of event distribution is a recursive method. By passing it down, call the dispatchTouchEvent method to find the event handler, which is the common responsibility chain mode in the project.
  • During distribution, the ViewGroup determines whether to intercept events through onInterceptTouchEvent
  • During distribution, the default of View handles events through onTouchEvent
  • If the underlying View does not consume, the onTouchEvent method of the parent element is executed step by step by default.
  • If all the onTouchEvent methods of the View return false, the onTouchEvent method of the Activity will be executed finally, and the event distribution will end.

4. Conflict resolution

We often encounter the problem of sliding conflict in development. For example, a page slides in both horizontal and vertical directions at the same time. At this time, we need to click action according to the situation_ Judge and intercept events during move
There are two common sliding conflict resolution methods:
1. External interception law
2. Internal interception method

4.1 external interception method

The principle of external interception method is very simple, which is carried out through onInterceptTouchEvent we analyzed above
The template code of external interception method is as follows:

    //External interception method: parent view java      
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercepted = false;
        //Parent view interception condition
        boolean parentCanIntercept;
        switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                intercepted = false;
                break;
            case MotionEvent.ACTION_MOVE:
                if (parentCanIntercept) {
                    intercepted = true;
                } else {
                    intercepted = false;
                }
                break;
            case MotionEvent.ACTION_UP:
                intercepted = false;
                break;
        }
        return intercepted;
    }
Copy code

But this approach will bring a problem if action_ When down is handed over to the child view for processing, subsequent events should be directly distributed to the view. Why can it be intercepted by the parent view?
Let's take another look at the dispatchTouchEvent method

    public boolean dispatchTouchEvent(MotionEvent ev) {
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {
        	//1. Judge interception
            intercepted = onInterceptTouchEvent(ev);
        } 
        // Dispatch to touch targets.
        if (mFirstTouchTarget == null) {
        	//4. Subsequent events are directly handled by ViewGroup
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
        } else {
            while (target != null) {
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                	//2.cancelChild is true
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                        target.child, target.pointerIdBits)) {
                        handled = true;
                    }
                    if (cancelChild) {
                       if (predecessor == null) {
                       	   //3.mFirstTouchTarget is set to null
                           mFirstTouchTarget = next;
                       } else {
                           predecessor.next = next;
                       }
                       target.recycle();
                       target = next;
                       continue;
                    }
                }
            }
        }
    }
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
        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;
        }
    }
Copy code

As can be seen above:
1. First, intercept the event through the onInterceptTouchEvent method
2. If intercepted is true, cancelChild is also true, and the dispatchTransformedTouchEvent method passes Action_CANCEL to child View
3. After cancelchild, set mFirstTouchTarget to null
4. After mfirsttouchtarget is empty, subsequent events are handled by ViewGroup

To sum up, this is the reason why the external interception method can succeed

4.2 internal interception method

Next, let's look at the template code of the internal interception method

    //Parent view java            
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
            return false;
        } else {
            return true;
        }
    }
    //Subview java
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        //Parent view interception condition
        boolean parentCanIntercept;
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                getParent().requestDisallowInterceptTouchEvent(!parentCanIntercept);
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return super.dispatchTouchEvent(event);
    }
Copy code

The internal interception method is to give the initiative to the child View. If the child View needs events, it will be consumed directly. Otherwise, it will be handed over to the parent container for processing
The internal interception method is mainly controlled by the requestdisallowintercepttuchevent method

Let's see why calling this method can achieve internal interception

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        final boolean intercepted;
        //Only when ActionDown or mFirstTouchTarget is empty will it be judged whether to intercept
        if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                intercepted = onInterceptTouchEvent(ev);
            } 
        } 
    }
Copy code

As shown above, the principle is simple
1. The child View controls the value of mGroupFlags through requestdisallowintercepttuchevent, so as to control the value of disallowIntercept
2. When disallowIntercept is true, it will not go to onInterceptTouchEvent, and the external cannot be intercepted. When external processing is required, set disallowIntercept to false

summary

This paper summarizes the detailed process of event distribution mechanism from screen to View in detail. The following questions are listed for readers' reference to facilitate readers to judge whether they really master this knowledge point

  • 1. Briefly describe how events are transferred from the screen to the View
  • 2. How many times did the responsibility chain distribute during the event distribution?
  • 3. Why events are distributed from decorview - > activity - > phonewindow - > decorview
  • 4. How many solutions to sliding conflict? Introduce them respectively
  • 5. If only in the Action of onintercepttuchevent_ Intercept events in move. Tell me how actions from ViewGroup to View are passed
  • 6. Click a View in the ViewGroup, then move your finger to another place and lift it up. How are events distributed
  • 7. What is the relationship between OnTouch and OnTouchEvent in view? What about OnTouch and OnClick events?
  • 8. Write the pseudo code of the long press event

reference material

Thumb reporter went deep into Android company to find out the secrets behind the event distribution mechanism
Daily question: did the event arrive at DecorView or Window first?
Reflection on the design and implementation of Android event distribution mechanism
A view event was distributed. The interviewer asked for six times and hit my soul. I was abused to the skin