note_44: Event distribution

Event Distribution

Reference resources:

Code posted in this article has been deleted!

1. Overview of the process

From the moment you touch a View, the event distribution process follows:

Explain:

  1. OnTouchEvent is a method that is called when handling touch events directly and is similar to OnTouchListener.onTouch is called before View receives a touch event, that is, it takes precedence over onTouchEvent.
  2. The onInterceptTouchEvent is whether to intercept the current event or not. It is worth noting that only ViewGroup has this method, neither Activity nor View. The return value of this method, TRUE, means that the current ViewGroup will handle the event itself and will no longer pass it to subsequent child Views. FALSE is returned by default.
  3. The dispatchTouchEvent is event distribution, and if TRUE is returned, the current event is consumed and will not be distributed further down. The onInterceptTouchEvent is executed within this method. Continue dispatch by first determining that the current event is not being CANCEL or INTERCEPT.

2. dispatchTouchEvent

stay Process overview As you can see in the diagram, dispatchTouchEvent is used in three places: Activity, ViewGroup, View. From the system source code, it can be seen that the implementation of this method in these three places is completely different and has different meanings.

1. Activity.dispatchTouchEvent

Activity.java

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

public Window getWindow() {
	return mWindow;
}

(1) onUserInteraction and onUserLeaveHint

onUserInteraction and onUserLeaveHint are callback methods used in pairs and need to be implemented by the user himself. A notification list that can be used to process or cancel the status bar.

onUserInteraction is called when the key, touch, trackball event is distributed to the Activity.

onUserLeaveHint is called before the Activity returns to the background and onPause is executed.

(2)PhoneWindow

PhoneWindow.java

public class PhoneWindow extends Window implements MenuBuilder.Callback {
    private DecorView mDecor;

    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }
    
    private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
        public boolean superDispatchTouchEvent(MotionEvent event) {
            return super.dispatchTouchEvent(event);
        }
    }  
}

Window is an abstract class, and its only implementation class is android.policy.PhoneWindow.

PhoneWindow is a top-level Window that holds the mDecor as its view and is the top-level view.

DecorView inherits from FrameLayout, which means it is a ViewGroup. The dispatchTouchEvent is not overridden in FrameLayout, so DecorView executes super. When dispatchTouchEvent is executing ViewGroup.dispatchTouchEvent.

PhoneWindow implements superDispatchTouchEvent from Activity.dispatchTouchEvent transitions to ViewGroup.dispatchTouchEvent. This means passing events from the activity to the ViewGroup. From a code point of view, this process is bound to occur, and there are no intercepted situations.

(3) Return value

Activity is visible. DispatchTouchEvent has a return value, but whether it returns TRUE or FALSE, it represents a consumer event here. When FALSE is returned, onTouchEvent must have been executed. When TRUE is returned, events must have been distributed, but onTouchEvent may have been executed.

2. ViewGroup.dispatchTouchEvent

ViewGroup.java

public boolean dispatchTouchEvent(MotionEvent ev) {
    // Check for interception.
    final boolean intercepted;
    if (actionMasked == MotionEvent.ACTION_DOWN
            || mFirstTouchTarget != null) {
        final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        if (!disallowIntercept) {
            intercepted = onInterceptTouchEvent(ev);
            ev.setAction(action); // restore action in case it was changed
        } else {
            intercepted = false;
        }
    } else {
        // There are no touch targets and this action is not an initial down
        // so this view group continues to intercept touches.
        intercepted = true;
    }

    // Check for cancelation.
    final boolean canceled = resetCancelNextUpFlag(this)
            || actionMasked == MotionEvent.ACTION_CANCEL;
    
    if (!canceled && !intercepted) {
        final View[] children = mChildren;
        for (int i = childrenCount - 1; i >= 0; i--) {
            final int childIndex = customOrder
                ? getChildDrawingOrder(childrenCount, i) : i;
            final View child = (preorderedList == null)
                ? children[childIndex] : preorderedList.get(childIndex);
            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {}
        }
    }
}

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, 
                                              View child, int desiredPointerIdBits) {
    // Perform any necessary transformations and dispatch.
    if (child == null) {
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
        handled = child.dispatchTouchEvent(transformedEvent);
    }
}

The biggest difference between ViewGroup and Activity when distributing an event is to determine whether the event is CANCELED or INTERCEPTED before deciding whether to continue dispatch.

If you want to continue distribution down, you must find the sub-view you touch by traversing the entire sub-view array most commonly and directly. If the boundary of the child view found matches the coordinate conditions of the touch event and the event can be received, it can be distributed.

If the qualified child view is empty as the distribution proceeds, the event calls super.dispatchTouchEevnt processing. Otherwise, distribute to child views.

onInterceptTouchEvent

ViewGroup.java

// First touch target in the linked list of touch targets.
private TouchTarget mFirstTouchTarget;

