In order to write this article, I repeatedly read dozens of times the source code. And write the time interval is long, sometimes write to write their own chaos, and go to see the source code to analyze, so may repeat the content more will be a little messy, but I believe you follow the source code and this article step by step, should still have a harvest!
This article will introduce how the view event is transmitted and distributed, as well as the causes and solutions of click and slide conflicts. These will be solved by reading the source code ~
Some basics
##MotionEvent When a finger touches the screen, ActionDown will be triggered once, then ActionMove will be triggered once or more, and finally ActionUp will be triggered once
Event distribution, interception, consumption
This is the existence of the three methods in activity, viewGroup and View, and this article has been focusing on the interpretation of these three methods in different situations
Crude event distribution chart
You’re stacking views here, because views are stacked on the screen
The onTouch and onClick
Start with a simple piece of code that sets the onTouch and onClick events to the button. We know that onClick is going to execute when the onTouch event returns false and onClick is not going to execute when the onTouch event returns true and we all know that onClick is not going to execute if onTouch intercepts it. But how do these two simple pieces of code appear in the source code?
Button is a View, so we can directly enter the View source code and see its dispatchTouchEvent distribution method. We can know that the result of the first if judgment will affect the execution of the second if statement
First saw our onTouch event on the first if
if (li ! = null && li.mOnTouchListener ! = null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; }Copy the code
Let’s break down the judgment in the if condition
1.li ! = null && li.mOnTouchListener ! = null
So the first thing we can see is li equals mLisenterInfo, what is mLisenterInfo? Let’s go back to the setOnTouchListener method that was called to set the onTouch event, and if we go into view, we can see getListenerInfo()
Then go to getListenerInfo() to check
Li = mLisenterInfo! = null, li.mOnTouchListener! =null (setOnTouchListener assigns the value we passed in)
So the first condition is true
2.(mViewFlags & ENABLED_MASK) == ENABLED
This condition doesn’t need to be read too much, it’s just a judgment about whether you can click
So the second condition is true
3.li.mOnTouchListener.onTouch(this, event)
The method called is the onTouch we set to button
li.mOnTouchListener.onTouch(this, Event) return false --> result = false return true --> result = true result && onTouchEvent(event)) { }Copy the code
3.1 If we return false, then the first if statement is invalidated and cannot be entered. Result is the initial value fasle, so we will execute the second if –>Execute onTouchEvent(Event), event consumption
Then we go to performClick, and we finally see our onClick method called
3.2 If we return true, then the first if statement is invalidated and result is assigned to true so we do not execute the second if –> event consumable onTouchEvent(event) and the onClick method cannot be executed
Look at it from the point of view of the event distribution process
First go to viewGroup#dispatchTouchEvent and analyze a normal Down event
Note:If (! canceled && ! Intercepted) {}
The code block in this if statement is all related to event distribution. It can be said that as long as an if statement is entered, event distribution will be performed, and then the code in the if statement will be analyzed
digression
So let’s look at this first2 comments buildTouchDispatchChildList method
It will eventually execute tobuildOrderedChildList
All childViews are sorted in order of z-axis size, with the smallest in front and the largest in the back
We know that in a layout, all the views are superimposed on top of each other like this, so the smaller the bottom z-axis, the higher up the list. So when you walk through, you also take the last one
Look at the four isTransformedTouchPointInView method in the comments You click is a small circle in the picture, for example, when he traversal for will according to you click on the coordinates, and for the region, and see if it is within the scope of their own, If not, continue through the next childView
Look at theNewTouchTarget = getTouchTarget(child);
End of digression
If you go further down, you’ll enterdispatchTransformedTouchEvent
Because the child! = null, else, and then child.dispatchTouchEvent(transformedEvent) is called
In the above case, Button is child, so the event is distributed to Button for subsequent operations
We don’t override dispatchTouchEvent in the button, so we just go to dispatchTouchEvent in the View
So we’re back to where we started
As you can see, it returns true if the button handles or consumes an event or returns true on onTouch (which counts as processing). , in turn,The result of child.dispatchTouchEvent(transformedEvent) is true
Then we go back to the previous method, the if statement will be hit, and then it will enter
The if statement breaks and exits the for loop. The event is handled by the button and not retrieved by any other view or parent view, and the next view or viewgroup event is distributed.
In addition, there are two statements in red boxes that get three conditions, so pay special attention to that
newTouchTarget = mFirstTouchTarget ! = null
mFirstTouchTarget.next = null
alreadyDispatchedToNewTouchTarget = true
I’ve folded up the previous code, just to make it easier to see. The last statement will not hit because of the condition above, and finally the wholeif (! canceled && ! intercepted)
That’s the end of the code block
And then we go down, because mFirstTouchTarget! = null so let’s look at the else block
The dispatchTouchEvent method ends with the handled result return
#### Here the Down event ends
Sliding conflict
The above is just normal down event distribution
Next, use this example to look at the distribution of conflicting events to analyze down and Move events
To start with, the layout looks like this:
Custom BadViewpager, which holds a ListView, listView contains many items, more than one screen
So the viewpager is the parent view and the ListView is the child view
BadViewpager is normally swiped left and right
The ListView normally slides up and down
BadViewpager, rewrittenonInterceptTouchEvent
Method that intercepts the event and returns true(to create a conflict)
If BadViewpager’s onInterceptTouchEvent returns true, intercept the event
Viewpager can slide left or right, but ListView can’t slide up or down
That is, the event is sent to the viewPager and is intercepted. Let’s see how the ViewPager intercepts the event and consumes the event itself. Let’s start at the beginning, go back to dispatchTouchEvent for ViewGroup
Note: This is now an ACTION_DOWN event
Because the onInterceptTouchEvent method is overwritten in viewpager, intercepted is true
We know thatif (! canceled && ! intercepted) {}
intercepted is the key code block that distributes events to subviews. If intercepted is true, it means that if statements cannot enter, and events cannot be distributed to subviews.
And mFirstTouchTarget is onif (! canceled && ! intercepted) {}
Canceled is false because there is no cancellation event passing in the null child
So we can see that after entering dispatchTransformedTouchEvent
I’m just calling my dispatchTouchEvent, and I’m just sending the event to my own
Here the ACTION_DOWN event ends
If BadViewpager’s onInterceptTouchEvent returns false, the event is not intercepted
Viewpager can’t slide left or right, but ListView can slide up or down
The ACTION_DOWN event flow is the same as the event distribution of button above, so it will not be analyzed.
So here we have an ACTION_DOWN event,
Execute dispatchTouchEvent again to distribute ACTION_MOVE.
So there are a couple of conditions
newTouchTarget = mFirstTouchTarget ! = null
mFirstTouchTarget.next = null
AlreadyDispatchedToNewTouchTarget = false (note that there are different
Because each execution dispatchTouchEvent alreadyDispatchedToNewTouchTarget will be reset
And alreadyDispatchedToNewTouchTarget field only when distributing child view will be assigned to true
However, as you can see below, the following statement is not executed in a Move event
Here’s the ACTION_MOVE event
So let’s analyze the else statement below
We can see into dispatchTransformedTouchEvent
Here the ACTION_MOVE event ends
Two interception methods and cancel event generation
Let’s start with internal interception
Note that internal intercepts are handled by both child and parent views
Child view
In the parent view (Viewpager)
First look at the getParent (). RequestDisallowInterceptTouchEvent ()
Incoming true mGroupFlags = mGroupFlags | FLAG_DISALLOW_INTERCEPT
If false is passed mGroupFlags = mGroupFlags & FLAG_DISALLOW_INTERCEPT
So at dispatchTouchEvent (mGroupFlags & FLAG_DISALLOW_INTERCEPT)
When true is passed, the result is! =0, disallowIntercept is true, intercepted is false, and subsequent events are distributed as normal
When false is passed in, the result is =0, disallowIntercept is false, intercepted depends on onInterceptTouchEvent
If the parent view does not return true, the child view will not receive the event at all. None of the internally intercepted code is executed. I think that’s just one of the reasons
In this case, the viewgroup distributes events to the ListView, which is also a viewgroup. So it also goes dispatchTouchEvent on the ViewGroup, and that’s where the problem starts. When ACTION_DOWN is distributed, a reset method is performed
So here we’re doing mGroupFlags
When we go back to the dispatchTouchEvent method, mGroup does the operation again, so the final value is mGroupFlags & ~FLAG_DISALLOW_INTERCEPT &FLAG_DISALLOW_INTERCEPT, So this has to be 0
DisallowIntercept is false and intercepted is true if the onInterceptTouchEvent down event is not handled in the parent view
Cancel event generated
So again, we’re going to use this case, we’re going to use internal interception, and when the Down event ends, we’re going to go to the move event and the first move event comes in, it’s the ListView that’s holding the event, and it’s going to execute its own method
intercepted depends on onInterceptTouchEvent, since false will cause disallowIntercept to be false
The ViewGroup returns true, so intercepted is true, which results in the following execution
Perform dispatchTransformedTouchEvent and for mFirstTouchTarget assignment below
As already analyzed, next is null, so mFirstTouchTarget is set to NULL
Enter the dispatchTransformedTouchEvent check,
So we can see that this is canceling the child view event
Okay, so after this move event is done, it’s still a move event, because it’s triggered multiple times, so it’s just mFirstTouchTarget == null, That’s when the parent view gets the event so you can go from sliding up and down on the ListView to sliding left and right on the ViewPager
The process of external interception at the end is not analyzed. The process generated by the external interception is basically the same as the process generated by the cancel event.