Android event distribution mechanism source code and example analysis

Android event distribution mechanism source code and example analysis

1. Understanding of the event distribution process

1.1. Overview

1.2. Main methods

1.3. Core Behavior

1.4. Special Cases

2. Case Analysis

2.1. Case 1: No down event is consumed

2.2. Case 2: View0 consumes down event

2.3. Case 3: ViewGroup2nd consumes down event

3.down event distribution diagram

1. Understanding of the event distribution process

1.1. Overview

The main events are down (MotionEvent.ACTION_DOWN), move (MotionEvent.ACTION_MOVE), and up (MotionEvent.ACTION_UP).

Basically, gestures start with a down event and end with an up event, with a certain number of move events in between. These three events are the basis of most gestures.

Events and related information (such as coordinates) are encapsulated into MotionEvent.

The general distribution process is: first pass it to the Activity, then pass it to the Window to which the Activity is attached, then pass it from the Window to the top View of the view, that is, DecorView, and finally distribute it from DecorView to the entire ViewTree. There is also a backtracking process for distribution, and finally it returns to the call of the Activity.

Activity distribution event source code

  1. public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) {
  2. onUserInteraction();
  3. } if (getWindow().superDispatchTouchEvent(ev)) { return   true ;
  4. } return onTouchEvent(ev);
  5. }

In getWindow().superDispathTouchEvent is used to distribute events to DecorView. If the entire ViewTree does not consume the event, the onTouchEvent of the Activity will be called.

1.2. Main methods

1.2.1. Overview

The main methods of View or ViewGroup involved are:

dispatchTouchEvent, this method encapsulates the entire process of event distribution. It is the dispatcher and commander of event distribution. The core process is in this method. The following onInterceptTouchEvent and onTouchEvent callbacks are called in this method body. Whether to pass the event to

onInterceptTouchEvent and onTouchEvent are determined by dispatchTouchEvent.

onInterceptTouchEvent, this method determines whether to intercept the event. Only ViewGroup has this callback. Return true to intercept, return false to not intercept. When customizing View, you can override this method and use some specific logic to decide whether to intercept the event. If intercepted, the onTouchEvent of the ViewGroup will be called to handle the event.

onTouchEvent, this method processes the event and decides whether to continue consuming subsequent events. The prerequisites for calling this method are:

  • The View intercepts the event
  • Sub-Views do not consume events
  • No child View

This method formally processes MotionEvent. Return true to consume, return false to not consume. If consumed, the following events will be passed to the dispatchTouchEvent of the View; if not consumed, the following events will not be passed.

The onTouch callback of onTouchListener, like onTouchEvent, has a higher priority than onTouchEvent. If the listener is set and onTouch returns true, onTouchEvent will not be called again. If it returns false, the event will still be passed to onTouchEvent.

1.2.2. Some details in the dispatchTouchEvent method:

Most gestures start with a down event. If dispatchTouchEvent receives a down event, it will reset some variables and flags.

Reset variables and flags source

  1. // Handle an initial down.if (actionMasked == MotionEvent.ACTION_DOWN) { // Throw away all previous state when starting a new touch gesture.
  2. // The framework may have dropped the up or cancel event for the previous gesture
  3. // due to an app switch, ANR, or   some other state change.
  4. cancelAndClearTouchTargets(ev);
  5. resetTouchState();
  6. }

In the actual source code, ViewGroup inherits from View. When the child View does not consume the event or ViewGroup intercepts the event, it will pass a null value to dispatchTransformedTouchEvent, which will call super.dispatchTouchEvent internally and eventually pass the event to onTouchEvent for processing.

dispatchTransformedTouchEvent Key Part

  1. // Perform any necessary transformations and dispatch.if (child == null ) {
  2. handled = super.dispatchTouchEvent(transformedEvent);
  3. } else { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop;
  4. transformedEvent.offsetLocation(offsetX, offsetY); if (! child.hasIdentityMatrix()) {
  5. transformedEvent.transform(child.getInverseMatrix());
  6. }
  7.  
  8. handled = child.dispatchTouchEvent(transformedEvent);
  9. }

That is, dispatchTransformTouchEvent completes the *** process of distribution:

a. If the passed child is not empty, convert the coordinates to the child's coordinate system and call child.dispatchTouchEvent to dispatch the event to the child.

b. If the passed child is empty, call super.dispatchTouchEvent to distribute the event to onTouchEvent

1.2.3 Main relationships of methods

For a ViewGroup, the relationship between several important methods is as follows

Several important method relationship pseudocode

  1. public boolean dispatchTouchEvent(MotionEvent e) { boolean consumed = false ; if (onInterceptTouchEvent(e)) {
  2. consumed = onTouchEvent(e);
  3. } else { for ( View   view : childs) {
  4. consumed = view .dispatchTouchEvent(e); if (consumed) { break;
  5. }
  6. } if (!consumed) {
  7. consumed = onTouchEvent(e);
  8. }
  9. }
  10. return consumed;
  11. }

This is a simplified description of the event distribution process, which is much more complicated than this.

1.3. Core Behavior

View or ViewGroup has two core behaviors: intercept and consume. These two are independent of each other. Intercepting does not necessarily mean consuming. To determine whether to intercept, see onIntercepTouchEvent. To determine whether to consume, see onTouchEvent.

Note: There are other factors that affect whether to intercept. If it is not a down event and mFirstTouchTarget is null, the event will be intercepted directly.

In dispatchTouchEvent there is such code

Intercepted key source code

  1. // Check   for interception.final boolean intercepted;if (actionMasked == MotionEvent.ACTION_DOWN
  2. || mFirstTouchTarget != null ) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) {
  3. intercepted = onInterceptTouchEvent(ev);
  4. ev.setAction( action ); // restore action   in   case it was changed
  5. } else {
  6. intercepted = false ;
  7. }
  8. } else { // There are no touch targets and this action   is   not an initial down
  9. // so this view   group continues to intercept touches.
  10. intercepted = true ;
  11. }

From the source code above, we can see that if it is not a down event and mFirstTouchTarget is empty, onInterceptTouchEvent will not be called but intercepted directly. If the conditions are met, the FLAG_DISALLOW_INTERCEPT flag will be checked. If interception is not allowed (disallowIntercept is true), onInterceptTouchEvent will not be called and it will be marked directly as not to be intercepted.

Source code for processing onTouchEvent

  1. boolean result = false ;
  2.  
  3. ...
  4.  
  5. ListenerInfo li = mListenerInfo;if (li != null && li.mOnTouchListener != null  
  6. && (mViewFlags & ENABLED_MASK) == ENABLED
  7. && li.mOnTouchListener.onTouch(this, event)) {
  8. result = true ;
  9. }if (!result && onTouchEvent(event)) {
  10. result = true ;
  11. }

It can be seen that when the View is in the ENABLE state and has mTouchListener, onTouch will be called first, and onTouchEvent will be called only when onTouch returns false.

The processing results of onTouch or onTouchEvent are:

  • Return true, and the subsequent events will continue to be consumed. This means that the subsequent events will continue to be passed to the View's dispatchTouchEvent method for dispatch. The parent View will create a TouchTarget instance for the View and add it to the linked list. The first item in the linked list is mFirstTouchTarget. Subsequent move and up events will be directly handed over to the View's dispatchTouchEvent.
  • Return false and no longer consume subsequent events, which means that subsequent events will be intercepted by the parent View and no longer passed down.

1.4. Special Cases

In special cases, the child View can use requestDisallowInterceptTouchEvent to affect the distribution to the parent View, and can decide whether the parent View should call onInterceptTouchEvent. For example, if requestDisallowInterceptTouchEvent(true) is set, the parent View does not need to call onInterceptTouchEvent to determine the interception, but simply does not intercept.

This method can be used to resolve gesture conflicts. For example, the child View consumes the event first, but then the parent View also meets the conditions for gesture triggering and intercepts the event, causing the child View to be unable to continue responding after the gesture is halfway executed. You can use requestDisallowInterceptTouchEvent(true), so that for subsequent events, the parent View will not use the onInterceptTouchEvent callback to determine whether to intercept the event, but will directly pass the event on.

2. Case Analysis

Here are three simple examples, three classes ViewGroup1st, ViewGroup2nd and View0, the hierarchical relationship is

  1. <ViewGroup1st>
  2. <ViewGroup2nd>
  3. <View0 />
  4. </ViewGroup2nd></ViewGroup1st>

These three classes have two layers of ViewGroup, the top layer is View. These examples are mainly used to understand consumption behavior, so no event interception is performed.

2.1. Case 1: No down event is consumed

After touching the View0 area on the screen, the log information is output as follows

  1. 12-30 14:06:03.694 31323-31323/lyn.demo D/ViewGroup1st: dispatchTouchEvent before12-30 14:06:03.694 31323-31323/lyn.demo D/ViewGroup1st: onInterceptTouchEvent return :false12-30 14:06:03.694 31323-31323/lyn.demo D/ViewGroup2nd: dispatchTouchEvent before12-30 14:06:03.694 31323-31323/lyn.demo D/ViewGroup2nd: onInterceptTouchEvent return :false12-30 14:06:03.694 31323-31323/lyn.demo D/View0: dispatchTouchEvent before12-30 14:06:03.694 31323-31323/lyn.demo D/View0: onTouchEvent return :false12-30 14:06:03.694 31323-31323/lyn.demo D/View0: dispatchTouchEvent return :false12-30 14:06:03.694 31323-31323/lyn.demo D/ViewGroup2nd: onTouchEvent return :false12-30 14:06:03.694 31323-31323/lyn.demo D/ViewGroup2nd: dispatchTouchEvent return :false12-30 14:06:03.694 31323-31323/lyn.demo D/ViewGroup1st: onTouchEvent return : false12-30 14:06:03.694 31323-31323/lyn.demo D/ViewGroup1st: dispatchTouchEvent return : false  

When the down event starts the distribution process from DecorView:

ViewGroup1st receives the event, executes onInterceptTouchEvent and returns false, and does not intercept. Then it calls dispatchTouchEvent of ViewGroup2nd to distribute it to ViewGroup2nd.

ViewGroup2nd receives the event, and dispatchTouchEvent repeats the dispatch strategy of ViewGroup1st. Because neither intercepts, View0's dispatchTouchEvent is called.

View0 receives the event, but View0 is not of ViewGroup type, so the event is directly passed to onTouchEvent.

View0 does not consume events, onTouchEvent returns false, and the dispatchTouchEvent method therefore also returns false.

ViewGroup2nd, because View0's dispatchTouchEvent returns false, determines that the subclass does not consume the event, so it passes the event to onTouchEvent. However, it does not consume the event itself, so onTouchEvent also returns false, and continues to throw the event to ViewGroup1st.

ViewGroup1st repeats the process of ViewGroup2nd.

Subsequently, the move event will no longer be passed down, but will be directly intercepted by the Activity.

2.2. Case 2: View0 consumes down event

First, the down event is passed, the log is as follows

  1. 12-30 14:14:09.384 7350-7350/lyn.demo D/ViewGroup1st: dispatchTouchEvent before12-30 14:14:09.384 7350-7350/lyn.demo D/ViewGroup1st: onInterceptTouchEvent return :false12-30 14:14:09.384 7350-7350/lyn.demo D/ViewGroup2nd: dispatchTouchEvent before12-30 14:14:09.384 7350-7350/lyn.demo D/ViewGroup2nd: onInterceptTouchEvent return :false12-30 14:14:09.384 7350-7350/lyn.demo D/View0: dispatchTouchEvent before12-30 14:14:09.384 7350-7350/lyn.demo D/View0: onTouchEvent return :true12-30 14:14:09.384 7350-7350/lyn.demo D/View0: dispatchTouchEvent return :true12-30 14:14:09.384 7350-7350/lyn.demo D/ViewGroup2nd: dispatchTouchEvent return : true12-30 14:14:09.384 7350-7350/lyn.demo D/ViewGroup1st: dispatchTouchEvent return : true  

The transfer of ViewGroup1st and ViewGroup2st is the same as in Case 1. The difference is that View0 onTouchEvent returns true and consumes subsequent events, and View0's dispatchTouchEvent also returns true. ViewGroup2nd and ViewGroup1st do not execute onTouchEvent and also directly return true.

