This article will continue to analyze the distribution and handling of events in viewGroups.
ViewGroup event distribution and processing is extremely complex, reflected in the following aspects
- A ViewGroup not only distributes events, but may also truncate and process them.
- for
ACTION_DOWN
.ACTION_MOVE
.ACTION_UP
And evenACTION_CANCEL
Events, there are different handling situations. - The ViewGroup code is also mixed with the handling of multiple fingers.
Given the complexity of the code, this article will break down the different cases in sections, with a diagram showing how the code works at the end.
Due to lack of space, this article will not cover the multi-touch code, because multi-touch is also a difficult area to cover. If I have time later, and if I feel the need, I’ll do another article on ViewGroup handling of multi-finger events.
Handle ACTION_DOWN events
Checks whether the event is truncated
When the ViewGroup detects an ACTION_DOWN event, the first thing it does is check whether the ACTION_DOWN event is truncated.
public boolean dispatchTouchEvent(MotionEvent ev) {
if (onFilterTouchEventForSecurity(ev)) {
// Do some reset actions, including clearing FLAG_DISALLOW_INTERCEPT
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 1. Check whether the event is truncated
final boolean intercepted;
if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {
// Since FLAG_DISALLOW_INTERCEPT was previously cleared, this value is false
final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
if(! disallowIntercept) {// Determine if you truncate
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else{}}else{}}Copy the code
For ACTION_DOWN events, the ViewGroup only uses the onInterceptTouchEvent() method to determine whether to truncate.
We first to analyze the ViewGroup. OnInterceptTouchEvent () returns false, also is not truncated ACTION_DOWN, then to analyze the situation of the truncation.
Do not truncate the ACTION_DOWN event
Find the child View that handles the event
If the ViewGroup does not truncate ACTION_DOWN events, the intercepted value is false. That means that the ViewGroup doesn’t truncate the event anymore, so you have to find a child View to handle the event
public boolean dispatchTouchEvent(MotionEvent ev) {
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// 1. Check whether the event is truncated
// ...
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
/ / not truncated
if(! canceled && ! intercepted) {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;
final int childrenCount = mChildrenCount;
if (newTouchTarget == null&& childrenCount ! =0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Get an ordered set of child Views
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
// 2. Loop through to find a child View that can handle ACTION_DOWN events
// 2.1 Get a child View
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// 2.2 Check whether the child View can handle events
if(! canViewReceivePointerEvents(child) || ! isTransformedTouchPointInView(x, y, child,null)) {
ev.setTargetAccessibilityFocus(false);
// If not, go through the next loop to find the child View
continue;
}
// 3. Distribute events to child views
// ...
}
}
}
}
}
return handled;
}
Copy the code
First, in step 2.1, get a child View. As for how to get a child View, we don’t need to delve into this, if you later encounter the drawing order, and the child View to receive events in the order, you can go back to analyze the order of the child View here.
After obtaining a child View, step 2.2 determines whether the child View meets the criteria for event processing. There are two criteria
- through
canViewReceivePointerEvents()
Determines whether the child View can receive events. The principle is very simple, as long as the View is visible, or as long as the View is animated, then the View can receive events. - through
isTransformedTouchPointInView()
Determines whether the coordinates of the event are in the child View. Its principle can be described simply, first to convert the event coordinates to View space coordinates, and then determine whether the converted coordinates are in the View. This is easy to say, but to explain it requires some knowledge of View scrolling and Matrix, so I’m not going to explain it in detail here.
In step 2.2, if you find a child View that does not have the ability to handle events, then you go through the next loop to find the next child View that can handle events. This step will almost always find the child View, because if we want to use a control, we have to press our finger on it.
Events are distributed to child views
Now that we have a child View that can handle events, we’re going to distribute ACTION_DOWN events to it, and we’re going to look at the results and see if it handles ACTION_DOWN events, so let’s look at the code
public boolean dispatchTouchEvent(MotionEvent ev) {
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// 1. Check whether the event is truncated
// ...
// No cancellation, no truncation
if(! canceled && ! intercepted) {if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
if (newTouchTarget == null&& childrenCount ! =0) {
// Look for a View that can handle the event
for (int i = childrenCount - 1; i >= 0; i--) {
// 2. Find a subview that can handle events
// ...
// 3. Distribute events to child views
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// 3.1 Child View handles the event and gets a TouchTarget object
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
}
}
if (mFirstTouchTarget == null) {}else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while(target ! =null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
// 3.2 Found the child View to handle ACTION_DOWN event, set the result
handled = true;
} else{}}}}// 3.3 Return the result
return handled;
}
Copy the code
Step 3, through dispatchTransformedTouchEvent () method to send events to the View, and through the return value to determine the View of the results
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
final MotionEvent transformedEvent;
// The hand index has not changed
if (newPointerIdBits == oldPointerIdBits) {
// 1. Child has identity matrix
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {}else {
// Convert the event coordinates to the coordinates of the child space
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
// Send the event to child for processing
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
// Return the processing result
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
if (child == null) {}else {
// 2. Handle child without identity matrix
// Convert the event coordinates to the coordinates of the child space
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
// Then convert the converted coordinates through the inverse matrix again
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
// Finally pass to child to handle the event after the coordinate transformation
handled = child.dispatchTouchEvent(transformedEvent);
}
// Return the processing result
return handled;
}
Copy the code
Although the process here is divided into two steps depending on whether the child View has the identity matrix or not, the process here is roughly the same, the event coordinates are first converted, and then handed to the child View’s dispatchTouchEvent() processing.
Can be seen from the dispatchTransformedTouchEvent () implementation, it returns the result is a child View dispatchTouchEvent (). If it returns true, it means that the child View handled ACTION_DOWN, and then it has reached step 3.1, fetching a TouchTarget object by addTouchTarget()
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
// Get a TouchTarget from the object pool
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
// Insert into the head of the list
target.next = mFirstTouchTarget;
// mFirstTouchTarget points to the beginning of the single list
mFirstTouchTarget = target;
return target;
}
Copy the code
Note that mFirstTarget refers to the head of the single linked list, and mFirstTouchTarget.child refers to the child View that handled the ACTION_DOWN event.
This means that the child View of the ACTION_DOWN event has been found and processed. After that, we go to 3.2 and 3.3 and return true.
Let’s use a diagram to illustrate the process of not truncating ACTION_DOWN events
The ViewGroup handles the ACTION_DOWN event itself
The ViewGroup can handle ACTION_DOWN events on its own, and there are two situations that make this possible
- The ViewGroup itself is truncated
ACTION_DOWN
The event - ViewGroup could not be found to process
ACTION_DOWN
The child View of the event
Since the code handles both cases the same way, I’ll put them together, and the code looks like this
public boolean dispatchTouchEvent(MotionEvent ev) {
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Check whether the event is truncated
final boolean intercepted;
if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {
// When ACTION_DOWN, disallowIntercept value is always false
final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
if(! disallowIntercept) {// Returns true to truncate the event
intercepted = onInterceptTouchEvent(ev);
} else{}}else{}// 1. If the ViewGroup truncates the event, go to step 3
if(! canceled && ! intercepted) {if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
if (newTouchTarget == null&& childrenCount ! =0) {
// 2. If all child views do not handle ACTION_DOWN events, go to step 3
for (int i = childrenCount - 1; i >= 0; i--) {
// Find a subview that can handle events
// ...
// View handles events
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
}
}
}
}
}
if (mFirstTouchTarget == null) {
// 3. ViewGroup handles ACTION_DOWN events by itself
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else{}}// 4. Return the processing result
return handled;
}
Copy the code
As you can see from the code, if the ViewGroup truncates an ACTION_DOWN event or can’t find a child View that can handle an ACTION_DOWN event, it eventually gets to step 3, Through dispatchTransformedTouchEvent ACTION_DOWN events to their processing () method, pay attention to the third parameter is null, the incoming said there is not handle events of the View
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
// Hand index unchanged
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
/ / call the dispatchTouchEvent ()
handled = super.dispatchTouchEvent(event);
} else{}// Returns the result of the processing
returnhandled; }}else{}return handled;
}
Copy the code
Simply call the parent View’s diaptchTouchEvent() method, which is passed to the onTouchEvent() method as the View event handler knows from the event distribution.
There’s actually an OnTouchListener loop for View event handling, but you don’t normally set this listener for a ViewGroup, so it’s ignored here.
Can be seen from the whole process of analysis, if ViewGroup processing ACTION_DOWN event, then ViewGroup. DispatchTouchEvent (), the return value is and ViewGroup. OnTouchEvent () returns the same value.
Now we also have a picture of the ViewGroup handling ACTION_DOWN events on its own, and there are two sets of processes, but again I want to emphasize the ViewGroup handling ACTION_DOWN events on its own
- ViewGroup truncation
ACTION_DOWN
The event - ViewGroup could not be found to process
ACTION_DOWN
The child View of the event
Handle ACTION_DOWN summary
The ViewGroup handling of ACTION_DOWN is critical, and it’s always important to remember that it’s going to find mFirstTouchTarget, because mFirstTouchTarget.child points to the child View that handled the ACTION_DOWN event.
Why mFirstTouchTarget is so critical is that all subsequent events are handled around mFirstTouchTarget, such as passing subsequent events to mFirstTouchTarget.Child.
Handle ACTION_MOVE events
Checks whether the ACTION_MOVE event is truncated
For ACTION_MOVE events, ViewGroup will also determine whether to truncate, as shown in the following code snippet
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// 1. Check whether truncation occurs
final boolean intercepted;
// 1.2 If there is a child View to handle ACTION_DOWN events
if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {
// Determine whether the child View requests that the parent View not be allowed to truncate the event
final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
if(! disallowIntercept) {// Subviews allow truncation of events
// Determine if you truncate
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else { // Subviews are not allowed to truncate events
intercepted = false; }}else {
// truncate ACTION_MOVE event if there is no child ACTION_DOWN View
intercepted = true; }}}Copy the code
As you can see from the code, mFirstTouchTarget becomes the criterion for truncating the ACTION_MOVE event. Now you can see how important ACTION_DOWN event handling is. It directly affects the handling of ACTION_MOVE events, as well as ACTION_UP and ACTION_CANCEL events.
MFirstTouchTarget == NULL (ACTION_DOWN) {ViewGroup == null (ACTION_DOWN) {ViewGroup == null;
Step 1.2, if there is a child View that handles the ACTION_DOWN event, that is, mFirstTouchTarget! = null. Before distributing the event to mFirstTouchTarget.Child, the ViewGroup needs to see if it is truncated, which can happen in two ways
- If the child View allows the parent View to truncate the event, then it passes
onInterceptTouchEvent()
To see if the ViewGroup itself is truncated - If the child View does not allow the parent View to truncate the event, then the ViewGroup must not truncate.
Now, there are two cases where a ViewGroup does not truncate an ACTION_MOVE event
mFirstTouchTarget ! = null
, the child View allows the parent ViewGroup to truncate the event, and the ViewGroup’sonInterceptTouchEvent()
returnfalse
mFirstTouchTarget ! = null
The child View does not allow the parent ViewGroup to truncate the event
So, let’s first analyze the case where the ViewGroup does not truncate the ACTION_MOVE event
Don’t cut ACTION_MOVE
The event is distributed to mFirstTouchTarget.child
If the ViewGroup does not truncate the event, which means mFirstTouchTarget is not null, then the ACTION_MOVE event will be distributed to mFirstTouchTarget.Child. Let’s look at the code
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
// 1. Check whether ACTION_MOVE is truncated
final boolean intercepted;
if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {
final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
if(! disallowIntercept) {1.1 My son allowed me to truncate, but I decided not to truncate
intercepted = onInterceptTouchEvent(ev);
} else {
If my son does not allow it, I will not do it
intercepted = false; }}else{}if(! canceled && ! intercepted) {if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
}
}
if (mFirstTouchTarget == null) {
// Truncate the event
} else {
while(target ! =null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
} else {
CancelChild is false without truncating the event
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
// 3. Pass the event to the child View pointed to by mFirstTouchTarget
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
// ...
}
// ...}}}return handled;
}
Copy the code
ViewGroup truncation ACTION_MOVE events, call dispatchTransformedTouchEvent () the event to mFirstTouchTarget. Chid processing
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
final MotionEvent transformedEvent;
// 1. Child has identity matrix
if (newPointerIdBits == oldPointerIdBits) { // The hand index has not changed
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {}else {
// Convert the event coordinates
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
// Pass the event to child
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
if (child == null) {}// 2. Child no identity matrix
else {
// Convert the event coordinates
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
// Pass the event to child
handled = child.dispatchTouchEvent(transformedEvent);
}
return handled;
}
Copy the code
We can see that in either case, the child.dispatchTouchEvent() method is eventually called to pass the ACTION_MOVE event to the child. That is, the child View that handles the ACTION_DOWN event will eventually receive the ACTION_MOVE event.
Let’s use a diagram to summarize how ViewGroup does not truncate ACTION_MOVE events
Truncation ACTION_MOVE
As you can see from the previous analysis, if a ViewGroup truncates an ACTION_MOVE event, there are two scenarios
mFirstTouchTarget == null
Then the ViewGroup will truncate the event and handle it itself.mFirstTouchTarget ! = null
And the child View is allowed to truncate the event of the ViewGrouponInterceptTouchEvent()
Returns true.
However, the code processing flow in these two cases is different, which undoubtedly makes code analysis more difficult. Let’s look at the first case, without mFirstTouchTarget
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final boolean intercepted;
if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {}else {
// 1. MFirstTouchTarget is null, truncating the event
intercepted = true;
}
if(! canceled && ! intercepted) {if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
}
}
if (mFirstTouchTarget == null) {
// 2. Truncate and give the event to the ViewGroup itself
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// ...}}return handled;
}
Copy the code
From the code you can see, when mFirstTouchTarget = = null, ViewGroup truncation, call dispatchTransformedTouchEvent () method to their processing, this method analysis before, Note that the third argument here is null, which means that there is no child View to handle the event
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// No change in hand index
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
// Call the dispatchTouchEvent() method of the parent View
handled = super.dispatchTouchEvent(event);
} else{}returnhandled; }}Copy the code
It’s a simple call to the parent View’s dispatchTouchEvent() method, which is viewGroup.onTouchEvent (), And ViewGroup. DispatchTouchEvent () returns a value and ViewGroup. OnTouchEvent () is the same.
Now let’s look at the second truncation case, which is mFirstTouchTarget! = null and ViewGroup. OnInterceptTouchEvent () returns true.
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
// Check for truncation
final boolean intercepted;
if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {
final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
if(! disallowIntercept) {// 1. Subviews are allowed to truncate, and viewgroups are also truncated. intercepted is true
intercepted = onInterceptTouchEvent(ev);
} else {
intercepted = false; }}else{}if(! canceled && ! intercepted) {// ...
}
if (mFirstTouchTarget == null) {
// ...
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while(target ! =null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
// ...
} else {
// if intercepted is true, cancelChild is true to cancelChild processing
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
// 2. Send ACTION_CANCEL event to child
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
// Cancel child to handle the event
if (cancelChild) {
if (predecessor == null) {
// 3. Set mFirstTouchTarget to null
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue; } } predecessor = target; target = next; }}}return handled;
}
Copy the code
Step 1, when mFirstTouchTarget! = null, child View allows the parent ViewGroup truncation ACTION_MOVE events, and ViewGroup. OnInterceptTouchEvent () returns true, is also the parent ViewGroup truncation.
Step 2, ViewGroup will still call dispatchTransformedTouchEvent () method sends event mFirstTouchTarget, only this time mFisrtTouchTarget received ACTION_CANCEL event, Instead of an ACTION_MOVE event. Note that the second argument cancelChild has a value of true, so let’s look at the implementation
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
final int oldAction = event.getAction();
// Cancel is true
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
// Set the event type to ACTION_CANCEL
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
// Send the ACTION_CANCEL event to child
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
// Return the result of child processing
returnhandled; }}Copy the code
Mfirsttouchtarget. child receives ACTION_CANCEL when ViewGroup truncates the ACTION_MOVE event. Now you know how a View receives an ACTION_CANCEL event!!
After sending the ACTION_CANCEL event to the mFirstTouchTarget, I did the third step, setting the mFirstTouchTarget to NULL. So this is going too far, ViewGroup truncates the ACTION_MOVE event that was mFirstTouchTarget, turns ACTION_MOVE into ACTION_CANCEL and sends mFirstTouchTarget, Finally, disqualify mFirstTouchTarget.child from receiving subsequent events.
Since a large number of ACTION_MOVE events will be generated during sliding, how to handle subsequent ACTION_MOVE events after ViewGroup truncates ACTION_MOVE events? If mFirstTouchTarget == null, call viewGroup.onTouchEvent ().
Now, let’s use another diagram to show how the ViewGroup truncates the ACTION_MOVE event
This graph doesn’t list the results of sending ACTION_CANCEL and doesn’t seem to care about the results of ACTION_CANCEL processing.
Handles ACTION_UP and ACTION_CANCEL events
A View/ViewGroup processes a sequence of events each time, starting with ACTON_DOWN and ending with ACTION_UP/ACTION_CANCEL, with zero or more ACTION_MOVE events in between.
ACTION_UP and ACTION_CANCEL are theoretically one or the other.
ViewGroup handles ACTION_UP and ACTION_CANCEL events in the same way ACTION_MOVE events are handled. You can analyze it again from the source code.
Proper use of requestDisallowInterceptTouchEvent ()
A child View can request that its parent View not allow truncation of events. How can a child View do that
final ViewParent parent = getParent();
if(parent ! =null) {
parent.requestDisallowInterceptTouchEvent(true);
}
Copy the code
Access to the parent View, and calls the requestDisallowInterceptTouchEvent (true) method, which does not allow the parent View truncation.
The parent View is commonly ViewGroup, we take a look at ViewGroup. RequestDisallowInterceptTouchEvent () method of implementation
// ViewGroup.java
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
// FLAG_DISALLOW_INTERCEPT is set
if(disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0)) {
// We're already in this state, assume our ancestors are too
return;
}
// FLAG_DISALLOW_INTERCEPT is set based on the parameter value
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass the request up to the parent View
if(mParent ! =null) { mParent.requestDisallowInterceptTouchEvent(disallowIntercept); }}Copy the code
RequestDisallowInterceptTouchEvent (Boolean disallowIntercept) will decide whether to set up according to the value of the parameter disallowIntercept FLAG_DISALLOW_INTERCEPT tag, Then ask the parent View to do the same thing.
Now, we can imagine one thing, if a child View invokes the getParent. RequestDisallowInterceptTouchEvent (true), All parent views on top of the child View will set a FLAG_DISALLOW_INTERCEPT flag. Once this flag is set, all parent views do not truncate any subsequent events. This method is really overbearing and should be used with caution, otherwise it may affect the functionality of a parent View.
However requestDisallowInterceptTouchEvent () method call are not effective at any time, please see the following code
private void resetTouchState(a) {
// Clear FLAG_DISALLOW_INTERCEPT
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
public boolean dispatchTouchEvent(MotionEvent ev) {
if (onFilterTouchEventForSecurity(ev)) {
ACTION_DOWN Clears FLAG_DISALLOW_INTERCEPT
if (actionMasked == MotionEvent.ACTION_DOWN) {
resetTouchState();
}
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// omit the ACTION_DOWM, ACTION_MOVE, ACTIOON_UP code
// ACTION_CANCEL or ACTION_UP also clears FLAG_DISALLOW_INTERCEPT
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
}
}
return handled;
}
Copy the code
We can see that the FLAG_DISALLOW_INTERCEPT flag is cleared first when an ACTION_DOWN event is processed, which means, Before the child View if the parent View to handle ACTION_DOWN calls the getParent (). RequestDisallowInterceptTouchEvent (true) method, is invalid.
ACTION_UP or ACTION_CANCEL events indicate the termination of the event sequence, and we can see that FLAG_DISALLOW_INTERCEPT is unflagged after ACTION_UP or ACTION_CANCEL events are processed. This is obviously understandable, because when one sequence of events is finished, the state must be restored and the next sequence of events must be processed.
Now, we can now get a corollary, getParent () requestDisallowInterceptTouchEvent (true) after receiving ACTION_DOWN, It is called until ACTION_UP or ACTION_CANCEL events are received. Obviously this method is only for ACTION_MOVE events.
So, when does a subview request that the parent View not truncate the ACTION_MOVE event? Let me use the ViewPager example to give you a sense of what it is.
In the first case, the ViewPager receives the ACTION_MOVE event in onInterceptTouchEvent() and is ready to truncate the ACTION_MOVE event. Before executing the sliding code, Call getParent (). RequestDisallowInterceptTouchEvent (true), request the parent View does not allow truncation subsequent ACTION_MOVE events. Why make this request to the parent View? Since the ViewPager has already started sliding with ACTION_MOVE, it doesn’t make sense for the parent View to truncate the ViewPager’s ACTION_MOVE.
The second case is that the ViewPager is still in the sliding state after the finger is quickly sliding and lifted. At this time, if the finger is pressed again, the ViewPager thinks that this is an action to terminate the current sliding and start sliding again. So the ViewPager will ask the parent View not to truncate the ACTION_MOVE event because it will start sliding with the ACTION_MOVE immediately.
If you can understand these two articles, you should have no problem analyzing ViewPager.
One conclusion from both cases is that if the current control is about to use ACTION_MOVE to perform some ongoing action, such as sliding, it can ask the parent View not to allow subsequent ACTION_MOVE events to be truncated.
conclusion
The article is very long, but each process has been clearly analyzed. However, in practice, whether it is custom View event processing or event conflict resolution, we tend to feel intimidated and confused. I now summarize the key points of this article, which I hope you will keep in mind in your practical application
- Be sure to know
ViewGroup.dispatchTouchEvent()
When to returntrue
, when to returnfalse
. Returned because the event was processedtrue
Is returned because no event was handledfalse
. - To deal with
ACTION_DOWN
When, there is a key variable, namelymFirstTouchTarget
, be sure to remember that only in consumption outACTION_DOWN
Events have values. ACTION_MOVE
Under normal circumstances it would be transmittedmFirstTouchTarget.child
If truncated by the ViewGroup, it will receiveACTION_MOVE
intoACTION_CANCEL
Event sent tomFirstTouchTarget.child
And themFirstTouchTarget
Empty, subsequentACTION_MOVE
The event will be passed to the ViewGrouponTouchEvent()
.ACTION_UP
.ACTION_CANCEL
Event processing flow andACTION_MOVE
The same.- Note that the child View request does not allow the parent View to truncate the call timing.