The source code presented in this article is based on 8.0

preface

Event distribution mechanism is not only a core knowledge point but also a difficult point, and it is also the theoretical basis of sliding conflict solution, so it is very important to master the event distribution mechanism of View.

1. Basic understanding

1. Object to which events are distributed

Events are distributed to Touch events, which are generated when the user touches the screen.

There are four types of events, as follows:

type instructions
MotionEvent.ACTION_DOWN The finger just touches the screen, generally is the beginning of the event
MotionEvent.ACTION_MOVE When a finger moves on the screen, multiple move events are generated
MotionEvent.ACTION_UP The moment your finger is released from the screen
MotionEvent.ACTION_CANCEL End the event, no human cause

Same event sequence: refers to a series of events generated in this process from the moment when the finger touches the screen to the moment when the finger leaves the screen. This sequence generally starts with a Down event, contains multiple move events in the middle, and finally ends with an up event

2. The nature of event distribution

The essence of event distribution is the whole process of passing a click event to a specific View

3. Order of event distribution

The order of event delivery: Activity->Window->DecorView->ViewGroup->View. When a click event occurs, it is always passed first to the current Activity, then through the Window to the DecorView, then to the ViewGroup, and finally to the View.

The order of event distribution is Activity->Window->View in Development Art Quest, and Activity->ViewGroup->View in some blogs, but it is the same (see source code below). Window is an abstract class whose only implementation is PhoneWindow, which passes events directly to a DecorView that descends from FrameLayout, which in turn subclasses ViewGroup. So you can also end up thinking that the implementation of Window event distribution is actually implemented by the ViewGroup. So you can also think of the order of event delivery as: Activity->ViewGroup->View.

Second, core methods

View’s event distribution mechanism is logically controlled by event distribution -> event interception -> event processing, which happens to correspond to the three core methods

Event distribution: dispatchTouchEvent

Used to distribute events, which must be called if the event can be passed to the current View. The return result is affected by the current View’s onTouchEvent and its child’s dispatchTouchEvent, indicating whether the current event is consumed.

Public Boolean dispatchTouchEvent(MotionEvent ev)

return:

  • Ture: The current View consumes all events
  • False: Stop distribution and hand over to the upper-layer control’s onTouchEvent method for consumption. If this layer control is an Activity, the event will be consumed and processed by the system

2. Event interceptor: onInterceptTouchEvent

Note that only viewGroups have this method in activities, ViewGroups, and Views. So once a click event is passed to the View, the View’s onTouchEvent method is called

Used within dispatchTouch Event to determine whether to intercept events. If the current View intercepts an event, the other methods of that sequence of events are handled by the current View, so the method is not called again because it is no longer asked whether to intercept the event.

Public Boolean onInterceptTouchEvent(MotionEvent ev)

return:

  • Ture: Intercepts the event and passes it to this layer’s onTouchEvent for processing
  • False: not intercepted, sent to the child View for processing by the child View’s dispatchTouchEvent
  • Super. OnInterceptTouchEvent (ev) : the default don’t intercept

3. Event handling: onTouchEvent

If not, the current View cannot receive any more events in the same sequence of events and the event will be reassigned to its parent, that is, the parent’s onTouchEvent will be called

Public Boolean onTouchEvent(MotionEvent ev)

return:

  • True: the current event is consumed after onTouchEvent processing
  • False: does not respond to the event, continuously passed to the upper level onTouchEvent method processing, until a View’s onTouchEvent returns true, the event is considered to be consumed, if the top-level View still returns false, the event is not consumed. This is handled by the Activity’s onTouchEvent.
  • Super.ontouchevent (EV): Consumes the current event by default, consistent with returning true.

3. Event distribution mechanism

When analyzing the event distribution mechanism, the sequence of events should be dissected step by step. DecorView->ViewGroup->View. Since Windows and DecorView can be thought of as an Activity->ViewGroup process, the event distribution mechanism is analyzed from the source code in three parts:

  • Activity’s distribution mechanism for click events
  • ViewGroup distribution mechanism for click events
  • View’s distribution mechanism for click events

1. Activity event distribution mechanism

We know that when a click event occurs, the event is always delivered first to the current Activity, which is delivered by the Activity’s dispatchTouchEvent. The Activity passes the event to the Window object for distribution, which is passed to the DecorView. Here is a source code analysis to verify this process:

Source: Activity# dispatchTouchEvent