Then move your finger slightly, and the move event is passed down

  1. 12-30 14:14:09.484 7350-7350/lyn.demo D/ViewGroup1st: dispatchTouchEvent before12-30 14:14:09.484 7350-7350/lyn.demo D/ViewGroup1st: onInterceptTouchEvent return :false12-30 14:14:09.484 7350-7350/lyn.demo D/ViewGroup2nd: dispatchTouchEvent before12-30 14:14:09.484 7350-7350/lyn.demo D/ViewGroup2nd: onInterceptTouchEvent return :false12-30 14:14:09.484 7350-7350/lyn.demo D/View0: dispatchTouchEvent before12-30 14:14:09.484 7350-7350/lyn.demo D/View0: onTouchEvent return :true12-30 14:14:09.484 7350-7350/lyn.demo D/View0: dispatchTouchEvent return :true12-30 14:14:09.484 7350-7350/lyn.demo D/ViewGroup2nd: dispatchTouchEvent return : true12-30 14:14:09.484 7350-7350/lyn.demo D/ViewGroup1st: dispatchTouchEvent return : true  

The process is the same as the transmission of the down event. Because it will also pass through ViewGroup2nd's onInterceptTouchEvent, if ViewGroup2nd has an interception behavior at this time, the move event will not be transmitted to View0. To avoid this situation, you need to call View0's requestDisallowInterceptTouchEvent, as shown in Section 1.4.

2.3. Case 3: ViewGroup2nd consumes down event

First, the down event is passed, the log is as follows

  1. 12-30 14:25:30.074 18848-18848/lyn.demo D/ViewGroup1st: dispatchTouchEvent before12-30 14:25:30.074 18848-18848/lyn.demo D/ViewGroup1st: onInterceptTouchEvent return :false12-30 14:25:30.084 18848-18848/lyn.demo D/ViewGroup2nd: dispatchTouchEvent before12-30 14:25:30.084 18848-18848/lyn.demo D/ViewGroup2nd: onInterceptTouchEvent return :false12-30 14:25:30.084 18848-18848/lyn.demo D/View0: dispatchTouchEvent before12-30 14:25:30.084 18848-18848/lyn.demo D/View0: onTouchEvent return :false12-30 14:25:30.084 18848-18848/lyn.demo D/View0: dispatchTouchEvent return :false12-30 14:25:30.084 18848-18848/lyn.demo D/ViewGroup2nd: onTouchEvent return :true12-30 14:25:30.084 18848-18848/lyn.demo D/ViewGroup2nd: dispatchTouchEvent return :true12-30 14:25:30.084 18848-18848/lyn.demo D/ViewGroup1st: dispatchTouchEvent return : true  

Since View0 does not consume events, dispatchTouchEvent returns false, so the onTouchEvent method of ViewGroup2nd is executed.

ViewGroup2nd consumes the event, onTouchEvent returns true, and then dispatchTouchEvent of ViewGroup2nd and ViewGroup1st both return true.

Move your finger, and the move event will be passed on.

  1. 2-30 14:25:30.174 18848-18848/lyn.demo D/ViewGroup1st: dispatchTouchEvent before12-30 14:25:30.174 18848-18848/lyn.demo D/ViewGroup1st: onInterceptTouchEvent return :false12-30 14:25:30.174 18848-18848/lyn.demo D/ViewGroup2nd: dispatchTouchEvent before12-30 14:25:30.174 18848-18848/lyn.demo D/ViewGroup2nd: onTouchEvent return :true12-30 14:25:30.174 18848-18848/lyn.demo D/ViewGroup2nd: dispatchTouchEvent return : true12-30 14:25:30.174 18848-18848/lyn.demo D/ViewGroup1st: dispatchTouchEvent return : true  

At this time, ViewGroup2nd directly intercepts the move event, no longer passes through onInterceptTouchEvent, and no longer distributes it to View0, but directly calls onTouchEvent for processing.

3. Down event distribution diagram

When each View does not intercept the down event, the down event is transmitted like this:

<<:  Research and practice of Android unit testing

>>:  Learn common Mac commands to help iOS development

Recommend

What are the requirements for adding keywords to Baidu’s bidding promotion?

What are the requirements for adding keywords to ...

Operational strategy: How to effectively recall lost users?

User churn is a very troubling problem for operat...

Why does iced cola taste better when eating hot pot?

In the cold winter, nothing can warm people up be...

Google Talk, once a favorite among programmers, will be shut down next week

Google Talk, which was once popular, has begun to...