Comprehensive Analysis of Android Event Distribution Mechanism

  • 2021-12-11 19:10:46
  • OfStack

Directory event distribution mechanism ViewGroup. dispatchTouchEvent source code analysis Source code analysis of View. dispatchTouchEvent and View. onTouchEvent

Event distribution mechanism

Two phases of the event distribution mechanism:

Distribution: Events are distributed from parent view to child view, and are not passed after being intercepted, and enter the backtracking stage Backtracking: Events are traced back from the child view to the parent view, and no backtracking is done after being consumed

Key methods:

ViewGroup. dispatchTouchEvent Distribute Events to Child Views ViewGroup. onInterceptTouchEvent Returning true means intercepting the distribution event, no longer delivering, and entering the current view onTouchEvent View. dispatchTouchEvent default event distribution, calling onTouchEvent View. onTouchEvent typically overloads this method to handle events, returning true to indicate consumption events, no longer passing, and returning false to backtrack ViewParent. requestDisallowInterceptTouchEvent (true) ensures that events are not intercepted before they are distributed to child views

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


A.dispatchTouchEvent
 B.dispatchTouchEvent
  C.dispatchTouchEvent
   D.dispatchTouchEvent
   D.onTouchEvent 
  C.onTouchEvent
 B.onTouchEvent
A.onTouchEvent

Suppose B intercepts events:


A.dispatchTouchEvent
 B.dispatchTouchEvent -> B.onInterceptTouchEvent 
 B.onTouchEvent
A.onTouchEvent

Suppose C. onTouchEvent consumes an event:


A.dispatchTouchEvent
 B.dispatchTouchEvent
  C.dispatchTouchEvent
   D.dispatchTouchEvent
   D.onTouchEvent 
  C.onTouchEvent 

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 {...}
}

ViewGroup. dispatchTouchEvent source code analysis

1. Start: ACTION_DOWN event starts a new event sequence, clearing the previous touch state
2. Intercept:

2.1. Non-ACTION_DOWN Events If there are no child view consumption events at present, the event sequence has been intercepted
2.2. When the event is not intercepted and the child view has not applied for prohibition of interception, try to intercept the event through onInterceptTouchEvent

3. Distribution: If the event is not intercepted or canceled, traverse the child view to distribute the event and find the touch target of the current event

3.1. Find a view touch target that can consume the current event in the touch target linked list- > Mark it as the current touch target and delay until step 4 distributes the event to it
3.2. 1 view not in the touch target list consumes an event- > Mark it as the current touch target and set it as the head of the touch target linked list
3.3. The view consuming the current event was not found, but the linked list of touch targets is not empty- > Mark the end of the linked list of touch targets as the current touch target

4. Distribution: If the touch target chain is not empty, traverse the touch target chain to try to pass an event or cancel the touch target (the event is intercepted)
5. Backtracking: If you touch the target linked list as empty (there is no sub-view consumption event sequence at present), you will forward the event to the base class dispatchTouchEvent for processing
Note: The Touch Target (ViewGourp. TouchTarget) describes a touched sub-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. `ACTION_DOWN`  Event start 1 A new sequence of events, clearing the previous touch state  ...
    }
    //  Omit code  ... 
    final boolean intercepted;
    // 2.  Intercept 
    if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
      final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
      if (!disallowIntercept) {
        // 2.2.  Event is not intercepted and the child view has not applied to prohibit interception, and then pass the  onInterceptTouchEvent  Attempt to intercept events 
        intercepted = onInterceptTouchEvent(ev);
        //  Omit code  ...
      } else {
        intercepted = false;
      }
    } else { 
      // 2.1.  Non  `ACTION_DOWN`  Event If there are no child view consumption events at present, the event sequence has been intercepted 
      intercepted = true;
    }
    //  Omit code  ... 
    if (!canceled && !intercepted) {
      //  Omit code  ... 
          // 3.  Distribution: If the event is not intercepted or canceled, traverse the child view to distribute the event and find 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.  A 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 to step 4 Distribute events to it 
              //  Omit code  ... 
              break;
            }
            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
              //  Omit code  ...
              // 3.2. 1 Events were consumed by views that are not in the touch target linked list  ->  Mark it as the current touch target and set it as the head of the touch target linked list 
              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 linked list is not empty  ->  Mark the end of the linked list of touch targets 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) {
      // 5.  Backtracking: The linked list of touch targets is empty ( There is currently no child view consumption event sequence ) The event is forwarded to the base class  dispatchTouchEvent  Deal with 
      handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
    } else {
      //  Omit code  ... 
      // 4.  Distribution: If the linked list of touch targets is not empty, traverse the chain of touch targets to try to pass events or cancel the touch targets ( Event intercepted )
      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;
}

Source code analysis of View. dispatchTouchEvent and View. onTouchEvent

Scroll bar consumes mouse events OnTouchListener Consumer Touch Events onTouchEvent Consumer Touch Events

TouchDelegate Consumer Touch Events


public boolean dispatchTouchEvent(MotionEvent event) {
  //  Omit code  ...
  boolean result = false;
  
  //  Omit code  ... 
  if (onFilterTouchEventForSecurity(event)) {
    //  Scroll bar consumes mouse events 
    if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
      result = true;
    } 
    // OnTouchListener  Consumer touch event 
    ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) {
      result = true;
    } 
    // View Default event handling logic in which events may be set  TouchDelegate  Consumption 
    if (!result && onTouchEvent(event)) {
      result = true;
    }
  }
  //  Omit code  ... 
  return result;
} 

public boolean onTouchEvent(MotionEvent event) {
  //  Omit code  ... 
  if (mTouchDelegate != null) {
    // TouchDelegate  Consumer touch event 
    if (mTouchDelegate.onTouchEvent(event)) {
      return true;
    }
  }
  //  Omit code  ... 
  return false;
}

The above is the Android event distribution mechanism comprehensive analysis of the details, more information about Android event distribution mechanism please pay attention to other related articles on this site!


Related articles: