Event distribution mechanism

Two phases of the event distribution mechanism:

  • Dispatch: Events are distributed from parent view to child view. After being intercepted, they are not transmitted and go back
  • Traceback: Events traceback from the child view to the parent view, not after being consumed

Key methods:

  • ViewGroup.dispatchTouchEventDistribute events to subviews
  • ViewGroup.onInterceptTouchEventIf true is returned, the distribution event is intercepted, no longer passed, and the current view is enteredonTouchEvent
  • View.dispatchTouchEventDefault event distribution, callonTouchEvent
  • View.onTouchEventUsually override this method to handle events, returning true to consume the event, not passing it, and returning false to trace back
  • ViewParent.requestDisallowInterceptTouchEvent(true)You can ensure that events are not intercepted before they are distributed to subviews

Assuming the view level is A.B.C.D, the default procedure for event dispatch backtracking is:

A.dispatchTouchEvent
  B.dispatchTouchEvent
    C.dispatchTouchEvent
      D.dispatchTouchEvent
      D.onTouchEvent 
    C.onTouchEvent
  B.onTouchEvent
A.onTouchEvent
Copy the code

Suppose B intercepts the event:

A.dispatchTouchEvent
  B.dispatchTouchEvent -> B.onInterceptTouchEvent 
  B.onTouchEvent
A.onTouchEvent
Copy the code

Suppose C.onTouchEvent consumes the event:

A.dispatchTouchEvent
  B.dispatchTouchEvent
    C.dispatchTouchEvent
      D.dispatchTouchEvent
      D.onTouchEvent 
    C.onTouchEvent  
Copy the code

Event distribution mechanism pseudocode:

class Activity { 
    fun dispatchTouchEvent(ev) {
        if (parent.dispatchTouchEvent(ev)) {
            return true
        }
        return onTouchEvent(ev)
    }
    fun onTouchEvent(ev):Boolean {...}
} 

class ViewGroup : View {  
    fun dispatchTouchEvent(ev) { 
        var handled = false
        if(! onInterceptTouchEvent(ev)) { handled = child.dispatchTouchEvent(ev) }return handled || super.dispatchTouchEvent(ev) 
    } 
    fun onInterceptTouchEvent(ev):Boolean{... }fun onTouchEvent(ev):Boolean {...}
} 

class View {  
    fun dispatchTouchEvent(ev) { 
        var result = false
        if (handleScrollBarDragging(ev)) {
            result = true
        }
        if(! result && mOnTouchListener.onTouch(ev)) { result =true
        } 
        if(! result && onTouchEvent(ev)) { result =true
        }
        return result
    } 
    fun onTouchEvent(ev):Boolean {...}
}
Copy the code

ViewGroup. DispatchTouchEvent source code analysis

  1. start:ACTION_DOWNThe event starts a new sequence of events that clears the previous touch state
  2. intercept:

    2.1. TheACTION_DOWNEvent If no subview currently consumes events, the event sequence has been intercepted

    2.2. If an event is not intercepted and the subview does not apply for an interception prohibition, use onInterceptTouchEvent to intercept the event
  3. Distribute: If the event is not intercepted or cancelled, the subview distributes the event and looks for the touch target of the current event 3.1. The view touch target that can consume the current event is found in the Touch target linked list -> mark it as the current touch target and delay until Step 4 to distribute the event to it 3.2. A view that is not in the Touch target list consumes the event -> marks it as the current touch target and sets it to the touch target list header 3.3. The view consuming the current event was not found, but the Touch target list is not empty -> Marks the end of the touch target list as the current touch target
  4. Distribution: If the touch target list is not empty, traverse the touch target chain to try to pass the event or cancel the touch target (the event is blocked)
  5. Backtracking: If the touch target list is empty (there are currently no subviews consuming the event sequence), the event is forwarded to the base class dispatchTouchEvent for processing

Note: the TouchTarget (ViewGourp.TouchTarget) describes a touched child view and its captured pointer ids


public boolean dispatchTouchEvent(MotionEvent ev) {
    // omit code...
    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {
     
        if (actionMasked == MotionEvent.ACTION_DOWN) { 
            // 1. The 'ACTION_DOWN' event starts a new sequence of events and clears the previous touch state...
        }
        // omit code...
        final boolean intercepted;
        / / 2. Intercept
        if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {
            final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
            if(! disallowIntercept) {If the event is not intercepted and the subview does not apply for blocking prohibition, use onInterceptTouchEvent to intercept the event
                intercepted = onInterceptTouchEvent(ev);
                // omit code...
            } else {
                intercepted = false; }}else { 
            // 2.1. Non-action_down events If there are currently no sub-view consumption events, the event sequence has been intercepted
            intercepted = true;
        }
        // omit code...
        if(! canceled && ! intercepted) {// omit code...
                    // 3. Dispatch: If the event is not intercepted or cancelled, the subview dispatches the event and looks for the touch target of the current event
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        // omit code...
                        newTouchTarget = getTouchTarget(child);
                        if(newTouchTarget ! =null) { 
                            // 3.1. The view touch target that can consume the current event is found in the Touch target list -> mark it as the current touch target and defer sending the event to it until Step 4
                            // omit code...
                            break;
                        }
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            // omit code...
                            // 3.2. A view not in the Touch target list consumes an event -> marks it as the current touch target and sets it as the touch target list header
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }
                        // omit code...
                    }
                    
                if (newTouchTarget == null&& mFirstTouchTarget ! =null) {
                    // 3.3. The view consuming the current event was not found, but the Touch target list is not empty -> Mark the end of the touch target list as the current touch target
                    newTouchTarget = mFirstTouchTarget;
                    while(newTouchTarget.next ! =null) {
                        newTouchTarget = newTouchTarget.next;
                    }
                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                }
            // omit code...
        }

        // Dispatch to touch targets.
        if (mFirstTouchTarget == null) {
            Backtracking: If the touch target list is empty (there are currently no subviews consuming the event sequence), the event is forwarded to the base class dispatchTouchEvent
            handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
        } else {
            // omit code...
            // 4. Distribution: The touch target list is not empty, then the touch target chain is traversed to try to pass the event or cancel the touch target (the event is blocked)
            TouchTarget target = mFirstTouchTarget;
            while(target ! =null) { 
                final TouchTarget next = target.next; 
                // omit code...
                    if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {
                        handled = true;
                    } 
                // omit code...target = next; }}// omit code...
    }
    // omit code...
    return handled;
}
Copy the code

View.dispatchTouchEvent and view. onTouchEvent source code analysis

  • Scroll bars consume mouse events
  • OnTouchListener consumes the touch event
  • OnTouchEvent consumes touch events
    • TouchDelegate consumes touch events
public boolean dispatchTouchEvent(MotionEvent event) {
    // omit code...
    boolean result = false;
    
    // omit code...
    if (onFilterTouchEventForSecurity(event)) {
        // The scroll bar consumes mouse events
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        } 
        // OnTouchListener consumes the touch event
        ListenerInfo li = mListenerInfo;
        if(li ! =null&& li.mOnTouchListener ! =null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        } 
        // View's default event handling logic, where events may be set to be consumed by the TouchDelegate
        if(! result && onTouchEvent(event)) { result =true; }}// omit code...
    return result;
} 

public boolean onTouchEvent(MotionEvent event) {
    // omit code...
    if(mTouchDelegate ! =null) {
        // TouchDelegate consumes touch events
        if (mTouchDelegate.onTouchEvent(event)) {
            return true; }}// omit code...
    return false;
}
Copy the code