Public Boolean dispatchTouchEvent(MotionEvent ev) {public Boolean dispatchTouchEvent(MotionEvent ev) {if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } / / if the Window of the Activity dispatchTouchEvent returned true. / / the Activity dispatchTouchEvent returns true, the click event to stop passing down the if (getWindow().superDispatchTouchEvent(ev)) { return true; } // If Window dispatchTouchEvent returns false, click the event to pass to activity. onTouchEvent return onTouchEvent(ev); }Copy the code

OnUserInteraction () is performed first when the click event occurs; What is this method? No hurry. We’ll follow it.

Public void onUserInteraction() {} Public void onUserInteraction() {}Copy the code

It can be seen from the source code that this method is empty in the Activity. When the Activity is at the top of the stack, the touch screen clicking on home,back,menu will trigger this method, so this method can realize the screensaver function. Let’s go back to the Activity’s dispatchTouchEvent method and call getWindow().superDispatchTouchEvent(EV) to hand the event to the Activity’s attached Window for distribution. Returns true if the final event is consumed, and if the event is unhandled, the Activity calls its own onTouchEvent() method to handle the event.

GetWindow is a Window object. In the source code of Window, we can find that in fact Window is an abstract class. It is obvious that its methods are naturally abstract methods, so we must find out its concrete implementation class.

Source: Window# superDispatchTouchEvent

/** * Abstract base class for a top-level window look and behavior policy. An * instance of this class should be used as  the top-level view added to the * window manager. It provides standard UI policies such as a background, title * area, default key processing, etc. * * <p>The only existing implementation of this abstract class is * android.view.PhoneWindow, which you should instantiate when needing a * Window. */ public abstract class Window { ... Public abstract Boolean superDispatchTouchEvent(MotionEvent event); . }Copy the code

From the source we can find in front of the Window class annotation is explained, this time will test our English ability Liao! It’s actually pretty easy to understand in general, as long as we focus on the comments in the latter part.

The only existing implementation of this abstract class isandroid.view.PhoneWindow, which you should instantiate when needing a Window.

From here we can see that the only implementation class is PhoneWindow. Without further discussion, let’s look at the implementation of the superDispatchTouchEvent method in PhoneWindow.

Source: PhoneWindow# superDispatchTouchEvent

   private DecorView mDecor;
   @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }
Copy the code

From the source code, you can see that PhoneWindow passes the event directly to a DecorView. Who is that DecorView? As shown below.

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

GetDecorView ().findViewById(Android.r.i.C.ontent).getChildAt(0) This method gets the layout set in the setContentView. DecorView inherits from FrameLayout, which in turn inherits from ViewGroup. In a DecorView, the superDispatchTouchEvent method calls the dispatchTouchEvent class using super. Instead of calling the ViewGroup’s dispatchTouchEvent (FrameLayout does not have a dispatchTouchEvent), the DecorView passes the event to the ViewGroup for processing. In other words, the event has been passed to the top-level View that is set in the Activity via setContentView (the top-level View is usually the ViewGroup).

The flow chart is as follows:

At this point, we also verify that the order of event distribution mentioned earlier is: Activity->Window->DecorView->ViewGroup. So how does a ViewGroup pass events to a View? Let’s continue the analysis!

2. Distribution mechanism of ViewGroup events

ViewGroup events are sent from dispatchTouchEvent(), so we will start with this part of the source code analysis, because this method has a lot of code, the following will be posted as needed:

Source: ViewGroup# dispatchTouchEvent

