primers

What modern people see and touch most every day is the mobile phone screen. The distribution mechanism of touch events in Android development is also a very interesting part. In this article, we will study Android touch event mechanism without in-depth but superficial.

First, touch events

For events triggered by Touch, in Android, events mainly include onClick, onLongClick, onDrag, and onScroll, etc. Click also includes click and double click, and also includes single-finger operation and multi-finger operation. The first state of Touch is ACTION_DOWN, which means that the screen is pressed. After that, touch will have subsequent events, such as move, lift, etc. One Action_DOWN, n ACTION_MOVE, and one ACTION_UP constitute many events in Android. The sequence of touch events usually looks like this:

  • Down (ACTION_DOWN) // indicates that the user has pressed down the screen
  • ACTION_MOVE // Indicates that the user is moving on the screen
  • Lift (ACTION_UP) // indicates that the user leaves the screen
  • The cancel gesture (ACTION_CANCEL) // indicates that it will not be made by the user, but by the program

These events are defined in the code like this:

public final class MotionEvent extends InputEvent implements Parcelable {
    // code omitted
    
    public static final int ACTION_DOWN             = 0;    // Press event
    
    public static final int ACTION_UP               = 1;    // Lift the event
    
    public static final int ACTION_MOVE             = 2;    // Gestures move events
    
    public static final int ACTION_CANCEL           = 3;    / / cancel
  // code omitted
}
Copy the code

Of course, touch produces far more events than these, but the most common contact is these.

All action events must first be performed by ACTION_DOWN, and all subsequent actions are based on this premise. When the action is completed, it may be followed by ACTION_MOVE and lift (ACTION_UP), or it may be directly lifted without moving after the action is completed.

Touch related components and operations

2.1 components

All event operations occur on the touch screen, and on the screen to interact with the user is a variety of View components (View), in Android, all views are inherited from the View, in addition to a variety of layout components (ViewGroup) to the View layout, ViewGroup also inherited from the View. All UI controls such as Button and TextView inherit from View, and all layout controls such as RelativeLayout and container controls such as ListView inherit from ViewGroup. So, event operations are mostly between views and viewgroups.

2.2 operating

There are three methods for handling touch events:

  1. public boolean dispatchTouchEvent(MotionEvent event)
  2. public boolean onTouchEvent(MotionEvent event)
  3. public boolean onInterceptTouchEvent(MotionEvent event)

There are dispatchTouchEvent and onTouchEvent methods in both views and ViewGroups. Specifically, there is an onInterceptTouchEvent method in ViewGroup. All of these methods return Boolean values. They all return true or false. This is because the event is passed one after another.

The three methods can be summarized in a table:

Touch event-related methods Methods the function ViewGroup View Activity
public boolean dispatchTouchEvent(MotionEvent ev) Dispatching events Yes Yes Yes
public boolean onInterceptTouchEvent(MotionEvent ev) Events to intercept Yes No No
public boolean onTouchEvent(MotionEvent ev) Incident response Yes Yes Yes

From this table we can see that the ViewGroup and View respond to all three methods related to Touch events, while the Activity does not respond to onInterceptTouchEvent(MotionEvent EV).

Also note that a View responds to dispatchTouchEvent(MotionEvent EV) and onInterceptTouchEvent(MotionEvent EV) only when a child View is added to the View. If the current View is already a smallest unit View (such as a TextView), then there is no way to add a child View to the smallest View, so there is no way to distribute and intercept events to the child View. So it doesn’t have dispatchTouchEvent(MotionEvent EV) or onInterceptTouchEvent(MotionEvent EV), only onTouchEvent(MotionEvent EV).

Event distribution, interception and consumption

Let’s first understand the whole process of the touch event mechanism from the overall context, which can be roughly understood by an inverted U-shaped diagram:

Touch events are passed from the Activity triggering event to the layout file, layer by layer to the child container to the view at the bottom level. If the event is not processed or consumed by each layer of the layout file, the event is passed from the bottom level up to the Activity for consumption. It’s like an upside-down U.

To understand the U-shaped process, consider this example:

Unwrapping the details a bit, you get a flow chart like this:

3.1 Consumption of events

The consumption of events is mainly accomplished by the dispatchTouchEvent(MotionEvent EV) function and onTouchEvent(MotionEvent Event event) function in the View class. The specific consumption functions are user-defined OnTouchListener() and onClickListener().

The dispatchTouchEvent function is located in the View.java class. Can be overridden by a ViewGroup that inherits a View.

public boolean dispatchTouchEvent(MotionEvent event) {
        // If the event should be handled by accessibility focus first.
        if (event.isTargetAccessibilityFocus()) {
            // We don't have focus or no virtual descendant has it, do not handle the event.
            if(! isAccessibilityFocusedViewOrHost()) {return false;
            }
            // We have focus and got the event, then use normal event dispatch.
            event.setTargetAccessibilityFocus(false);
        }
        boolean result = false;

        if(mInputEventConsistencyVerifier ! =null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        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)) {
                result = true;
            }

            if(! result && onTouchEvent(event)) { result =true; }}if(! result && mInputEventConsistencyVerifier ! =null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }

        // Clean up after nested scrolls if this is the end of a gesture;
        // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
        // of the gesture.
        if(actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL || (actionMasked == MotionEvent.ACTION_DOWN && ! result)) { stopNestedScroll(); }return result;
    }
Copy the code

The most important part of this source code is

             //noinspection SimplifiableIfStatement
            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;
            }
Copy the code

The first if statement above the first three conditions are generally true, is the key to li. MOnTouchListener. The onTouch (this event), this is we can copy our OnTouchListener (), the content of the return value is also defined by us. If this returns true, resut is assigned true and the subsequent onTouchEvent(event) function is not executed. If the return value is false, the onTouchEvent(event) function is executed. OnTouchEvent (Event) basically executes the ClickListener method we added for the component.

3.2 Event Distribution

Interception of events is done by dispatchTouchEvent() in the ViewGroup. Pseudocode can be described like this:

public boolean dispatchTouchEvent(MotionEvent event) {
    if(onInterceptTouchEvent(ev)){
        consume = onTouchEvent(ev)
    }else{
        consume = child.dispatchTouchEvent(ev)
    }
    return consume;
}
Copy the code

The source code has this paragraph:

while(target ! =null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
Copy the code

This is the part of the code that passes the touch event to the child View, where the handled event is the return value of the whole dispatchTouchEvent, determined by the handler defined in the View.

3.3 Event Interception

In a ViewGroup, if an interceptor is defined, the touch event will not be distributed, but will detect if it is consumed and then return to the parent ViewGroup based on the returned value.

Event interception is implemented by onInterceptTouchEvent(MotionEvent 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

The source code is much simpler and is definitely called by the previous dispatch function dispatchTouchEvent.

Four,

With regard to the distribution, interception, and consumption of touch events, we don’t have to worry about the detailed implementation in the source code, but rather understand the distribution and consumption logic of views and viewgroups to ensure that only one View will eventually consume the event and successfully return a Boolean value.

Refer to the article

Android Touch Event delivery mechanism – Cnblogs.com

Android Touch Event Distribution process _Mr.Simple’s column -CSDN blog

Touch Event Passing Learning Notes – Jianshu.com