Distribution of Android Touch events is an essential skill for Android engineers. There are several main directions for in-depth analysis of event distribution:

  1. How touch events are passed from the driver to the Framework InputManagerService;

  2. How WMS passes events to target Windows via ViewRooImple;

  3. How touch events are passed to internal child Views once they arrive at the DecorView.

Which is closely related to the upper software development is article 3, which is also the focus of this class.


Before delving into the event distribution source code, there are two concepts that need to be clarified.

  • ViewGroup 

A ViewGroup is a group of views, which may contain multiple sub-views. When a finger touches the screen, the area where the finger is located can be either within the ViewGroup display scope or on its internal View control.

Therefore, the focus of event distribution within it is to deal with the logical relationship between the current Group and sub-view;

  1. Whether the current Group needs to intercept touch events;

  1. Whether touch events need to continue to be distributed to child views;

  1. How to distribute touch events to child views.

  • View

View is a simple control, can not be subdivided, the internal will not exist sub-view, so its event distribution focus on the current View how to handle the touch event, and according to the corresponding gesture logic for some columns of effect display (such as sliding, zoom, click, long press, etc.).

  1. Whether TouchListener exists;

  2. Whether to receive and handle touch events yourself (the main logic is in the onTouchEvent method).

  3. Event distribution core dispatchTouchEvent

  4. The whole distribution of events between views is essentially one big recursive function, and that recursive function is the dispatchTouchEvent method. In this recursive process, onInterceptTouchEvent is called to intercept the event, or onTouchEvent is called to handle the event.

From a macro perspective, the source code of dispatch is as follows:



As noted in the code, dispatch is divided into three main steps:

Step 1: Determine whether the current ViewGroup needs to intercept the touch event. If it does, the touch event will no longer be passed to the child View (or notify the child View with CANCEL).

Step 2: If there is no interception, distribute the event to the child View for further processing. If the child View captures the event, assign the mFirstTouchTarget value to the View that captures the touch event.

Step 3: Redistribute events according to mFirstTouchTarget.

Take a look at each step in detail: The code for Step 1 is as follows



The red box in the figure indicates the conditions for interception:

If the event is DOWN, onInterceptTouchEvent is called to check the event. If mFirstTouchTarget is not null, it means that a child View caught the event. If the child View’s dispatchTouchEvent returns true, it means that the touch event was caught. If the current ViewGroup does not intercept events in Step 1, go to Step 2.

Step 2 The specific code is as follows



A closer look at the above code shows that:

(1) in the figure shows that the premise of active event distribution is that the event is DOWN.

Step ② in the figure traverses all sub-views;

③ in the figure, determine whether the event coordinate is within the coordinate range of the sub-view, and the sub-view is not in the animation state;

In figure (4) place call dispatchTransformedTouchEvent method distributed event to View, if the child View successful capture events, will be mFirstTouchTarget assigned to the View.

Step 3 The specific code is as follows

Step 3 There are two branches.

Branch 1: If mFirstTouchTarget is null at this point, no sub-View captured the event in the event distribution described above. In this case, direct call dispatchTransformedTouchEvent methods, and introduced to the child to null, will eventually call super. DispatchTouchEvent method. It actually ends up calling its own onTouchEvent method to handle touch events. That is, if no child View catches and handles touch events, the ViewGroup handles them through its onTouchEvent method. Branch 2: mFirstTouchTarget is not null, indicating that a sub-view captured the touch event in Step 2 above, so the current and subsequent events are directly handed to the View pointed to by mFirstTouchTarget for processing. The event distribution process code demo defines the following layout file:

DownInterceptedGroup and CaptureTouchView are two custom views. Their source code is as follows:

Touch the CaptureTouchView with your finger and lift it up by sliding it some distance. The final log is printed as follows:

