Basic knowledge of

View position parameter

  • Layout coordinates: (left.top), (right.bottom) describes the position of the View relative to the parent View. It represents the coordinates of the top left and bottom right corner of the View relative to the parent View (open “Show Layout Boundaries” in Developer mode to see the box surrounded by these coordinates).
  • Absolute coordinates: (x.yRepresents the position of the View relative to the screen coordinate system. It represents the position of the top left corner of the View relative to the screen.
  • Offset coordinates: (translationX.translationYIs the View’s offset from its initial position. Modifying this property will not change the View’s Layout coordinates, but will change the View’s absolute coordinates.

Touch events

A series of sliding events will be generated in the process of finger sliding on the screen, and these events are encapsulated into one MotionEvent object after another. Through MotionEvent, we can obtain the following data: Event types Action, event location relative to View (x, Y), event location relative to screen (rawX, rawY). There are three common event types:

  • ACTION_DOWN: Triggers when the finger is pressed. Each press triggers only once.
  • ACTION_MOVE: Triggered when a finger slides across the screen, which generates multiple events.
  • ACTION_UP: Triggers when your finger moves away from the screen. Triggers only once each time you move away.

Generally, a complete sliding process will start with ACTION_DOWN event and end with ACTION_UP event, and there are 0~N ACTION_MOVE events in the middle. We call such a group of click events as an event sequence.

TouchSlop

TouchSlop is the minimum sliding distance set by the system. When the sliding distance is less than this value, it is not considered a sliding distance (depending on how you use this value). This is a system constant whose value depends on the device. Use viewConfiguration.get (context).getScaledTouchSlop() to get this value.

System-level distribution

Event distribution originates from the System layer InputManager, which runs in the Native layer and is responsible for communicating with the hardware and receiving its input events. For example, when a user touches a touch, the screen first captures the touch event and hands it to the InputManager, which then passes the event to the WindowInputEventReceiver, an inner class member of ViewRootImpl. The member adds the event to an input event queue for subsequent processing.

When an event is queued, there will be an InputStage responsibility chain in ViewRootImpl to process the events in the queue one by one. This responsibility chain is composed of subclass objects of InputStage. Each node in the chain represents the processing stage of a class of events. For example, ViewPostImeInputStage represents the view input processing stage, which mainly processes events such as buttons, trackballs, and screen touches. The UI-level touch event distribution we will talk about later is in this stage.

Distribution of the Activity level

In the ViewPostImeInputStage, the touch event MotionEvent is distributed via the processPointerEvent method to the DecorView, which is the mView member in the ViewRootImpl.

When the event arrives in the DecorView, the DecorView passes the event to the Activity’s dispatchTouchEvent method via the mWindow member, The Activity first passes the event back to the DecorView via the Window’s superDispatchTouchEvent method for view-level event distribution. If the event is not handled at the View level, SuperDispatchTouchEvent returns false, at which point the Activity’s onTouchEvent will be called to handle the event.

View level event distribution

When the Window passes the event back to the DecorView, the DecorView handles the event as the root node of the View tree (a ViewGroup), thus entering the event distribution at the View level. There are three key approaches to event distribution at the View level:

Three key methods

  • DispatchTouchEvent (MotionEvent): Boolean This method is used for event distribution and behaves differently depending on the role the View plays:

    • As a View, it is responsible for distributing events to its own processing, such as handling the drag of the scroll bar, calling its ownonTouchEventMethod to handle events, etc.
    • As a ViewGroup, is responsible for determining whether an event should be assigned to someone (itself or a child View) and calling the target ViewdispatchTouchEventMethod to further distribute events.

    This method returns a Boolean value indicating whether the event has been processed.

  • onInterceptedTouchEvent(MotionEvent): Boolean This method is used to determine whether to intercept the current event. Called in the dispatchTouchEvent method of the ViewGroup, it returns true to intercept the event, which sends the event and subsequent events in the sequence to the current View.

  • OnTouchEvent (MotionEvent): Boolean Method used to handle events, called by the View’s dispatchTouchEvent method, returning a value indicating whether the event was handled by the current View.

Core distribution process

In the whole View hierarchy distribution process, there are two types of roles involved in event distribution, one is View and the other is ViewGroup. The former is where the event is ultimately processed, and the latter is used to distribute the event to the corresponding View processing. Note that the ViewGroup is also a View, so it can act as the View itself for the final processing of events.

As we know, the View in Android is presented to the user in a tree structure, which is composed of a number of ViewGroup and View type nodes, and the essence of event distribution is to find the appropriate View in this tree to process events, so, We can simply think of event distribution at the View level as a multi-fork tree search problem, with “billions” of details added to it.

Since the root node and the middle node of the View tree must be a ViewGroup, let’s first look at how a ViewGroup handles event distribution. Here is the pseudo-code for ViewGroup event distribution:

/ / pseudo code
public boolean dispatchTouchEvent(MotionEvent event) {
    boolean consumed = false;
    // 1. Traverse the child view and find the appropriate child view to call its dispatchTouchEvent
    for (int i = mChildrenCount - 1; i >= 0; i--) {
        View child = getChildAt(i);
        // If the subview is not in the scope of this event, skip the view
        if(! childCanDealThisEvent(child))continue;
        // Child always calls the correct dispatchTouchEvent method because of polymorphism
        if (child.dispatchTouchEvent(event)) {
            // 2. If the child View successfully consumes the event, the loop is broken
            consumed = true;
            break; }}// 3. If no child View consumes the event, try to handle the event yourself
    if(! consumed) {// The ViewGroup acts as the View's dispatchTouchEvent
        consumed = super.dispatchTouchEvent(event);
    }
    // 4. Return the processing result to the caller
    return consumed;
}
Copy the code

As you can see, the ViewGroup distributes events by traversing and calling the dispatchTouchEvent method of the child View. Using the polymorphic mechanism, this process is repeated recursively when the child View is a ViewGroup, so that the event can be passed layer by layer in the View tree. Until you find the View that finally needs to process the event. If you’re familiar with data structures, this is essentially a DFS (depth-first search) algorithm for trees. Let’s see how the View works:

/ / pseudo code
public boolean dispatchTouchEvent(MotionEvent event) {
    boolean result = false;
    // 1. If TouchListener is set, call the onTouch method of the listener first
    if(mOnTouchListener ! =null && mOnTouchListener.onTouch(event)) {
        // If the listener's onTouch returns true, the event has been consumed
        result = true;
    }
    // 2. If the event has not been consumed, the onTouchEvent method is called
    if(! result && onTouchEvent(event)) { result =true;
    }
    // 3. Return the processing result to the caller
    return result;
}
Copy the code

The above is the core process of View layer event distribution, the overall call process is shown in the following figure, of course, compared with the real implementation of the middle also omitted many details, such as the optimization of event processing details, and event interception mechanism.

Optimize the distribution process

As mentioned earlier, a finger swipe across the screen produces a series of events, that is, many events in a short period of time, and it would be a waste of resources to do a DFS on the View tree for each event distribution.

In fact, most of the time, press – move – lift such a group of operations are usually handled by the same View, so the designer called the events generated by such a group of operations as event sequence, for the events in the same event sequence, are handed to the same View to deal with, so as to reduce unnecessary traversal. So how does a View tree remember a View when it distributes events?

When the ViewGroup receives an ACTION_DOWN event, the entire event distribution process is performed. When a child View is found to have successfully consumed the event, the child View is saved to an mFirstTouchTarget member. For the ViewGroup at the next level in the View tree, the child View of the consuming event is also saved in this way, leaving a path to the target View in the View tree.

When the ViewGroup receives other events in the sequence of events, it simply fetches the next View it needs to access from the mFirstTouchTarget and calls its dispatchTouchEvent to pass the event to it. Here is the pseudo-code implementation:

/ / pseudo code
public boolean dispatchTouchEvent(MotionEvent event) {
    TouchTarget newTouchTarget = null;
    if (isDownEvent(event)) {
        // If the event is down, traverse the sub-view recursively looking for a sub-view that can handle the event, otherwise skip
        for (int i = mChildrenCount - 1; i >= 0; i--) {
            View child = getChildAt(i);
            if(! childCanDealThisEvent(child))continue;
            if (child.dispatchTouchEvent(event)) {
                // If a child View consumes this event, assign the TouchTarget and break out of the loop
                newTouchTarget.child = child;
                break;
            }
        }
        mFirstTouchTarget = newTouchTarget;
    }

    if (mFirstTouchTarget == null) {
        // The ViewGroup acts as the View's dispatchTouchEvent
        consumed = super.dispatchTouchEvent(event);
    } else {
        if(mFirstTouchTarget ! = newTouchTarget) {// When an event other than the Down event arrives, the TouchTarget is called directly
            // dispatchTouchEvent for the target view completes the event dispatch.
            consumed = mFirstTouchTarget.child.dispatchTouchEvent(event);
        } else {
            // When the Down event arrives, it will execute dispatchTouchEvent through the child View to find the target View to handle the event.
            // Handle the target View of the event, thus assigning mFirstTouchTarget with dispatchTouchEvent
            // It has already been executed, so it does not need to be executed again.
            consumed = true; }}return consumed;
}
Copy the code

Event interception mechanism

Reviewing the event distribution process we have implemented previously, we see that when an event is distributed in a ViewGroup, we always try to pass the event to a child View for processing, and only try to process it ourselves when we are sure that no child View consumes the event. Why is it designed this way?

A closer look at the application interface reveals that the views that appear at the front of the screen and are most responsive to our touch are usually the lowest nodes in the View tree. So, in order to get the event to the first View as quickly as possible, the ViewGroup will give the event priority to the child View.

The same principle applies to traversal of child views in a ViewGroup from the view with the largest index to the view with the smallest index. Because subviews like FrameLayout can overlap viewgroups, usually the one with the largest index is also the View that is displayed first.

Giving the event priority to the sub-view to handle the design is fine in most cases. However, in some special situations, this design is not enough.

Imagine a ScrollView that has a lot of child views inside it. When we want to scroll the ScrollView to display other child views, according to the above design, the child View has to decide the event before the ScrollView processing, and if any child View consumes the event, then the ScrollView will not have a chance to process the event. In other words, the child View decides whether the ViewGroup can handle events or not. This good? That’s not good! The child View has no obligation to know if the parent View wants to handle events, and forcing it to do so only increases the coupling between them.

Therefore, the designer designed an event blocking mechanism for the ViewGroup to determine whether it should handle the event before distributing it to its child views. In this mechanism, the ViewGroup can override the onInterceptTouchEvent method to decide if it wants to intercept and process the event.

Here is the event distribution pseudo-code with the interception mechanism added:

/ / pseudo code
public boolean dispatchTouchEvent(MotionEvent event) {
    // Determine whether to intercept this event based on the onInterceptTouchEvent return value.
    boolean intercept = onInterceptTouchEvent(event);

    TouchTarget newTouchTarget = null;
    if(! intercept) {// Iterate over the sub-view distribution event if it is not intercepted
        if(isDownEvent(event)) { ... mFirstTouchTarget = newTouchTarget; }}if (intercept || mFirstTouchTarget == null) {
        // Try to handle the event yourself if you decide to intercept it or if no child View consumes it.
        consumed = super.dispatchTouchEvent(event);
    } else if(mFirstTouchTarget ! = newTouchTarget) { consumed = mFirstTouchTarget.child.dispatchTouchEvent(event); }return consumed;
}
Copy the code

There are a few problems with the code above: it doesn’t seem very performance friendly to determine whether an event needs to be intercepted every time it is received. Referring to the previous event sequence optimization, we can limit the call timing of intercepting methods:

/ / pseudo code
public boolean dispatchTouchEvent(MotionEvent event) {
    boolean intercept = false;
    if(isDownEvent(event) || mFirstTouchTarget ! =null) {
        // Set mFirstTouchTarget to NULL if the event is DOWN.
        // When another event in this sequence comes, mFirstTouchTarget is null and no interception judgment is triggered.
        // If mFirstTouchTarget is not null, the event already has an identified child View consumption.
        // Interception judgment is also required.
        if (onInterceptTouchEvent(event)) {
            intercept = true;
            mFirstTouchTarget = null;
        } else {
            intercept = false; }}else {
        intercept = true; }... }Copy the code

Why do we need to make interception judgments when we already have certain child View consumption events? In most cases, a ViewGroup cannot determine whether interception is necessary only with a DOWN event. For example, if a ScrollView wants to slide up and DOWN, it must encounter at least a MOVE event to determine whether interception is necessary. Therefore, you may need to call onInterceptTouchEvent several times before deciding whether to intercept.

In this case, it is possible for the child View to be “truncated” by the parent View while processing events. In order to “compensate” for the damage caused by the event being robbed by the child View, the ViewGroup needs to intercept the event and render a CANCEL event to the View that has been set to mFirstTouchTarget. When the child View receives the “treat”, You can begin to “self-medicate” (cancel the click event, stop the timer that determines the long press event, etc.) and safely abandon the blocked event.

Here is event interception with cancellation added:

/ / pseudo code
public boolean dispatchTouchEvent(MotionEvent event) {
        boolean intercept = false;
        if(isDownEvent(event) || mFirstTouchTarget ! =null) {
            intercept = onInterceptTouchEvent(event);
        } else {
            intercept = true;
        }
        // Iterate over the sub-View distribution event.if (mFirstTouchTarget == null) {
            consumed = super.dispatchTouchEvent(event);
        } else {
            if (intercept) {
                // If an event sequence intercepts an event, set the current event to cancel,
                // To the mFirstTouchTarget to tell the child View that the sequence of events has been intercepted.
                event.setAction(MotionEvent.ACTION_CANCEL);
                mFirstTouchTarget.child.dispatchTouchEvent(event);
                mFirstTouchTarget = null;
            } else if(mFirstTouchTarget ! = newTouchTarget) { consumed = mFirstTouchTarget.child.dispatchTouchEvent(event); }else {
                consumed = true; }}return consumed;
    }
Copy the code

The intercept

As mentioned in the previous intercepting mechanism, we need to design an onInterceptTouchEvent method to help the parent View seize event handling rights. But in this mechanism, the parent View relies heavily on the parent View for the parent View to work together: the parent View needs to know about the child View and has to give up blocking when it needs something so that the event can be passed to the child View.

For example, if a scrolling ScrollView has a SeekBar inside it, when we need to drag the SeekBar, the ScrollView must determine that it is dragging the SeekBar and not scrolling itself.

In most cases, we can override the parent View’s onInterceptTouchEvent method to handle such conflicts, but sometimes the parent View is out of our control. Is there nothing we can do about it? Is not in the ViewGroup as well as a method, called requestDisallowInterceptTouchEvent through this method View can ask for the parent View not intercept events, so we have to intercept part of events to do some adjustments, To the compatible requestDisallowInterceptTouchEvent:

/ / pseudo code
private boolean isDisallowIntercept = false;

public boolean dispatchTouchEvent(MotionEvent event) {
        boolean intercept = false;
        if(isDownEvent(event) || mFirstTouchTarget ! =null) {
            if(isDisallowIntercept) {
                / / child View by calling the parent View requestDisallowInterceptTouchEvent
                // Set isDisallowIntercept to disable event interception by the parent View.
                intercept = false;
            } else{ intercept = onInterceptTouchEvent(event); }}else {
            intercept = true; }... }public void requestDisallowInterceptTouchEvent(boolean disallow) {
    isDisallowIntercept = disallow;
}
Copy the code

Of course, the permissions provided by this method are very small, because it must also be used in conjunction with the onInterceptTouchEvent from the parent View: In the parent’s onInterceptTouchEvent, you can’t intercept a DOWN event, because once the parent intercepts a DOWN event, it won’t enter isDisallowIntercept for subsequent events. Why is it designed this way? In fact, the power struggle between the parent View and the child View on the event processing is a process of checks and balances. Neither side can have too much power, otherwise it will be unbalanced. From the very beginning the rights of the child View is too large, to use onInterceptTouchEvent father View regain control, and then to requestDisallowInterceptTouchEvent limit the rights of the parent View, step by step, thus balancing the parent View and the View, This allows them to work well together on events and adds flexibility to event processing.

conclusion

This paper roughly combed the distribution of the overall process of the events, and event distribution in the View layer to try the progressive evolution in the form of flow charts, pseudo code the whole distribution process, but in the end than real events distribution, there is no doubt that the lack of a lot of details, and even some link code and the actual, but should be consistent. Finally, the best way to eat this article is with the source code. The ideas contained in the pseudocode can be found in the source code.

reference

  • Android development art exploration
  • Reflection | Android event dispatch mechanism – but the design and implementation of the qing mei
  • Interception mechanism reflection | Android events – but the design and implementation of the qing mei
  • – Carson_Ho