See many events distribution articles, feel a little deficient, today this article will start with the scene and finally source code. Analyse the distribution of events as deeply as possible to achieve the effect of one article, that is to say, you don't need to look at the second article, most of the scenes here are from "Possibly the best article to disseminate on Android events" Come on, and then cover it with source code. How can programmers just remember the conclusion!!!
I. objectives
Today's goal is also clear, in order to enable you to fully grasp the event distribution mechanism, today's only goal is to use scenarios to cover all source code, from easy to difficult step by step. So:
1. Completely grasp the event distribution mechanism, remember is complete!!!
2. Can prepare for the following examples, so we must learn well before we can not be confused.
II. Event distribution
1. Basic assumptions (citing scenario articles):
We only consider the four most important touch events: DOWN,MOVE,UP and CNCEL. A gesture is a sequence of events, starting with a DOWN event (generated when the user touches the screen), followed by zero or more MOVE events (generated when the user moves his finger around), and finally with a single UP or CANCEL event (generated when the user's finger leaves the screen or when the system tells you that gesture ends for other reasons). When we refer to the "gesture remainder" we refer to the subsequent MOVE event and the final UP or CANCEL event of the gesture.
_Here I also don't consider multi-touch gestures (assuming only one finger) and ignore the fact that multiple MOVE events can be grouped together.
_The view level we are going to discuss is as follows: the outermost layer is a ViewGroup A, which contains one or more children, one of which is ViewGroup B, and the ViewGroup B contains one or more children, one of which is View C, and C is not a ViewGroup. Here we ignore the possible overlap between views at the same level.

Assuming that the user first touches a point on the screen that is marked as a touch point, the DOWN event occurs at that point. Then the user moves his finger and finally leaves the screen. Whether or not the finger leaves the area of C is irrelevant in the process. The key is where gesture begins.
2. Scene 1
Assuming that neither A, B or C above overrides the default event propagation behavior, the following is the process of event propagation:
DOWN events are passed to C. Since C does not listen to onTouchListener's onTouch method, it goes to C's onTouchEvent event, which returns false, indicating "I don't care about the gesture".
So the event is passed to the onTouch method in B's onTouchListener, which is passed to onTouchEvent because there is no listening method. This method returns false, so it means "I don't care about the gesture either."
3. Similarly, because B does not care about the gesture, DOWN events are passed to the onTouchEvent method of A, which also returns false.
Since no view cares about gesture, they will no longer receive anything from the "gesture remainder".
2.1 Scenario-Source Analysis:
From Scene One we know that this is a complete transfer process. We should first make it clear that the source of all events starts with the dispatch TouchEvent event of ViewGroup, that is to say, the dispatch TouchEvent event of ViewGroup, so we start with this method (the source code of other scenario analysis will also be used here, where each comment is numbered, then I will say how much comment should correspond to this code):
@Override public boolean dispatchTouchEvent(MotionEvent ev) { ........ boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; // Handle an initial down. //1. In this place, our event begins with initialization, i.e. clearing the response control and resetting the touch state. if (actionMasked == MotionEvent.ACTION_DOWN) { cancelAndClearTouchTargets(ev); resetTouchState(); } final boolean intercepted; //2. If our click event is ACTION_DOWN or the response control is not empty and //RequDisallow InterceptTouchEvent (false) enters the interception event //onInterceptTouchEvent, otherwise it will skip the interception directly, that is to say it has already been //intercepted = true if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { intercepted = false; } } else { intercepted = true; } if (intercepted || mFirstTouchTarget != null) { ev.setTargetAccessibilityFocus(false); } // Check for cancelation. final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; // Update list of touch targets for pointer down, if needed. final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; //3. Used to record the latest response controls TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; //4. Enter this method if not cancel and view group uncovered events if (!canceled && !intercepted) { View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus() ? findChildWithAccessibilityFocus() : null; //5. If the event is ACTION_DOWN, ACTION_POINTER_DOWN, ACTION_HOVER_MOVE, then enter the judgment. if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { final int actionIndex = ev.getActionIndex(); // always 0 for down final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) : TouchTarget.ALL_POINTER_IDS; removePointersFromTouchTargets(idBitsToAssign); final int childrenCount = mChildrenCount; //6. If there is no response control and there is a sub-view under ViewGroup if (newTouchTarget == null && childrenCount != 0) { final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); //7. This ArrayList is a sort of sub-view hierarchy, i.e. the hierarchical relationship of sub-view distribution. final ArrayList<View> preorderedList = buildTouchDispatchChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; //8. Traversing through all subviews for (int i = childrenCount - 1; i >= 0; i--) { //9. Get subviews from arraylist first final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex); ........ //10. Find this sub-view from the response object list and assign values as soon as they are found newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { newTouchTarget.pointerIdBits |= idBitsToAssign; break; } resetCancelNextUpFlag(child); //11. The dispatch Transformed TouchEvent method is mainly to look at //If child is not empty, call his dispatchTouchEvent method, and if true is returned, enter if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // Child wants to receive touch within its bounds. mLastTouchDownTime = ev.getDownTime(); if (preorderedList != null) { // childIndex points into presorted list, find original index for (int j = 0; j < childrenCount; j++) { if (children[childIndex] == mChildren[j]) { mLastTouchDownIndex = j; break; } } } else { mLastTouchDownIndex = childIndex; } mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); //12. Add this child to the list of response objects, where the head is inserted into the list and mFirstTouchTarget is assigned newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } // The accessibility focus didn't handle the event, so clear // the flag and do a normal dispatch to all children. ev.setTargetAccessibilityFocus(false); } if (preorderedList != null) preorderedList.clear(); } if (newTouchTarget == null && mFirstTouchTarget != null) { // Did not find a child to receive the event. // Assign the pointer to the least recently added target. newTouchTarget = mFirstTouchTarget; while (newTouchTarget.next != null) { newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |= idBitsToAssign; } } } // Dispatch to touch targets. if (mFirstTouchTarget == null) { //13. If mFirstTouchTarget is empty, enter here. Notice that the child is empty here. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { //14. If it's not empty, it means that the previous DOWN event has been responded to, so it will enter here. TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } } ......... return handled; }
This method is very long and I have omitted it very much, but these methods are very important, but don't worry, except for the comments in the code, I will explain them here.
The DOWN event in our scenario 1 first enters where we first see the first comment:
//1. In this place, our event begins with initialization, i.e. clearing the response control and resetting the touch state. if (actionMasked == MotionEvent.ACTION_DOWN) { cancelAndClearTouchTargets(ev); resetTouchState(); }
As explained in the commentary, because the event is DOWN, it is a new event, so it is necessary to clear some records of the original response control, and reset the touch state that is initialization, back to the initial state. Then we see the next key point:
//2. If our click event is ACTION_DOWN or the response control is not empty and //RequDisallow InterceptTouchEvent (false) enters the interception event //onInterceptTouchEvent, otherwise it will skip the interception directly, that is to say it has already been //intercepted = true if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { intercepted = false; } } else { intercepted = true; }
Our scenario event passes here, because we do not override the default event propagation behavior, so our onInterceptTouchEvent returns false without intercepting the event, and our event passes down:
//4. Enter this method if not cancel and view group uncovered events if (!canceled && !intercepted) { }
We see that the event will be judged when it is passed here, because canceled is false and we have not intercepted so intercepted is false, so this event is valid. Come into this judgment:
//5. If the event is ACTION_DOWN, ACTION_POINTER_DOWN, ACTION_HOVER_MOVE, then enter the judgment. if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { }
Here, because we are a judgment, because our event is DOWN, so our event is also valid, our method will also enter, so proceed to the following code: DOWN, DOWN, DOWN, DOWN, DOWN, DOWN, DOWN, DOWN, DOWN, DOWN, DOWN, DOWN.
//6. If there is no response control and there is a sub-view under ViewGroup if (newTouchTarget == null && childrenCount != 0) { }
This judgment mainly depends on whether the new TouchTarget = null or not, because it is a DOWN event, indicating that this is a new event has not yet responded to the object, so the new TouchTarget is null is valid, and then look at childrenCount!= 0? In this place, our view is not empty, let's look at the basic assumption that the picture can be known. So we will enter into this judgment again:
//7. This ArrayList is a sort of sub-view hierarchy, i.e. the hierarchical relationship of sub-view distribution. final ArrayList<View> preorderedList = buildTouchDispatchChildList();
What is this method for? As you can see from the method name, this method sorts our views according to the hierarchical structure, and then adds them to the ArrayList. So our views have a hierarchical relationship. Then we call it to traverse all views:
//8. Traversing through all subviews for (int i = childrenCount - 1; i >= 0; i--) { }
Our place is to operate on the view hierarchy one by one. First of all, we are:
//9. Get subviews from arraylist first final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
You can see here that we get the sub-view corresponding to this i, that is, our first view:
//10. Find this sub-view from the response object list and assign values as soon as they are found newTouchTarget = getTouchTarget(child);
This place finds the corresponding view from the list and assigns it to the new TouchTarget, which means that our view will respond to events.
//11. The dispatch Transformed TouchEvent method is mainly to look at //If child is not empty, call his dispatchTouchEvent method, and if true is returned, enter if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { }
This method is very important. Let's see what this method does.
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; final int oldAction = event.getAction(); //If the cancel event is received, the child is directly judged to be null or not, and if it is null, the dispatchTouchEvent of the parent class of ViewGroup is directly invoked, or the dispatchTouchEvent of the child View is invoked. 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); return handled; } //Eliminate some multi-touch content .......... // Perform any necessary transformations and dispatch. if (child == null) { handled = super.dispatchTouchEvent(transformedEvent); } else { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; transformedEvent.offsetLocation(offsetX, offsetY); if (! child.hasIdentityMatrix()) { transformedEvent.transform(child.getInverseMatrix()); } //Call dispatchTouchEvent of child if it is not empty handled = child.dispatchTouchEvent(transformedEvent); } // Done. transformedEvent.recycle(); return handled; }
This method looks down and scratches out the details. In fact, this method is to see if there are children. If there are children, it calls dispatch TouchEvent of sub-View directly, so that event distribution goes on. This is a recursive process. The order of invocation here is: the root ViewGroup, which distributes the intermediate ViewGroup, is distributed to the target View and then returned. So the dispatchTouchEvent method for event distribution to View C:
public boolean dispatchTouchEvent(MotionEvent event) { ......... //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; 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; }
We can see that the dispatch TouchEvent () method in View is not complicated. First, look at the method of li. mOnTouchListener. onTouch (this, event). This method is actually added when our control addTouchListener listener is added. If the onTouch in this listener returns true, we will not call the following onTouchEvent() method, because scenario 1 does not override silence. Recognize the method, so the li. mOnTouchListener. onTouch (this, event) defaults to return false, so we will go to the onTouchEvent() method. Here's onTouchEvent. Let's not talk about the specific content of the onTouchEvent. (In this case, the performClick() method is called when the UP event occurs, which is actually the onClick method that is called.) We know that the onTouchEvent of View C in Scenario 1 returns false, so we ask B, B and C to return false, so DOWN events are not consumed, so dispatch Transformed TouchEvent () returns false. So we will go to the following judgment:
// Dispatch to touch targets. if (mFirstTouchTarget == null) { //13. If mFirstTouchTarget is empty, enter here. Notice that the child is empty here. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { //14. If it's not empty, it means that the previous DOWN event has been responded to, so it will enter here. TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } }
Because we don't have a corresponding DOWN event in front of us, mFirstTouchTarget = null is true, so let's look at comment 13:
// Dispatch to touch targets. if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); }
That is to call the dispatchTransformedTouchEvent method directly with the parameter child==null. This method we saw earlier that if child== null calls super.dispatchTouchEvent(transformedEvent), which is passed to the parent class, it means that I do not consume this event.
So the DOWN of Scenario 1 is finished, and then "the remaining gesture events come" (MOVE,UP,CANCEL), because it is not DOWN events, our program will skip comment 2 directly, comment 5 will come directly to our if (mFirstTouchTarget == null) (comment 13, comment 14) judgment, that is, we will directly call the dispatch Transformed TouchEvent (ev, canceled, null, null).
TouchTarget.ALL_POINTER_IDS; because the third parameter child==null, our event will be passed directly to the parent class, and will no longer be passed down C,B,A. That is to say, because no view cares about gesture, they will no longer receive anything from the "gesture remainder".
3. scenario two
Now let's assume that C actually cares about gesture, probably because C is set to clickable or you override C's onTouchEvent method.
DOWN events are passed to C's onTouchEvent method, which can do whatever it wants to do, and finally return true.
_2. Because C says it is processing the gesture, DOWN events will no longer be passed to onTouchEvent methods of B and A.
3. Because C says that it is processing the gesture, the event of "the remainder of the gesture" will also be passed to C's onTouchEvent method, which returns true or false is irrelevant, but for consistency it is better to return true.
3.1 Scenario Binary Source Analysis
We see that compared to the first scenario, this place adds a sign that C cares about the event, that is, C consumes the event, so we are still the same. Starting with the dispatch TouchEvent () of ViewGroup, we initialize and then go into interception:
//2. If our click event is ACTION_DOWN or the response control is not empty and //RequDisallow InterceptTouchEvent (false) enters the interception event //onInterceptTouchEvent, otherwise it will skip the interception directly, that is to say it has already been //intercepted = true if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { intercepted = false; } } else { intercepted = true; }
Because similarly, our intercepted is false, and the other process conditions and scenarios are similar, so we will step by step to our annotations 11 and distribute the events, and then finally to the onTouchEvent method of C, because we know that the onTouchEvent of C in Scenario 2 returns true, so the judgment if at annotation 11 (dispatchT) Ransformed TouchEvent (ev, false, child, idBitsToAssign) returns true. So our process goes to 12 notes:
//12. Add this child to the list of response objects, where the head is inserted into the list and mFirstTouchTarget is assigned newTouchTarget = addTouchTarget(child, idBitsToAssign);
So we add our response object to the list of response objects and assign mFirstTouchTarget, so our mFirstTouchTarget is not null. Then the program break s down directly, which means that the DOWN event will not be passed to B and A. Then we go to comment 13:
// Dispatch to touch targets. if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else{ .... }
Now the mFirstTouchTarget == null is not empty in our place, so our process will go to the else program, let's see what else code does here:
// Dispatch to touch targets, excluding the new touch target if we already // dispatched to it. Cancel touch targets if necessary. TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; }
We see here that we will come to the judgment if (alreadyDispatchedToNewTouchTarget & & target = newTouchTarget), because our target = mFirstTouchTarget and newTouchTarget==mFirstTouchTarget and alreadyDispatchedToNewTouchTarget are true, so we will return handled=true directly here. Here our DOWN events are consumed, and then our "residual gesture events" (MOVE,UP) and so on. Like DOWN events, we also reach the judgment if (dispatch Transformed Touch Event (ev, false, child, idBitsToAssign) at annotation 11, which calls the onTouch Event method of View C. It doesn't matter whether the onTouch Event is up, the motion event returns true or false. Why? Because we have already recorded mFirstTouchTarget in this place. Our scenario two has been explained here. Now we have to upgrade the difficulty.
4. scenario three
Now we will discuss a new method: onInterceptTouchEvent, which only exists in ViewGroup, but not in ordinary View. Before any onTouchEvent of a view is called, its ancestors will first get an opportunity to intercept the event, in other words, they can steal it. In the "Handling Events" section just now, we have omitted this process. Now, let's add it:
DOWN events are passed to onInterceptTouchEvent of A, which returns false to indicate that it does not want to intercept.
DOWN is passed to B's onInterceptTouchEvent, and it does not want to intercept it, so the method also returns false.
Now, the DOWN event is passed to the onTouchEvent method of C, which returns true because it wants to handle gesture s headed by the event.
Now, the next event of the gesture, MOVE, has arrived. Once again, this MOVE event is passed to the onInterceptTouchEvent method of A, which returns false again, as does B.
Then, the MOVE event is passed to the onTouchEvent of C, as in the previous section.
The other events in the "gesture remainder" are handled as above, if the onInterceptTouchEvent methods of A and B continue to return false.
4.1 Scene Three Source Analysis
Because neither Scenario 1 nor Scenario 2 illustrates the onInterceptTouchEvent method, so Scenario 3 illustrates the onInterceptTouchEvent method. Although Scenario 3 adds this method, we see that this place is returned false, so we see the interception code:
//2. If our click event is ACTION_DOWN or the response control is not empty and //RequDisallow InterceptTouchEvent (false) enters the interception event //onInterceptTouchEvent, otherwise it will skip the interception directly, that is to say it has already been //intercepted = true if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { intercepted = false; } } else { intercepted = true; }
Because of this location A,B's onInterceptTouchEvent returns false as the default return false for our previous scenario analysis. So the code flow is actually the same as Scenario 2. If we want to make a difference, let's look directly at the last scenario, which I think has a larger value ratio.

5. scenario four
Now let's go a step further, assuming that B does not intercept DOWN events, but it intercepts the next MOVE events. The reason may be that B is a scrolling view. When a user clicks only in its area, the clicked element should be able to handle the click event. But when the user's finger moves a certain distance, he can no longer see gesture as a click - obviously, the user wants scroll. That's why B takes over gesture.
The following is the order in which events are processed:
DOWN events are passed to the onInterceptTouchEvent methods of A and B in turn, and they all return false because they do not want to intercept at present.
_2. The onTouchEvent method passed the DOWN event to C returns true.
When MOVE events follow, A's onInterceptTouchEvent method still returns false.
_3.B's onInterceptTouchEvent method receives the MOVE event, when B notices that the user's finger movement distance has exceeded a certain threshold (or slop). Therefore, B's onInterceptTouchEvent method decides to return true and take over the gesture's subsequent processing.
Then, the MOVE event will be transformed by the system into a CANCEL event, which will be passed to the onTouchEvent method of C.
Now, here comes another MOVE event, which is passed to the onInterceptTouchEvent method of A. A still does not care about the event, so the onInterceptTouchEvent method continues to return false.
At this point, the MOVE event will no longer be passed to B's onInterceptTouchEvent method, which once returned true, will never be called again. In fact, the MOVE and the "gesture remainder" will be passed to the onTouchEvent method of B (unless A decides to intercept the "gesture remainder".
C will never receive anything from gesture again.
5.1 Scene Four Source Analysis
We saw our big box coming. We saw that in scenario 1, neither A nor B wanted to intercept DOWN, so the DOWN event ended up in View C. The onTouchEvent of View C returned true to indicate that I wanted to consume the event. So the program records mFirstTouchTarget as View C, when the MOVE event comes to the Interception Method of Note 2:
//2. If our click event is ACTION_DOWN or the response control is not empty and //RequDisallow InterceptTouchEvent (false) enters the interception event //onInterceptTouchEvent, otherwise it will skip the interception directly, that is to say it has already been //intercepted = true if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { }
We see that this is not a DOWN event, but at this time mFirstTouchTarget!= null, so we will go into the interception method. A means that we don't want to intercept the event, but B means that you meet my requirements and I want to intercept you. So the event will be blocked by B and returned to true by onInterceptTouchEvent, that is, intercepted to true. So when we go to Note 4:
//4. Enter this method if not cancel and view group uncovered events if (!canceled && !intercepted) { }
This judgment will not pass, that is, the process will not be executed, so that the program will go directly to Note 13 and 14, that is:
// Dispatch to touch targets. if (mFirstTouchTarget == null) { }else{ }
At this point, our mFirstTouchTarget is not empty, so we will go to else's code and judge if (alreadyDispatched to New TouchTarget & & target= new TouchTarget). The target of this judgment is our mFirstTouchTarget, which is View C, but our new TouchTarget is reassigned to null, so if we fail to make this judgment, it will be. Walk into the else method.
final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; }
Because the first cancelChild in this code is a judgment, we know that intercepted is intercepted by B and returned to true, so when we call dispatchTransformed TouchEvent (ev, cancelChild, target. child, target. pointerIdBits), cancelChild is true. Let's see what we have done when we go to this dispatchTransformed TouchEvent method:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; final int oldAction = event.getAction(); //If the cancel event is received, the child is directly judged to be null or not, and if it is null, the dispatchTouchEvent of the parent class of ViewGroup is directly invoked, or the dispatchTouchEvent of the child View is invoked. 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); return handled; } //Eliminate some multi-touch content .......... // Perform any necessary transformations and dispatch. if (child == null) { handled = super.dispatchTouchEvent(transformedEvent); } else { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; transformedEvent.offsetLocation(offsetX, offsetY); if (! child.hasIdentityMatrix()) { transformedEvent.transform(child.getInverseMatrix()); } //Call dispatchTouchEvent of child if it is not empty handled = child.dispatchTouchEvent(transformedEvent); } // Done. transformedEvent.recycle(); return handled; }
We see the first comment, judging if (cancel | oldAction == MotionEvent. ACTION_CANCEL) because the incoming cancel is true, so we set the event to CANCEL and then call dispatch TouchEvent to the child view, which passes the CANCEL event loop to the child view. That is, the MOVE event mentioned in Scenario 4 will be transformed into a CANCEL event by the system, which will be passed to the onTouchEvent method of C. Then let's look at the code above:
if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; }
Because our cancelChild is true, the code will come in. Because the code is executed in a loop, the mFirstTouchTarget will eventually be set to null.
So what if there's another MOVE event? What is our process? First of all, we know that this MOVE event also arrives at the interception part first. Let's review this method again.
//2. If our click event is ACTION_DOWN or the response control is not empty and //RequDisallow InterceptTouchEvent (false) enters the interception event //onInterceptTouchEvent, otherwise it will skip the interception directly, that is to say it has already been //intercepted = true if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { intercepted = false; } } else { intercepted = true; }
Now let's look at the judgment that actionMasked = MotionEvent. ACTION_DOWN must be false because we are now a MOVE event. If mFirstTouchTarget is set to null above, it must be true, so we jump directly to the intercepted = true part. That is, the onInterceptTouchEvent method is no longer called. The program arrives at the judgment if (! Canceled & & intercepted) in Note 4. Because intercepted is true, this method is skipped. The program jumps directly to comment 13, 14:
if (mFirstTouchTarget == null) { ..... }else{ .... }
At this point we see that our mFirstTouchTarget == null is true, so our code calls if to determine that the program inside will eventually call the handled = super. dispatchTouchEvent (transformed Event) method. This method calls the onTouchEvent method in B. That is, the MOVE and the "gesture remainder" mentioned at the end of Scenario 4 will be passed to the onTouchEvent method of B (unless A decides to intercept the "gesture remainder". C will never receive anything from gesture again. Here we have finished the distribution of our events. In fact, the process is quite simple. As long as you read the source code carefully and combine the scene, the problem will be solved.
Summary: Event distribution is complex, it is because the previous explanation is not very thorough, leading to people feel very troubled, but as long as we carefully go down to find that it is still very clear, learned the event distribution, then we can explain many phenomena, and then look forward to the next event distribution practice.
