First, what is an event
Generally speaking of event distribution, when talking about events, I mean the Touch event in Android.
In official terms: When a user touches the screen, the Touch action is called a Touch event.
Since it is the event generated by the user’s touch behavior, the classification of the event is clear:
- The finger just touched the screen
- Swipe your finger across the screen
- Finger off the screen
- Cancellation events for non-human reasons (system triggered, beyond the developer’s control)
Normally, a finger touching the screen triggers a series of clicks:
Second, what is distribution
The name may sound confusing, but the fact is that when touch happens, someone (object) has to deal with it. This is called distribution.
So event distribution is a process in which the user touches who (object) handles the event after it is generated.
In Android, Touch events are handled by Activiy, ViewGroup, and View.
We know that there are three objects to handle events, so since events are handled, there must be a method, just as a solution is needed to solve things.
There are three main ways to handle events in Android:
dispatchEvent()
: Distribution event.OnInteraceTouchEvent()
: Determines whether to intercept.OnTouchEvent()
: Handles click events.
Here’s an example:
In an Internet company, the boss (Activity object) has an idea, so it is impossible for him to implement the idea (also does not exclude the boss is a smooth commander 😋), so he will call the dispatchEvent() method, which is the so-called distribution event.
There are many departments under the boss. For example, the boss of the technical department (ViewGroup object) thinks the task can be handled by himself. He doesn’t send the dispatchEvent to someone under him.
If the head of the technology department (ViewGroup object) thinks he needs to give his subordinates a workout, then the dispatchEvent() method is called and the task continues to be assigned to the bottom person.
Now that the task is being passed on to the best programmer (View object), he can no longer distribute the event, so he has to do what he has to do, which is to call OnTouchEvent() and consume the event.
As can be seen from the above examples:
- The Activity is
distribution
和consumption
The ability of events. - VieGroup has
distribution
、intercept
,consumption
The ability of events. - The View is
consumption
The ability of events.
Also, the direction of event delivery is from the Activity –> ViewGroup –>View direction.
Activity event distribution source code analysis
Go to the dispatchEvent() method of the Actvity class:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
Copy the code
In this method, there are only two if statements and one return value. We need to clarify the overall logic:
-
The first if statement is of little concern; the onUserInteraction() method has not yet been rewritten in development.
-
The second if statement will focus on:
// 1. GetWindow returns the Window object whose only implementation is the PhoneWindow class
getWindow().superDispatchTouchEvent()
// 2. Indirect call
PhoneWindow.superDispatchTouchEvent()
// 3. This method calls the dispatchTouchEvent of the parent class, and the parent class of the DecorView is FrameLayout
DecorView.superDispatchTouchEvent()
// 4. Final call
ViewGroup.dispatchTouchEvent()
Copy the code
- If the second if statement fails, the Activity consumes the event itself, returning the value of the OnTouchEvent() method, which is returned by default
false
.
The reader can click on the getWindow() method and follow the steps above, eventually calling the ViewGroup dispatchTouchEvent() method.
Here’s another illustration:
To this Activity event distribution source code analysis finished, or very simple, summed up is:
- Activity calls dispatchTouchEvent(), which in turn calls ViewGroup dispatchTouchEvent().
- If true is returned, the ViewGroup handles the event, and the method ends.
- If false is returned, no ViewGroup needs to process the event, and the Activity consumes the event itself.
ViewGroup event distribution source code analysis
Let’s go to the dispatchTouchEvent() method in the ViewGroup class. There’s a lot of code in this method, but let’s go through it step by step:
// The dispatchEvent() method in ViewGroup
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
/ /... Omit the above code
// Indicates whether the event is processed. The default is false
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
}
/ /... Omit code
return handled;
}
Copy the code
As you can see, all of our event processing, are onFilterTouchEventForSecurity (ev), the return value of this method, this method is for the sake of safety rules, used to filter out some special circumstances.
Method returns true by default, false only if the interface is blurry or the click event is blurry. In general, this returns true.
So we will analyze the main things in this method, or the overall grasp of a paragraph, first take a look at the top wave of code:
// 1. Initialize the Down event and clear the operation
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 2. Whether to intercept flag bits. The default is false
final boolean intercepted;
if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {
final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
if(! disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action);// restore action in case it was changed
} else {
intercepted = false; }}else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
Copy the code
In the above pile of code, the initialization operation of down event is carried out first, and then the logic to determine whether to intercept, focusing on the judgment of the intercept code.
If the event is Down or mFirstTouchTarget is not null, then… Otherwise return true.
So what does this variable mFirstTouchTarget mean?
When the event is successfully processed by a child element of the ViewGroup, mFirstTouchTarget is assigned and points to the child element. That is, either a Down event or a child View needs to handle the event, or a ViewGroup needs to intercept the event.
When the first if statement is true, there is also an if statement in which the Boolean disallowIntercept is false by default. When we are in the View of dispatchToucheEvent () method, rewrite the parent. RequestDisallowingInterceptTouchEvent (true), said the child controls to close the parent control interception function, DisallowIntercept is true, ViewGroup does not intercept events.
It is important to note that requestDisallowingInterceptTouchEvent (true) this method can only be intercepted besides Donw outside events, because ViewGroup in performing dispatchTouchEvent (), FLAG_DISALLOW_INTERCEPT is cleared.
If we don’t override this method, disallowIntercept with a value of false will call the ViewGroup onInterceptTouchEvent() method, which returns false by default. If we want to intercept, override this method to return true.
This concludes the source code analysis of ViewGroup intercepting events (unique), so let’s summarize:
-
The outermost if statement determines whether the event is a Down event or if the ViewGroup has a child element to handle the event. If so, it enters the inner if statement. If not, the ViewGroup intercepts the event.
-
The inside of the if statement according to whether the child View to rewrite the requestDisallowingInterceptTouchEvent judging (), rewrite the not intercept, If not, call the onInterceptTouchEvent() method of ViewGroup, which returns false by default. You can override it to return true.
[] children = mChildren; final View[] children = mChildren; This line of code, starting here, loops through the child View in reverse order with some judgment logic, such as this:
if (! child.canReceivePointerEvents() || ! isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; }Copy the code
Or say is if the View is not visible in the animation, or touch no longer within the designated area, will continue to find the next, until we see dispatchTransformedTouchEvent () this approach, This method essentially calls the child View’s dispatchTouchEvent() and dispatches the event to the child View.
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
//....
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());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
/ /...
}
Copy the code
This method mainly depends on the above code, to determine whether the child View is empty, if it is empty, then call the parent class dispatchTouchEvent() method, not the air conditioner child View dispatchTouchEvent() method. At this point the ViewGroup–>View event distribution source analysis is over.
One of the things it doesn’t say is how this mFirstTouchTarget is assigned to the child View, so if you’re interested, you can find out for yourself, newTouchTarget = addTouchTarget(Child, idBitsToAssign); This line of code assigns values and uses a one-way linked list, which I won’t go into here.
Here’s a picture to illustrate:
For ViewGroup event distribution, here’s a summary:
-
The outermost if judgment filters some fuzzy clicks and fuzzy events.
-
Inner logic:
- Initialize the Down event to clear the flag.
- Then determine whether the ViewGroup intercepts, and if there are no child views to process events, consume them. If there are child views to consume events, the dispatchEvent() method of the child View is called.
5. View event distribution source code analysis
Finally we come to the View’s dispatchTouchEvent() method and we find the key code again:
public boolean dispatchTouchEvent(MotionEvent event) {
/ /...
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//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; }}/ /...
}
Copy the code
So this if statement right here is filtering out obscure events just like dispatchTouchEvent in ViewGoup. There are two main things in this logic:
-
- If you design a listener and the control is clickable and the onTouch() method returns true, the event distribution ends.
-
- If condition 1 is not true, the onTouchEvent() method is called.
This if judgment indicates that onTouch() takes precedence over onTouchEvent(). If we override onTouch() in our project and return true, the onTouchEvent() method will not be called again.
The onTouchEvent() method of the View is now available. We have seen so much before, and finally we are in the method of the specific consumption event. This method has more code, so we need to be patient.
In the onTouchEvent() method, we first check whether the View is clickable. If the View is clickable, we go to the switch statement and look at the case MotionEvent.ACTION_DOWN: condition.
The main concern in this condition is the checkForLongClick method, which checks for a long-press event. Into method, to see this one line of code that mPendingCheckForLongPress = new CheckForLongPress (); Click on the CheckForLongPress class, whose run method executes the performLongClick method, all the way to the performLongClickInternal method:
if(li ! =null&& li.mOnLongClickListener ! =null) {
handled = li.mOnLongClickListener.onLongClick(View.this);
}
Copy the code
This is the listener for the long-press event, and if it’s set in the code, it returns true and the event is handled.
After looking at the Down event, look at the move event, in the move event, there is not much code, mainly removes the long press event.
Finally, analyze the up event. In this event, if it is not a long-press event and mPerformClick is not null, then initialize PerformClick() and enter performInternal() in the inner run method. To enter the performClick () :
final boolean result;
final ListenerInfo li = mListenerInfo;
if(li ! =null&& li.mOnClickListener ! =null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
Copy the code
Here is the familiar onClickListener() event listener. If click events are set, return true and event distribution is complete.
If both long-press and click events are set, the long-press event has a higher priority than the click event. If the long-press event returns true, the click event will not be triggered.
Ok, about the View event distribution source code analysis is over, or with a picture to summarize:
6. Overall Flow chart
This concludes the analysis of the source code for Android’s event distribution mechanism. Let’s summarize with an overall flow chart:
As you can see, our event distribution mechanism is a U-shaped structure. The Activity sends it step by step to the child View, and if the child View can’t handle it, it passes it step by step to our Activity.
To sum up:
- Normally, a sequence of events can only be intercepted and consumed by one View.
- ViewGroup does not intercept events by default.
- The View’s onTouchEvent consumes events by default, unless it is unclickable.
- Can pass requestDisallowingInterceptTouchEvent () method to intervene in the parent element distribution of events, except donw events.
7. Common cases of sliding conflict are solved
There are three common sliding conflicts:
- The external sliding direction is consistent with the internal sliding direction.
- The external sliding direction is inconsistent with the internal sliding direction.
- The above two cases are nested.
Steps to resolve sliding conflicts:
- Develop an appropriate sliding strategy.
- Distribute events according to a sliding policy.
No matter how complex the sliding conflict is, there are established rules. Here, a simple one can be judged by the distance difference between the horizontal and vertical directions. If the sliding distance in the vertical direction is large, it is judged as vertical sliding, otherwise, it is horizontal sliding. Of course, depending on the specific business to develop the appropriate sliding strategy.
Here are two general solutions:
- External interception
- Internal interception method
What is external interception? That is, click events are first intercepted by the parent container. If the parent container needs this event, it will be intercepted. If the parent container does not need this event, it will not be intercepted.
Specific methods of external interception:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercepted = false;
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
intercepted = false;
break;
case MotionEvent.ACTION_MOVE:
if(The parent container requires events){intercepted =true;
}else {
intercepted = false;
}
break;
case MotionEvent.ACTION_UP:
intercepted = false
break;
}
return intercepted;
}
Copy the code
The important thing to note here is that the Down event must return false. Once set to true, subsequent move events are not passed to child views.
What is internal interception? That is, the parent container does not intercept any events, and all events are passed to child elements. If the child element needs the event, it consumes it; otherwise, the parent container handles it.
Specific practices:
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
if(the parent container need events) {getParent () requestDisallowInterceptTouchEvent (false);
}
break;
case MotionEvent.ACTION_UP:
break;
}
return super.dispatchTouchEvent(event);
}
Copy the code
In addition, the parent container intercepts events other than down.
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
if(action == MotionEvent.ACTION_DOWN){
return false;
}else {
return true; }}Copy the code
These are the two general methods, as to add specific logic code in the move event, which needs to be combined with the actual needs to write, can not be general, but the above two ideas can be used in any sliding conflict, recommend the use of scheme 1 external interception method.
At the end of the article
The paper come zhongjue shallow, and must know this to practice. Winter Night reading shows the child Yu — Lu You
Well, on the event distribution of the source code analysis and sliding conflict two general ideas on this, you eat happy.