This article is a personal summary of the following articles for personal study and review:www.jianshu.com/p/38015afcd…

1 Basic Concepts

1.1 Who is the Event Distribution Target?

  • Touch events are generated when the user touches the screen, and the details (location, time, and so on) are encapsulated as MotionEvent objects. The MotionEvent object is the object for event distribution.

  • The event type

    The event type The specific action
    MotionEvent.ACTION_DOWN Press, finger touch screen (event start)
    MotionEvent.ACTION_UP Lift, finger off screen (usually, end of event)
    MotionEvent.ACTION_MOVE Swipe, swipe your finger across the screen
    MotionEvent.ACTION_CANCEL The gesture is cancelled and no longer accepts subsequent events (for non-human reasons)
    MotionEvent.ACTION_OUTSIDE This indicates that the user has reached a normal UI boundary
    MotionEvent.ACTION_POINTER_DOWN This means that the user uses another finger to touch the screen, that is, a new touch point appears when there is already one touch point.
    MotionEvent.ACTION_POINTER_UP Non-last finger up
  • A sequence of events usually starts with a DOWN event and ends with an UP event, with 0 to countless MOVE events in between.

when(event? .action? .and(MotionEvent.ACTION_MASK)){}// Multi-finger touch needs to be combined with MotionEvent.ACTION_MASK to be detected
Copy the code

1.2 Nature of event distribution

  • Pass the resulting MotionEvent to a specificViewThe whole process of processing (consumption)
  • Returns true once the event is consumed by one View and false if all views are not consumed.

1.3 Event Distribution Sequence

  • Activity → ViewGroup → View
  • The event is passed first to the Activity, then to the DecorView (ViewGroup object), the root node of the entire View tree, and then down the View tree (recursive process) until it reaches the leaf node (View object). Distribution ends as soon as the event is consumed anywhere

How does an event arrive at an Activity? (It is recommended to read this article at the end)

The touch information is first obtained by the underlying driver of the system and then handed over to InputManagerService, also known as IMS. IMS finds the window to distribute through WMS based on this touch information, and then IMS sends the touch information to the ViewRootImpl corresponding to the window (so WMS just provides information about the window — ViewRootImpl). The ViewRootImpl then distributes the touch information to the top-level View. The top-level View in an Activity is a DecorView. The DecorView overrides onDispatchTouchEvent to distribute touch information to the window.callback interface that the Activity implements. And you set yourself to the DecorView when you create the layout, so you’re actually redistributing back to the Activity.

See article: juejin.cn/user/393150…

2 Event distribution mechanism

  • To understand the distribution mechanism of View events, therefore, is to understand how activities, ViewGroups, and Views distribute events, respectively.

  • Activities, ViewGroups, and Views handle distribution in three ways:

    methods role Call time
    dispatchTouchEvent() Distribute events When passed to the current object (method called first)
    onTouchEvent() Handle events ** (ViewGroup is not overwritten, calls View) ** Called within dispatchTouchEvent()
    onInterceptTouchEvent() Intercepting events ** (ViewGroup only) ** Called within dispatchTouchEvent()

2.1 Event distribution mechanism of Activity

MotionEvent is passed to the Activity first, then the dispatchTouchEvent() method is called

2.1.1 dispatchTouchEvent Activity
public boolean dispatchTouchEvent(MotionEvent ev) {
    OnUserInteraction () is called when the event is pressed
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    // Call PhoneWindow superDispatchTouchEvent(ev)
    if (getWindow().superDispatchTouchEvent(ev)) {
        // If events are consumed in PhoneWindow, distribution is over, and returns true
        return true;
    }
    // If there is no consumption event in PhoneWindow, call onTouchEvent for the Activity to see if the Activity consumes the event
    return onTouchEvent(ev);
}
Copy the code

Non-important (skip) : OnUserInteraction () is an empty implementation that can be overridden to callback the initial calls of dispatchKeyEvent, dispatchTrackballEvent and other events when the screen is pressed (within the scope of the Activity). But things like buttons and trackballs are almost invisible on Android today). In addition, the onUserLeaveHint() callback is called in many places, because the onUserLeaveHint() callback is called when the user chooses to go into the background (the system does not). OnUserInteraction () will be called back when interacting with the Activity (events, home returns, clicking the notification bar to jump somewhere else, etc.).

Take a look at the superDispatchTouchEvent(EV) method in PhoneWindow:

public boolean superDispatchTouchEvent(MotionEvent event) {
    // Call DecorView superDispatchTouchEvent(event)
    return mDecor.superDispatchTouchEvent(event);
}
Copy the code

Look at the superDispatchTouchEvent(EV) method in the DecorView:

public boolean superDispatchTouchEvent(MotionEvent event) {
    // Call the parent's dispatchTouchEvent
    return super.dispatchTouchEvent(event);
}
Copy the code

Since FrameLayout did not override dispatchTouchEvent, it goes into the dispatchTouchEvent of the ViewGroup