@Override public boolean dispatchTouchEvent(MotionEvent ev) { ........ // Check for interception. final boolean intercepted; // Whether to block /* * When an event is handled by a child of a ViewGroup, MFirstTouchTarget will be assigned and point to child elements * / 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; }}Copy the code

ViewGroup determines whether to intercept only on ACTION_DOWN or mFirstTouchTarget! = null. MFirstTouchTarget is only known from the following code. This is what it does: When the event is handled by a child View of the ViewGroup, the mFirstTouchTarget points to that child View. So when an event is intercepted by this ViewGroup, the subclass will not handle the event, so mFirstTouchTarget =null, so when ACTION_MOVE and ACTION_UP events come in, If the judgment condition is false, the onInterceptTouchEvent of the ViewGroup will not be called again, and intercepted is given true. Therefore, all other events in the same sequence will be handled by the ViewGroup by default. We can also find the following statement in the source code:

final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! = 0;Copy the code

FLAG_DISALLOW_INTERCEPT is a tag, the tag is through requestDisallowInterceptTouchEvent (Boolean disallowIntercept) methods to set, generally used in child View. If FLAG_DISALLOW_INTERCEPT is set, ViewGroup will not be able to block click events other than ACTION_DOWN. This is because ViewGroup is distributing ACTION_DOWN events. The FLAG_DISALLOW_INTERCEPT bit will be reset. Let’s look at the source code.

if (actionMasked == MotionEvent.ACTION_DOWN) { // Throw away all previous state when starting a new touch gesture. // The framework may have dropped the up or cancel event for the previous gesture // due to an app switch, ANR, or some other state change. // cancelAndClearTouchTargets(ev); resetTouchState(); Flag_disallow_interception WPT = true}Copy the code

If the event is ACTION_DOWN, the ViewGroup will always call its onInterceptTouchEvent to ask if it wants to intercept the event.

RequestDisallowInterceptTouchEvent method on ACTION_DOWN other events, and is in the case of not intercept ACTION_DOWN events to be effective.

Now let’s look at the event distribution when ViewGroup no longer intercepts the event, source code as follows:

final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; I -) {/ / traverse all child elements of ViewGroup final int childIndex = getAndVerifyPreorderedIndex (childrenCount, I, customOrder); final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex); // If there is a view that has accessibility focus we want it // to get the event first and if not handled we will perform a // normal dispatch. We may do a double iteration but this is // safer given the timeframe. if (childWithAccessibilityFocus ! = null) { if (childWithAccessibilityFocus ! = child) { continue; } childWithAccessibilityFocus = null; i = childrenCount - 1; } /** ** Determines whether a child element can receive a click event: ** If the child element is playing an animation and the click event coordinates are in the region of the child element ** If an element meets these two conditions, the event is handed to it to handle **/ if (! canViewReceivePointerEvents(child) || ! isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; } newTouchTarget = getTouchTarget(child); if (newTouchTarget ! = null) { // Child is already receiving touch within its bounds. // Give it the new pointer in addition to the ones it is handling. newTouchTarget.pointerIdBits |= idBitsToAssign; break; } resetCancelNextUpFlag(child); / / dispatchTransformedTouchEvent actual call is a child element 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); / / record ACTION_DOWN event has been handled alreadyDispatchedToNewTouchTarget = true; break; } // The accessibility focus didn't handle the event, so clear // the flag and do a normal dispatch to all children. ev.setTargetAccessibilityFocus(false); }Copy the code

As you can see from the above code, when you do not intercept an event, it first iterates through all the children of the ViewGroup and then determines whether the children can receive the click event. This is determined by whether the child element is playing the animation and whether the coordinates of the click event fall within the child element’s region. If find a target children View to deal with the event, call the dispatchTransformedTouchEvent () method. Let’s look at the important implementation logic of this method:

			  if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
				   ........
                    handled = child.dispatchTouchEvent(event);
                }
Copy the code

You can see that since the child in the above is not equal to null, the dispatchTouchEvent method of the child element is called directly, causing the event to be passed to the child View, and then distribution continues.

You think this is the end of it? If the child element dispatchTouchEvent returns true, the operator will perform the following operations:

newTouchTarget = addTouchTarget(child, idBitsToAssign); / / record ACTION_DOWN event has been handled alreadyDispatchedToNewTouchTarget = true; break;Copy the code

These lines complete the assignment of the mFirstTouchTarget and terminate the traversal of the child elements. If dispatchTouchEvent returns false, then the ViewGroup will distribute the event to the next element (if there are any children). MFirstTouchTarget = addTouchTarget;

    private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }
Copy the code

MFirstTouchTarget = mFirstTouchTarget; mFirstTouchTarget = mFirstTouchTarget; = null.

So now we’re done distributing the ViewGroup round, but we’re not done yet, so what if after going through all the child elements the event isn’t handled properly?

There are two situations where there is no proper treatment:

  • A ViewGroup has no child elements
  • The child handles the click event, but returns false in dispatchTouchEvent (default returns true, only if you override the View method or return false in onTouchEvent)

Then the ViewGroup will handle the click event itself (and do the same when the ViewGroup intercepts the event).

            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            }
Copy the code

As you can see at that time or calls for dispatchTransformedTouchEvent method, but this time the third parameter is not a child but null, so they will call the following code:

//dispatchTransformedTouchEvent
handled = super.dispatchTouchEvent(event);
Copy the code

Super is just the dispatchTouchEvent method in the View, so clicking on the event starts sending it to the View.

ViewGroup doesn’t call onTouchEvent, ViewGroup doesn’t override onTouchEvent

The flow chart is as follows:

3. Distribution mechanism of View events

