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 ! = nullSo 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 methodIt will eventually execute tobuildOrderedChildListAll 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, rewrittenonInterceptTouchEventMethod 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.