I. Overview of event distribution

1.1. What is event distribution

MotionEvent is the process by which the click event is passed from the Activity to the ViewGroup and then distributed to the View

2.1 MotionEvent

The common event types of MotionEvent are as follows

The event Introduction to the
ACTION_DOWN fingerYour first exposure to a screenTriggered when
ACTION_MOVE fingerSwipe across the screenTrigger, will trigger multiple times
ACTION_UP fingerLeave the screenTriggered when
ACTION_CANCEL The eventIntercepted by upper levelsTriggered when
ACTION_OUTSIDE fingerNot in control areaTriggered when
ACTION_POINTER_DOWN There are non-major finger presses (That is, there is already a finger on the screen before pressing)
ACTION_POINTER_UP There are non-primary finger lifts (That is, there are still fingers on the screen after lifting)

Which ACTION_POINTER_DOWN and ACTION_POINTER_UP through MotionEvent. GetAction (), need through the MotionEvent. GetActionMasked () to obtain the event type, This parameter is required in multi-touch scenarios

Pointer: Introduced the concept of pointer in multi-touch and MotionEvent. A pointer represents a touch point. Each pointer has its own event type and its own horizontal coordinate value. A MotionEvent object may store information about multiple Pointers, each with its own PointerId and PointerIndex. Pointer’s ID does not change throughout the event stream, but index does

PointerId: Each finger has a fixed value of PointerID. PointerId from the time it is pressed, moved, and left the screen, which is used to distinguish the finger

PointerIndex: The Index of each finger at each event from press, move to leave the screen may not be fixed due to the influence of other fingers

Like MeasureSpec, The Google engineers use a 32-bit int (0x00000000) for PointerIndex and event type, with the lowest 8 bits (0x000000FF) for event type and the preceding 8 bits (0x0000FF00) for event number

1.2. Flow Chart

Second, source code analysis

2.1. Activity event distribution

Start the event sequence from the Activity’s dispatchTouchEvent() method

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        // Triggered when the down event occurs, the default implementation is null
        onUserInteraction();
    }
    / / actual call is ViewGroup dispathcTouchEvent ()
    if (getWindow().superDispatchTouchEvent(ev)) {
        / / if ViewGroup. DispatchTouchEvent () returns true, said the event was consumption, do not perform the Activity. OnTuchEvent ()
        return true;
    }
    / / if ViewGroup. DispatchTouchEvent () returns true, said no consumer events, perform the Activity. The onTouchEvent ()
    return onTouchEvent(ev);
}
Copy the code

OnUserInteraction () : This method is null by default. It is not often used but is useful in certain scenarios. The onUserInteraction method is triggered when the home, back, menu keys, etc., are clicked. Pulling down statubar, rotating the screen, or locking the screen does not trigger this method, so it can be used in screensaver applications

Continue to look at why getWindow (). SuperDispatchTouchEvent (ev) is the actual call ViewGroup. DispatchTouchEvent (ev)? GetWindow () gets the mWindow member of the Activity’s Window type, which is an abstract class. It is the only implementation class PhoneWindow, so getWindow () superDispatchTouchEvent (ev) of the actual call is PhoneWindow superDispatchTouchEvent (ev)

public class PhoneWindow extends Window implements MenuBuilder.Callback {
    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        returnmDecor.superDispatchTouchEvent(event); }... }Copy the code

The superDispatchTouchEvent(MotionEvent Event) method of the DecorView is called. The DecorView inherits from FrameLayout, and FrameLayout inherits from ViewGroup. So when performing mDecor. SuperDispatchTouchEvent (event)

public class DecorView extends FrameLayout implements RootViewSurfaceTaker.WindowCallbacks {
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event); }... }Copy the code
public abstract class ViewGroup extends View implements ViewParent, ViewManager { @Override public boolean dispatchTouchEvent(MotionEvent event) { ... }... }Copy the code

As shown above, the Activity->mWindow->mDecorView->ViewGrop completes the event distribution from the Activity to the ViewGroup

2.2 ViewGroup event distribution

Analysis of ViewGroup. DispatchTouchEvent main code