/**
 * Implement this method to intercept all touch screen motion events.  This
 * allows you to watch events as they are dispatched to your children, and
 * take ownership of the current gesture at any point.
 *
 * <p>Using this function takes some care, as it has a fairly complicated
 * interaction with {@link View#onTouchEvent(MotionEvent)
 * View.onTouchEvent(MotionEvent)}, and using it requires implementing
 * that method as well as this one in the correct way.  Events will be
 * received in the following order:
 *
 * <ol>
 * <li> You will receive the down event here.
 * <li> The down event will be handled either by a child of this view
 * group, or given to your own onTouchEvent() method to handle; this means
 * you should implement onTouchEvent() to return true, so you will
 * continue to see the rest of the gesture (instead of looking for
 * a parent view to handle it).  Also, by returning true from
 * onTouchEvent(), you will not receive any following
 * events in onInterceptTouchEvent() and all touch processing must
 * happen in onTouchEvent() like normal.
 * <li> For as long as you return false from this function, each following
 * event (up to and including the final up) will be delivered first here
 * and then to the target's onTouchEvent().
 * <li> If you return true from here, you will not receive any
 * following events: the target view will receive the same event but
 * with the action {@link MotionEvent#ACTION_CANCEL}, and all further
 * events will be delivered to your onTouchEvent() method and no longer
 * appear here.
 * </ol>
 *
 * @param ev The motion event being dispatched down the hierarchy.
 * @return Return true to steal motion events from the children and have
 * them dispatched to this ViewGroup through onTouchEvent().
 * The current target will receive an ACTION_CANCEL event, and no further
 * messages will be delivered here.
 */
public boolean onInterceptTouchEvent(MotionEvent ev) {
    return false;
}

public boolean dispatchTouchEvent(MotionEvent ev) {
    // Handle an initial down.
   if (actionMasked == MotionEvent.ACTION_DOWN) {
        resetTouchState();
   }
    
    // Dispatch to touch targets.
    if (mFirstTouchTarget == null) {
        // No touch targets so treat this as an ordinary view.
        handled = dispatchTransformedTouchEvent(ev, canceled, null, 
                                                TouchTarget.ALL_POINTER_IDS);
    } else {
        // Dispatch to touch targets, excluding the new touch target if we already
        // dispatched to it.  Cancel touch targets if necessary.
        TouchTarget predecessor = null;
        TouchTarget target = mFirstTouchTarget;
        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;
        }
    }
}

/**
 * Resets all touch state in preparation for a new cycle.
 */
private void resetTouchState() {
    clearTouchTargets();
}

/**
  * Clears all touch targets.
  */
private void clearTouchTargets() {
    TouchTarget target = mFirstTouchTarget;
    if (target != null) {
        mFirstTouchTarget = null;
    }
}

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 {
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        return handled;
    }
}

Only ViewGroup has onInterceptTouchEvent, and the return value defaults to FALSE. The source code relies solely on comments, and the code has only one sentence of return false;.

From ViewGroup. As you can see in the dispatchTouchEvent, the cycle of event processing is from ACTION_DOWN begins with the next ACTION_ The previous event of DOWN ended. The return value of onInterceptTouchEvent indicates whether the event is to be intercepted. Take a cycle for example, if ViewGroup. The onInterceptTouchEvent returns TRUE, and the touch event is directly dispatched to the ViewGroup itself. ACTION_DOWN events are only assigned to ViewGroup, and no other child views receive any events. ViewGroup will continue to intercept all events until the end of this cycle, but only events are ACTION_ When DOWN, other subviews are not received. If the event is ACTION_ Events such as MOVE, other child views will receive event ACTION_CANCEL.

ViewGroup calls super when onInterceptTouchEvent returns TRUE. DispatchTouchEvent, which calls View.dispatchTouchEvent.

3. View.dispatchTouchEvent

View.java

public boolean dispatchTouchEvent(MotionEvent 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;
     }
}

As you can see from the source code, OnTouchListener.onTouch will take precedence over onTouchEvent.

onTouchEvent is called when handling touch events, OnTouchListener.onTouch can intercept this touch event and follow the ViewGroup. The onInterceptTouchEvent is a bit similar, but it has many conditions and may not necessarily be executed.

(1) ViewGroup calls super.dispatchTouchEvent

If View at this time. OnTouchEvent is a super from ViewGroup.dispatchTouchEvent triggers when View. When onTouchEvent returns TRUE, it means the touch event has been consumed; If View. When onTouchEvent returns to FALSE, it means that touch events are not consumed, so ViewGroup. The dispatchTouchEvent will return FALSE and trigger the Activity.onTouchEvent.

(2) View calls dispatchTouchEvent

If View at this time. If dispatchTouchEvent is called by View, then it proves that the touch event has been distributed to the corresponding child view. If View.onTouchEvent returns TRUE, then the event is consumed; If View.onTouchEvent returns FALSE, then it returns to ViewGroup. In dispatchTouchEvent, either continue traversing to find a qualified child view dispatch event or call ViewGroup.onTouchEvent is processed. If the latter, if ViewGroup.onTouchEvent returns TRUE, then the event is consumed; If ViewGroup.onTouchEvent returns FALSE, then it returns to Activity.dispatchTouchEvent, and then call Activity.onTouchEvent.

3. Summary

Event distribution starts with Activity.dispatchTouchEvent starts, then goes to ViewGroup.dispatchTouchEvent. The ViewGroup should be judged first. Whether onInterceptTouchEvent intercepts events and, if so, leaves them to ViewGroup.onTouchEvent processing; If not, the event is handed over to View.dispatchTouchEvent processing. In View. In dispatchTouchEvent, if OnTouchListener is not considered. OnTouchEvent calls View.onTouchEvent.

In the process of calling various onTouchEvents, if the return value is TRUE, the event is proved to be consumed; If the return value is FALSE, all ViewGroup s and Views except Activity will be treated as events consumed, and then the events will be handled by the Activity or parent view's onTouchEvent.

In the process of invoking dispatchTouchEvent s, Activity is not considered, if the return value is TRUE, it means that the event is consumed by the current view or child view; If the return value is FALSE, it means that the event is not consumed and needs to be handled by the onTouchEvent of the Activity or parent view.

Keywords: Java Android Note

Added by homerjay on Wed, 12 Jan 2022 19:00:40 +0200