In the DOWN event above, the DownInterceptGroup onInterceptTouchEvent is emitted once; It then returns true in the dispatchTouchEvent of the child View CaptureTouchView, indicating that it captured and consumed the DOWN event. In this case CaptureTouchView is added to the mFirstTouchTarget in the DownInterceptGroup. Therefore, all subsequent MOVE and UP events are checked by the onInterceptTouchEvent of the DownInterceptGroup. For detailed source code, see captureTouchView.java

Why DOWN Events are special All touch events start with DOWN events, which is one of the reasons DOWN events are special. Another reason is that the processing result of the DOWN event directly affects the logic of subsequent MOVE and UP events.

In Step 2, only the DOWN event is passed to the child View for capture judgment. Once the child View is captured successfully, subsequent MOVE and UP events are performed by traversing the mFirstTouchTarget list to find the child View that previously accepted ACTION_DOWN. And assign touch events to these child views. That is, the distribution of subsequent events such as MOVE and UP depends on who captured their initial event Down.

MFirstTouchTarget mFirstTouchTarget mFirstTouchTarget

You can see that mFirstTouchTarget is a linked list of type TouchTarget. The TouchTarget is used to record the View that captured the DOWN event, stored in the Child variable in the figure above. But why a linked list type structure? Since Android devices support multiple fingers, each finger DOWN event can be saved as a TouchTarget. Determine in Step 3 that if mFirstTouchTarget is not null, the event is again distributed to the corresponding TouchTarget.

There is a bit of interesting logic in the code that continues to distribute events to child views in Step 3 above:

The red box in the figure above indicates that a child View has caught the Touch event, but the intercepted Boolean variable in the blue box is true again. In this case, event dominance is returned to the parent ViewGroup and a cancelChild == true is passed to the child View’s dispatch event.

Look at the dispatchTransformedTouchEvent method is part of the source code is as follows:

Because cancel was passed in as true and child is not null, the event is wrapped as an ACTION_CANCEL event and passed to the child.

When can this logic be triggered?

To sum up: When the parent View’s onInterceptTouchEvent returns false and then returns true in the child View’s dispatchTouchEvent (meaning that the child View caught the event), the key step is that during the subsequent MOVE, The onInterceptTouchEvent of the parent view returns true, and intercepted is reset to true. The logic above is fired, and the child control receives an ACTION_CANCEL touch event.

In fact, there is a classic example to illustrate this: when a custom View is added to a Scrollview, the Scrollview does not intercept the DOWN event by default, and the event is passed to the child controls within the Scrollview. The onInterceptTouchEvent method returns true and triggers the ScrollView only after the finger has been swiped a certain distance. When the ScrollView scrolls, the inner child View receives a CANCEL event and loses touch focus.

For example:

CaptureTouchView is a custom View with the following source code:

CaptureTouchView’s onTouchEvent returns true, indicating that it will capture and consume the touch events it receives.

After the above code is executed, the DOWN event will be passed to CaptureTouchView when the finger clicks the screen, and the finger slides the screen to scroll the ScrollView up and DOWN. At first, the MOVE event will be consumed by CaptureTouchView. But when the ScrollView starts scrolling, CaptureTouchView receives a CANCEL event and no subsequent touch events. Print log as follows:

Therefore, when we customize the View, especially the control that may be nested by ScrollView or ViewPager, do not omit the processing of CANCEL event, otherwise the UI display may be abnormal.

Conclusion:

The onInterceptTouchEvent method returns the value of the onInterceptTouchEvent method. If a child View captures and consumes the touch event, the mFirstTouchTarget is assigned; As a final step, the DOWN, MOVE, and UP events decide whether to handle the touch event themselves or redistribute it to the child View, depending on whether the mFirstTouchTarget is null. It then introduces several special points in the overall event distribution.

DOWN events are special: the starting point of the event; Decide who will consume subsequent events; MFirstTouchTarget is a linked list of views that capture touch events. CANCEL event trigger scenario: When the parent View does not intercept and then intercepts again in a MOVE event, the child View receives a CANCEL event.