The ViewGroup event distribution mechanism starts with dispatchTouchEvent.

Source: View# dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent event) { ...... boolean result = false; . // Check whether the window is blocked. If it is blocked, return false. For example, sometimes two views overlap and one of them is blocked. if (onFilterTouchEventForSecurity(event)) { if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) { result = true; } //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; // Check if mOnTouchListener is set, if onTouchListener is set, and onTouch returns true, // result = true if (li! = null && li.mOnTouchListener ! = null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } // If result = ture, onTouchEvent will not be called, so onTouchListener is higher than onTouchEvent if (! result && onTouchEvent(event)) { result = true; }}... return result; }Copy the code

Since the View is a single element, there are no child elements that can pass events down and only handle the events themselves, so the code is also significantly reduced. Result indicates whether to consume the event, and then judge onTouchListener. If the onTouch method in onTouchListenter returns true, then the onTouchEvent method will not be called again, indicating that onTouchListener has a higher priority than onTouchEvent.

And then let’s look at the implementation of onTouchEvent.

public boolean onTouchEvent(MotionEvent event) { final float x = event.getX(); final float y = event.getY(); final int viewFlags = mViewFlags; final int action = event.getAction(); final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE; // Handle the click event when it is not available. If ((viewFlags & ENABLED_MASK) == DISABLED) {if (action == motionEvent.action_UP && (mPrivateFlags &) PFLAG_PRESSED) ! = 0) { setPressed(false); } mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return clickable; } // If the VIew has a delegate set, the delegate's onTouchEvent method if (mTouchDelegate! = null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) { switch (action) { case MotionEvent.ACTION_UP: ..... boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) ! = 0; . // After all judgment performClickInternal(); break; case MotionEvent.ACTION_DOWN: .... break; case MotionEvent.ACTION_CANCEL: .... break; case MotionEvent.ACTION_MOVE: .... break; } // If the control is clickable, it must return true; } // If the control is not clickable, return false; }Copy the code

As you can see from the above code, whenever a View’s CLICKABLE, LONG_CLICKABLE, or CONTEXT_CLICKABLE is true, it consumes the event regardless of whether it is disabled. Then, if the control is clickable, each of the four event types is handled accordingly, notably the ACTION_UP event. You can see from the source code that the performClickInternal method is triggered when the ACTION_UP event occurs. What about the internal implementation of this method? As follows:

    private boolean performClickInternal() {
        // Must notify autofill manager before performing the click actions to avoid scenarios where
        // the app has a click listener that changes the state of views the autofill service might
        // be interested on.
        notifyAutofillManagerOnClick();

        return performClick();
    }
Copy the code

We can see that performClick is still called at the end, and inside performClick:

public boolean performClick() { // We still need to call this method to handle the cases where performClick() was called  // externally, instead of through performClickInternal() notifyAutofillManagerOnClick(); final boolean result; final ListenerInfo li = mListenerInfo; if (li ! = null && li.mOnClickListener ! = null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this); result = true; } else { result = false; } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); notifyEnterOrExitForAutoFillIfNeeded(true); return result; }Copy the code

As soon as we register the click event for the View via setOnClickListener, li.monClickListener is assigned a value and the onClick method is called.

The flow chart is as follows:

We can find the onTouch from the flowchart, onTouchEvent, onClick priority: the onTouch > onTouchEvent > onClick

conclusion

So far, we have combed through the distribution mechanism of click events through source code. The general process of event distribution is as follows:

  • When a click event occurs, it is always first passed to the current Activity, which is distributed by the Activity’s dispatchTouchEvent. The Activity passes the event to the Window, and the PhoneWindow class, the Window’s unique implementation, passes the event to the DecorView. The DecorView then passes the event to its parent ViewGroup, which is the View set by setContentView and therefore called the top-level View. The ViewGroup can either handle the event itself or pass it to its child View. But eventually the View’s dispatchTouchEvent is called to handle the event.
  • In the View dispatchTouchEvent, if onTouchListener is set, its onTouch method is called, and if onTouch returns true, onTouchEvent is not called again. If there is a click event, the onClick method is called in onTouchEvent. If the child View’s onTouchEvent returns false, it does not consume the event, The event is passed back to the onTouchEvent of the next-level ViewGroup. If none of the viewGroups returns true, it is eventually passed back to the Activity’s onTouchEvent.

Reference blog:

  • Android event distribution mechanism in detail walkthrough, you deserve it
  • Android development knowledge (9) : Android event processing mechanism: event distribution, transmission, interception, processing mechanism of principle analysis (2)