preface

1. Understand the composition of an Activity

An Activity contains a Window object, which is implemented by PhoneWindow. PhoneWindow uses a DecorView as the root View of the entire application window, which in turn divides the screen into two regions: One is the TitleView and the other is the ContentView, and what we usually write is displayed in the ContentView. The following diagram shows the structure of an Activity.

2. Types of touch events

Touch events correspond to the MotionEvent class, and there are three main types of events:

  • ACTION_DOWN
  • ACTION_MOVE(Moves beyond a certain threshold will be considered an ACTION_MOVE action)
  • ACTION_UP

Three, the three stages of event transmission

  • DispatchTouchEvent: method returns true to indicate that the event is consumed by the current view; Returning super.dispatchTouchEvent means that the event continues to be distributed.
  • OnInterceptTouchEvent: the method returns true to intercept the event and send it to its onTouchEvent method for consumption; Returning false means no interception and needs to continue to be passed to the subview.

If return super. OnInterceptTouchEvent (ev), event interceptor in two situations:

1. If the View(ViewGroup) has a child View and the child View is clicked, it is not intercepted and continues to be distributed to the child View for processing, equivalent to return false. 2. If the View(ViewGroup) has no child View or has child View but does not click on the subview (in this case, the ViewGroup is equivalent to a normal View), the onTouchEvent of the View will respond, which is equivalent to return true. Viewgroups such as LinearLayout, RelativeLayout, and FrameLayout are not blocked by default, whereas viewgroups such as ScrollView and ListView are blocked by default.Copy the code
  • Consume (onTouchEvent) : method returns true to indicate that the current view can process the corresponding event; A return value of false means that the event is not handled by the current view and is passed to the parent view’s onTouchEvent method for processing. If return super.onTouchEvent(EV), event handling is divided into two cases:

    1. If the View is clickable or longClickable, return true, indicating that the event was consumed. 2. If the View is not clickable or longClickable, false will be returned, indicating that the event is not consumed and will be passed up, just as false is returned.

Note: There are three classes with event-passing capabilities in Android.

  • Activity: Has both distribution and consumption methods.
  • ViewGroup: has distribute, intercept, and consume methods.
  • View: Has distribution and consumption methods.

4. Activity Distributes click events

When we operate on the touch screen, Linux receives the corresponding hardware interrupts, which are then processed into raw input events and written to the corresponding device nodes. And what our Android input system does is basically monitor these device nodes. When a device node has data to read, it reads the data and performs a series of translation processes, and then it finds the appropriate event receiver in all the Windows and sends it. When the click event is generated, the event is passed to the current Activity, which is done by the PhoneWindow in the Activity. The PhoneWindow passes the event processing to the DecorView, which passes the event processing to the ViewGroup. The source code flow is as follows:

1.Activity#dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); If (getWindow().superdispatchTouchEvent (ev)) {return true; if (getWindow().superdispatchTouchEvent (ev)); Return onTouchEvent(ev);} return onTouchEvent(ev); return onTouchEvent(ev); }Copy the code
2. Abstract Window#superDispatchTouchEvent
public abstract boolean superDispatchTouchEvent(MotionEvent event);
Copy the code
3. Unique implementation of class Phonewindows #superDispatchTouchEvent
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}
Copy the code

5. Event distribution mechanism of View

Events are dispatched to the ViewGroup’s dispatchTouchEvent method and handled by itself if its onInterceptTouchEvent returns true. If its mOnTouchListener is set, onTouch will be called. Otherwise onTouchEvent will be called. In onTouchEvent, onClick is called if mOnCLickListener is set. If its onInterceptTouchEvent returns false, it will be handled by a child View on the click event chain, and so on, completing the distribution. ViewGroup#dispatchTouchEvent