2.2.2 dispatchTouchEvent ViewGroup
public boolean dispatchTouchEvent(MotionEvent ev) {...// Define return object, default return false
    boolean handled = false; .// Call onInterceptTouchEvent to see if it needs to be intercepted
    intercepted = onInterceptTouchEvent(ev);
    // Neither cancel nor intercept traverses the child View
    if(! canceled && ! intercepted) { ...final intchildrenCount = mChildrenCount; .for (int i = childrenCount - 1; i >= 0; i--) {
            final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
            final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
            // See if the location of the click event is inside a child View
            if(! child.canReceivePointerEvents()|| ! isTransformedTouchPointInView(x, y, child,null)) {
                continue; }.../ / if there is such a child View call dispatchTransformedTouchEvent method, the method according to react to a child for null
            // Child is not null, call child.dispatchTouchEvent(event)
            // Call super.dispatchTouchEvent(event) if child is empty
            // Both are dispatchTouchEvent calls to View
            handled = dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)
        }
        ...
        // If no child View consumes the event, the ViewGroup sees if it wants to consume the event
        // Call super.dispatchTouchEvent(event) internally, i.e. call ViewGroup's parent view.dispatchTouchEvent (event)
        handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS); . }...return handled;
}  

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
    final boolean handled;
    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);
        returnhandled; }... }Copy the code
2.2.3 onInterceptTouchEvent method of ViewGroup
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

This method can be overridden to intercept events.

2.2.4 dispatchTouchEvent View
public boolean dispatchTouchEvent(MotionEvent event) {...// Define the return result
    boolean result = false; . ListenerInfo li = mListenerInfo;if(li ! =null&& li.mOnTouchListener ! =null	//1. Set setOnTouchListener to true
        && (mViewFlags & ENABLED_MASK) == ENABLED  //2. Check whether the current control is enable. Most controls are enable by default
        && li.mOnTouchListener.onTouch(this, event)) { / / 3. MOnTouchListener. The onTouch method return values
        // If all three criteria are met, true is returned, meaning the click event has been consumed
        result = true;
    }
    // If it is still not consumed, call onTouchEvent
    if(! result && onTouchEvent(event)) { result =true; }...return result;
}
Copy the code

You can see that the onTouch method in OnTouchListener takes precedence over the onTouchEvent (Event) method

2.2.5 onTouchEvent View
public boolean onTouchEvent(MotionEvent event) {...switch (action) {
        // Lift the event, performClickInternal calls the performClick method internally
    	caseMotionEvent.ACTION_UP: ... performClickInternal(); .// The click and move events will determine whether the long press is triggered, which is used to determine whether the long press callback is triggered
        caseMotionEvent.ACTION_DOWN: ... checkForLongClick( ViewConfiguration.getLongPressTimeout(), x, y, TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);  .case MotionEvent.ACTION_CANCEL:
           	...
        caseMotionEvent.ACTION_MOVE: ... }... }Copy the code

The switch outer layer also contains nested judgments that provide a default return: true if the control is clickable, false if it is not.

public boolean performClick(a) {...final boolean result;
    final ListenerInfo li = mListenerInfo;
    // If the onClick method is called back to prove that the event was consumed, return true. If not, return false
    if(li ! =null&& li.mOnClickListener ! =null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        li.mOnClickListener.onClick(this);
        result = true;
    } else {
        result = false; }...return result;
}
Copy the code
2.2.6 onTouchEvent Activity
public boolean onTouchEvent(MotionEvent event) {
    if (mWindow.shouldCloseOnTouch(this, event)) {
        finish();
        return true;
    }

    return false;
}
Copy the code

Look at the shouldCloseOnTouch method in the Window

public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
    final boolean isOutside =
            event.getAction() == MotionEvent.ACTION_UP && isOutOfBounds(context, event)
            || event.getAction() == MotionEvent.ACTION_OUTSIDE;
    if(mCloseOnTouchOutside && peekDecorView() ! =null && isOutside) {
        return true;
    }
    return false;
}
Copy the code
setFinishOnTouchOutside(true)// Therefore, things like Dialog can change the value of mCloseOnTouchOutside to close when clicking outside
Copy the code

Summary of distribution process: From the Activity. The dispatchTouchEvent began to distribute events to DecorView this ViewGroup, from the ViewGroup. DispatchTouchEvent distribution down, In the ViewGroup, onInterceptTouchEvent is called to determine whether it needs to be intercepted. If it does not, it will recursively distribute it to the child View of the leaf node. If the View calls dispatchTouchEvent and it has onTouchListener, it calls onTouch, and then it calls its own onTouchEvent based on what it returns, OnTouchEvent raises the event to check if there is an onClickListener, if there is an onClick method to consume the event, if there is no onClick method to consume the event, if there is no onTouchEvent, it goes back to the ViewGroup, so if none of the child views consume the event, it calls its parent onTouchEvent, which is in the View, Check again. If all of the DecorView’s children do not consume, and do not consume themselves, we go back to Acelasticity. The onTouchEvent of the Activity is called, and the Activity is consumed if the event is set to consume outside the Activity, otherwise it returns false.