@Override public boolean dispatchTouchEvent(MotionEvent ev) { ... boolean handled = false; / / if the space is not other controls keep out, and the default controls is not hidden, here true if (onFilterTouchEventForSecurity (ev)) {final int action = ev. The getAction (); final int actionMasked = action & MotionEvent.ACTION_MASK; If (actionMasked == motionEvent.action_down) {// These two methods set mFirstTouchTarget to null when it is down cancelAndClearTouchTargets(ev); resetTouchState(); } // Determine whether to intercept final Boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOW || mFirstTouchTarget ! = null) {/ / when the View invokes the requestDisallowInterceptTouchEvent (true) disallowIntercept = true / / default is false, in the use of internal intercept method and treatment of sliding conflict when using this method final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! = 0; if (! OnInterceptTouchEvent returns true intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { intercepted = false; } } else { intercepted = true; } if (intercepted || mFirstTouchTarget ! = null) { ev.setTargetAccessibilityFocus(false); }... 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 float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); final ArrayList<View> preorderedList = buildTouchDispatchChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; For (int I = childrenCount - 1; i >= 0; i--) { final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex); . // Continue to the next if (! child.canReceivePointerEvents() || ! isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; } newTouchTarget = getTouchTarget(child); if (newTouchTarget ! = null) { newTouchTarget.pointerIdBits |= idBitsToAssign; break; } resetCancelNextUpFlag(child); / / call the child View dispatchTouchEvent method if (dispatchTransformedTouchEvent (ev, false, child, idBitsToAssign)) { // Child wants to receive touch within its bounds. mLastTouchDownTime = ev.getDownTime(); if (preorderedList ! = null) { // childIndex points into presorted list, find original index for (int j = 0; j < childrenCount; j++) { if (children[childIndex] == mChildren[j]) { mLastTouchDownIndex = j; break; } } } else { mLastTouchDownIndex = childIndex; } mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); NewTouchTarget = addTouchTarget(Child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; // The child View's dispatchTouchEvent method returns true. If the child View breaks, the event is passed to the child View. } ev.setTargetAccessibilityFocus(false); } if (preorderedList ! = null) preorderedList.clear(); } // If (newTouchTarget == null && mFirstTouchTarget! = null) { newTouchTarget = mFirstTouchTarget; while (newTouchTarget.next ! = null) { newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |= idBitsToAssign; }} if (mFirstTouchTarget == null) {// No child View consumption event, Here will call current ViewGroup onTouchEvent method handled = dispatchTransformedTouchEvent (ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { ... }... return handled; }Copy the code

The overall process is that if the child view without invoking requestDisallowInterceptTouchEvent (true) and onInterceptTouchEvent () is not intercept, the traverse the view, The touch coordinates determine whether the child view’s dispatchTouchEvent() intercepts it. If it intercepts it passes it to the child View, if it does not intercept it passes it to the current ViewGroup’s onTouchEvent() method

DispatchTransformedTouchEvent (ev, false, child, idBitsToAssign) : through this method completed from the ViewGroup to View event distribution

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
    final boolean handled;

    // Canceling motions is a special case. We don't need to perform any transformations
    // or filtering. The important part is the action, not the contents.
    final int oldAction = event.getAction();
    if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
        event.setAction(MotionEvent.ACTION_CANCEL);
        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else {
            // dispatchTouchEvent to child View (event)
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        returnhandled; }...return handled;
}
Copy the code

2.3. View event distribution

public boolean dispatchTouchEvent(MotionEvent event) { ... If (onFilterTouchEventForSecurity (event)) {/ / controls enable the if ((mViewFlags & ENABLED_MASK) = = ENABLED && handleScrollBarDragging(event)) { result = true; } // This ListenerInfo contains Settings for some click-long touch listener ListenerInfo li = mListenerInfo; // If setOnTouchListener is set and its anonymous inner class onTouch(v, Event) returns true, the event will be consumed if (li! = null && li.mOnTouchListener ! = null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } // If setOnTouchListener is not set or onTouch(v,event) returns false, the onTouchEvent method is executed if (! result && onTouchEvent(event)) { result = true; } } return result; }Copy the code

If setOnTouchListener is set and its anonymous inner class onTouch(v, Event) returns true, it will consume the event and subsequent onTouchEvent methods will not be executed

If setOnTouchListener is not set or onTouch(v, Event) returns false, the onTouchEvent method is executed

Let’s look at the relationship between the onTouchEvent method and the onClick method

public boolean onTouchEvent(MotionEvent event) {...if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                ...
                booleanprepressed = (mPrivateFlags & PFLAG_PREPRESSED) ! =0;
                if((mPrivateFlags & PFLAG_PRESSED) ! =0 || prepressed) {
                    ...
                    if(! mHasPerformedLongPress && ! mIgnoreNextUpEvent) {// This is a tap, so remove the longpress check
                        removeLongPressCallback();

                        // 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(); } } } } mIgnoreNextUpEvent =false;
                break;
        }
        return true;
    }
    return false;
}
Copy the code

From here you can see that the onClick event is executed by calling performClickInternal() in ACTION_UP in onTouchEvent

So the execution order of these three events as setOnTouchEventListener. The onTouch > onTouchEvent > setOnclickListener. The onClick