1.ViewGroup resets status when ACTION_DOWN events arrive
// Handle an initial down. 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 stae change. cancelAndClearTouchTarget(ev); // FLAG_DISALLOW_INTERCEPT resetTouchState() is reset in this method; }Copy the code
2. Handle whether the current View blocks the click event
Final Boolean interception. // When the event is successfully handled by a child element of ViewGorup, mFirstTouchTarget is assigned and refers to the child element; otherwise, mFirstTouchTarget is null when the event is intercepted by a ViewGroup. if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! = null) {/ / in the sub View through requestDisallowInterceptTouchEvent methods to set / / FLAG_DISALLOW_INTERCEPT: ViewGroup will not be able to intercept events other than ACTION_DOWN FLAG_DISALLOW_INTERCEPT) ! = 0; if (! disallowintercept) { intercepted = onInterceptTouchEvent(ev); //re store action in case it was changed ev.setAction(action); } else { intercepted = false; } else { // There are no touch targets and this action is not an initial down so this // view group continues to Touches (ACTION_MOVE, action_up.eg). intercepted = true; }}Copy the code
3. The rest of the dispatchTouchEvent() method source
public boolean dispatchTouchEvent(MotionEvent ev) { ... final View[] children = mChildren; // Iterates through the child elements of the ViewGroup, and if the child element can receive the click event, passes it to the child element for processing. 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 (childWithAccessibilityFocus ! = null) { if (childWithAccessibilityFocus ! = child) { continue; } childWithAccessibilityFocus = null; i = childrenCount - 1; } // Determine whether the touch point is in the range of the child View or whether the child View is playing animation, if one // does not match, start traversing the next child View. if (! canViewReceivePointerEvents(child) || ! isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; } newTouchTarget == getTouchTarget(child); if (newTouchTarget ! = null) { newTouchTarget.pointerIdBits |= idBitsToAssign; break; } resetCancelNextUpFlag(child); if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { mLastTouchDownTime = ev.getDownTime(); if (preorderedList ! = null) { 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; break } ev.setTargetAccessibilityFocus(false); }... }Copy the code
4. Perform the real distribution of logic in dispathcTransformedTouchEvent method
private boolean dispatchTransformedTouchEvent(MotionEvent event,boolean cancel,View child,int desiredPointerIdBits) { final int oldAction = event.getAction(); if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { event.setAction(MotionEvent.ACTION_CANCEL); // Call the dispatchTouchEvent(event) method of the child View if there is no child View, or call the super.dispatchTouchEvent(event) method if there is no child View. if (child == null) { handled == super.. dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; }... }Copy the code
5. DispatchTouchEvent () to View
public boolean dispatchTouchEvent(MotionEvent event) { ... boolean result = false; if (onFilterTouchEventForSecurity(event)) { ListenerInfo li = mListenerInfo; // The onTouch method takes precedence over the onTouchEvent(event) method if (li! = null && li.mOnTouchListener ! = null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } if (! result && onTouchEvent(event)) { result == true; }}... return result; }Copy the code
6. Event pass to View onTouchEvent()
public boolean onTouchEvent(MotionEvent event) { ... final int action = event.getAction(); OnTouchEvent () returns true whenever either View's CLICKABLE or LONG_CLICKABLE is true. If ((viewFlags & CLICKABLE) = = CLICKABLE | | (viewFlags & LONG_CLICKABLE) = = LONG_CLICKABLE) | | (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) { switch(action) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) ! = 0; if ((mPrivatFlags & PFLAG_PRESSED) ! = 0 || prepressed) { boolean focusTaken = false; if (! mHasPerformedLongPress && ! mIgnoreNextUpEvent) { removeLongPressCallback(); if (! focusTaken) { if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (! post(mPerformClick)) { performClick(); }}}}... } return true; } return true; }Copy the code
7. The performCLick() method is called in the ACTION_UP event
public boolean performClick() { final boolean result; final Listenerinfo li = mListenerInfo; // If the View sets the click event, the onClick method is executed. if (li ! = null && li.mOnClickListener ! == null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this); result = true; } else { result = false; } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); return result; }Copy the code

According to the above source code analysis, the complete click event transfer process of View can be obtained as shown in the figure below.

Summary: the delivery rules of click event distribution

According to the source code analysis of event distribution, the relationship between the three important methods of click event distribution can be expressed in pseudocode as follows:

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

Some important conclusions:

1. Event delivery priority: onTouchListener. OnTouch > onTouchEvent > onClickListener.

2. Normally, a time series can only be intercepted and consumed by one View. Because once an element intercepts the event, all events in the same sequence are handed to it directly (that is, the View’s interception method is no longer called to ask it whether to intercept, but the remaining ACTION_MOVE, ACTION_DOWN, etc.). Exception: You can force the event to be handled by another View by returning false to the onTouchEvent that overrides the View.

3. If the View consumes no events other than ACTION_DOWN, the click event will disappear, the parent element’s onTouchEvent will not be called, and the current View will continue to receive subsequent events, which will eventually be passed to the Activity.

4.ViewGroup does not intercept any events by default (return false).

5. The View’s onTouchEvent will consume the event by default (returning true), unless it is unclickable (both Clickable and longClickable are false). The longClickable property of a View is false by default. The Clickable property of a Button is true by default and that of a TextView is false by default.

6. The Enable property of View does not affect the default return value of onTouchEvent.

7. Through requestDisallowInterceptTouchEvent method can intervene in the parent element in child elements distribution of events, except ACTION_DOWN events.

The final complete event distribution flow chart is as follows:

Reference links:

1. Android development art exploration

2. Android advanced Light

3, Android advanced advanced

4. Gityuan Android event distribution mechanism

5. Understand Android’s event distribution mechanism

6, Android event distribution mechanism: the most comprehensive, most easy to understand

7, Android development long distance ⅵ — Illustration of Android event distribution mechanism (deep source code)

Thank you for reading this article and I hope you can share it with your friends or technical group, it means a lot to me.