preface
Event distribution is a platitude topic, since it is a “cold meal”, then why began to “cold meal” today? To put it bluntly, I overestimated my understanding of the distribution of events.
Here are a few questions:
- 1. Call a View setOnTouchListener and onTouch() returns true
onTouchEvent()
Won’t be responded to? -> The answer is:Method expansion 2Part. - 2, a View
onTouchEvent()
If it returns true, why does its underlying View no longer respond to any event callbacks? -> The answer is:Method expansion 1Part of the - 3, if a ViewGroup is only overwritten
onTouchEvent()
And returns true, then itsonInterceptTouchEvent()
Will it be called back? -> The answer is:1.2. Partial summaryPart. - 4, rewrite
dispatchTouchEvent()
And just return true, what happens? -> The answer is:Method expansion 2Part.
If you can answer these questions clearly, then this article does not need to read, the upper left corner of the X, singing, dancing, Rap, play basketball… Of course, if you are willing to stay for a little bit of advertising, that is also great ~ haha
The body of the
Since it is called event distribution, it is essentially distribution. I’m guessing that when you first get to know this, you’ll be stuck with three methods: dispatchTouchEvent(), onInterceptTouchEvent(), and onTouchEvent(). But I really think the latter two methods complicate matters.
For event distribution, the core is the implementation of dispatchTouchEvent(). OnInterceptTouchEvent () and onTouchEvent() are just interfaces that allow us to participate in the distribution process.
Therefore, the core of this article is to comb through and read the dispatchTouchEvent() method implementation of the ViewGroup and View. Trust me, this article is definitely rewarding
ViewGroup dispatchTouchEvent()
Source code based on API-28
The logic of dispatchTouchEvent() is divided into two parts. The first part focuses on the determination of event consumption objects (Part 1.1). The second half focuses on subsequent distribution of event consumption objects (Part 1.2).
1.1. First assignment of mFirstTouchTarget
This part of the code logic is mainly for:
- 1. Find and record views that hit consumption events
- 2. Distribute DOWN events of View of each layer
public boolean dispatchTouchEvent(MotionEvent ev){
// Remember the mFirstTouchTarget, which is key
if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {/ / if the child View without invoking requestDisallowInterceptTouchEvent (true), then calls itself onInterceptTouchEvent ()
final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
if(! disallowIntercept) { intercepted = onInterceptTouchEvent(ev); }else {
intercepted = false; }}else {
// If it is not a DOWN event and mFirstTouchTarget == NULL, then the View is blocked
intercepted = true;
}
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false; // Notice that the local variable is used
if(! canceled && ! intercepted) {// Omit some code
if (newTouchTarget == null&& childrenCount ! =0) {
/ / traverse the View (the sequence here can rewrite setChildrenDrawingOrderEnabled + getChildDrawingOrder () () the custom order)
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// If the current View is in animation; Or x and y are not in the View area and continue directly
if(! canViewReceivePointerEvents(child) || ! isTransformedTouchPointInView(x, y, child,null)) {
continue;
}
// This method iterates over the TouchTarget, but the initial target needs to be assigned by mFirstTouchTarget, which is null. Specific implementation details can be viewed: Method expansion 4
newTouchTarget = getTouchTarget(child);
if(newTouchTarget ! =null) {
// Multiple operations are ignored for now
break;
}
// The DOWN event must go here because newTouchTarget == null
// This method starts distributing events to other tiers of views, and the return value of this method determines whether to follow if logic.
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// When the child View chooses to consume the event, the following code will be used. The main thing here is to assign newTouchTarget and mFirstTouchTarget. (See method expansion 3 for the logic of this method)
That is, if the code goes here, then mFirstTouchTarget will no longer be null
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true; }}}}// Up to this point is the logic of intercepted bit false
}
Copy the code
1.2. Partial summary
1. Find and record the View that hit the consumption event; 2. Distribute DOWN events of View of each layer. 1. Find and record views that hit consumption events: When DOWN comes to a ViewGroup, it will attempt to distribute if it does not intercept itself. Whether to assign the mFirstTouchTarget (the View that records the hit consumption event) will be determined based on whether the hit View consumes (override onTouchEvent()/onTouch()/ override dispatchTouchEvent()). 2. Distribute DOWN events of View at each layer: This part of the code, we first met dispatchTransformedTouchEvent () method, this method will be called the child or super dispatchTouchEvent (), Finally through the View of onTouchEvent ()/the onTouch () method return value to determine dispatchTransformedTouchEvent () returns a value. So by the time you get the return value, the event has actually been distributed across all views.
At this point if mFirstTouchTarget is not null, then the subsequent MOVE and UP event will retrace the set of processes (if (actionMasked = = MotionEvent. ACTION_DOWN | | mFirstTouchTarget! = null)). Or intercepted is true; Just give it to yourself.
Here to answer your third question, opening through code we can see that as long as mFirstTouchTarget is not null, and child View does not call requestDisallowInterceptTouchEvent (true), The onInterceptTouchEvent() of the current ViewGroup must be called. It has nothing to do with the return value of onTouchEvent().
OnInterceptTouchEvent () will be called if the onInterceptTouchEvent() of the ViewGroup is intercepted. Can I make onInterceptTouchEvent() return true from a ViewGroup if the View consumes a certain number of events? That way, events can be distributed to other views until they are consumed. Is that possible? The answer is no, and why read: Event distribution for extra reading.
1.3 Key to MOVE/UP event distribution
This partial logical DOWN is also triggered, but more often for MOVE/UP distribution
public boolean dispatchTouchEvent(MotionEvent ev){
// This logical analysis takes over the first half
// If mFirstTouchTarget == null, there are two possible ways: one is to find a View that can be hit, and the other is to intercept it directly
if (mFirstTouchTarget == null) {
// The child field is null, that is, the super.dispatchTouchEvent() is sent to the child field.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// mFirstTouchTarget is not null, which means it has a View to distribute
// omit some conditions
TouchTarget target = mFirstTouchTarget;
while(target ! =null) {
/ / use the alreadyDispatchedToNewTouchTarget here, is very simple for the DOWN event, actually distribution had gone again, and for mFirstTouchTarget assigned values, if not filter out the distribution process will go here two times.
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
// Otherwise, MOVE/UP events will be distributed to other views
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
// Cancel logic is not considered for now}}}Copy the code
1.4. Partial summary
This part of the code is small, and logical. There are two main branches, one is to find no consumable View, so send it to yourself, super. DispatchTouchEvent (). Its own onTouchEvent() handler. Otherwise, distribute subsequent events via mFirstTouchTarget.
2. Method expansion
ViewGroup dispatchTouchEvent() “eat”
2.1 Method Expansion 1:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Omit some code
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
// If the event hits a View, the View's dispatchTouchEvent() is called. Of course, if the View is a ViewGroup then this process will continue and the return value will be super.dispatchTouchEvent(event), which is the View's dispatchTouchEvent(see method expansion 2 for this logic).
// What if my current View overwrites dispatchTouchEvent() and returns true? -> Look at method expansion 2 to see
handled = child.dispatchTouchEvent(event);
}
return handled;
}
Copy the code
For this method, once handled returns true, the mFirstTouchTarget can be determined for the ViewGroup’s dispatchTouchEvent(). With mFirstTouchTarget, it means that the consuming View has been identified and there is no need to distribute the event down. Vernacular: The person who carried the blame has been found, and this matter will be traced in disorder. Ha ha ~
2: dispatchTouchEvent() from View
// For the View, the return value of dispatchTouchEvent() depends on the return value of onTouchEvent() and onTouch().
// This also illustrates a serious problem: OnTouchEvent is called within a dispatchTouchEvent of a View, and if we override a View's dispatchTouchEvent and return true, That means methods like onTouchEvent will never get a chance to execute again. (Which brings us to question no. 4.)
public boolean dispatchTouchEvent(MotionEvent event) {
/ / to omit
if (onFilterTouchEventForSecurity(event)) {
/ / to omit
ListenerInfo li = mListenerInfo;
// If the listener is not null and onTouch() returns true, the result local variable will be true. Then the first condition is not satisfied for the following criteria, so onTouchEvent() is no longer called. (Which answers the first question posed in the opening paragraph.)
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;
}
Copy the code
Summarize method deployment method 1 + 2: if we have a View to rewrite the dispatchTouchEvent () and direct return true, so for dispatchTransformedTouchEvent () this method, will directly be true; Otherwise, the View returns onTouchEvent() and onTouch().
2.3 Method Expansion 3:
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
Copy the code
2.3 Method Expansion 4:
private TouchTarget getTouchTarget(@NonNull View child) {
// Since the default value of mFirstTouchTarget is NULL, the first call to this method must return NULL. That is, when DOWN comes, this method returns null.
for(TouchTarget target = mFirstTouchTarget; target ! =null; target = target.next) {
if (target.child == child) {
returntarget; }}return null;
}
Copy the code
3. Event distribution for additional reading
The first clear answer to this question is no. Because we have read the entire source code. When the event has already been consumed by a View, this means that mFirstTouchTarget is not null, and getTouchTarget(Child)“““ will not be null, so the event will not be redistributed. The same sequence of events will only continue to be distributed to the mFirstTouchTarget.
For the current dispatchTouchEvent(). The event has already been consumed by other views. What is done is done. If you want to change onInterceptTouchEvent() to true, there is no way out.
The end of the
This is the end of this article, some friends may ask, about the CANCEL event has not been told! Yeah, why didn’t we talk about it? Because I haven’t seen it yet. If there is a chance, I will fill in the part about CANCEL event.
Don’t worry, let’s first understand the Lao of today’s article.