The source code presented in this article is based on 8.0
preface
Event distribution mechanism is not only a core knowledge point but also a difficult point, and it is also the theoretical basis of sliding conflict solution, so it is very important to master the event distribution mechanism of View.
1. Basic understanding
1. Object to which events are distributed
Events are distributed to Touch events, which are generated when the user touches the screen.
There are four types of events, as follows:
type | instructions |
---|---|
MotionEvent.ACTION_DOWN | The finger just touches the screen, generally is the beginning of the event |
MotionEvent.ACTION_MOVE | When a finger moves on the screen, multiple move events are generated |
MotionEvent.ACTION_UP | The moment your finger is released from the screen |
MotionEvent.ACTION_CANCEL | End the event, no human cause |
Same event sequence: refers to a series of events generated in this process from the moment when the finger touches the screen to the moment when the finger leaves the screen. This sequence generally starts with a Down event, contains multiple move events in the middle, and finally ends with an up event
2. The nature of event distribution
The essence of event distribution is the whole process of passing a click event to a specific View
3. Order of event distribution
The order of event delivery: Activity->Window->DecorView->ViewGroup->View. When a click event occurs, it is always passed first to the current Activity, then through the Window to the DecorView, then to the ViewGroup, and finally to the View.
The order of event distribution is Activity->Window->View in Development Art Quest, and Activity->ViewGroup->View in some blogs, but it is the same (see source code below). Window is an abstract class whose only implementation is PhoneWindow, which passes events directly to a DecorView that descends from FrameLayout, which in turn subclasses ViewGroup. So you can also end up thinking that the implementation of Window event distribution is actually implemented by the ViewGroup. So you can also think of the order of event delivery as: Activity->ViewGroup->View.
Second, core methods
View’s event distribution mechanism is logically controlled by event distribution -> event interception -> event processing, which happens to correspond to the three core methods
Event distribution: dispatchTouchEvent
Used to distribute events, which must be called if the event can be passed to the current View. The return result is affected by the current View’s onTouchEvent and its child’s dispatchTouchEvent, indicating whether the current event is consumed.
Public Boolean dispatchTouchEvent(MotionEvent ev)
return:
- Ture: The current View consumes all events
- False: Stop distribution and hand over to the upper-layer control’s onTouchEvent method for consumption. If this layer control is an Activity, the event will be consumed and processed by the system
2. Event interceptor: onInterceptTouchEvent
Note that only viewGroups have this method in activities, ViewGroups, and Views. So once a click event is passed to the View, the View’s onTouchEvent method is called
Used within dispatchTouch Event to determine whether to intercept events. If the current View intercepts an event, the other methods of that sequence of events are handled by the current View, so the method is not called again because it is no longer asked whether to intercept the event.
Public Boolean onInterceptTouchEvent(MotionEvent ev)
return:
- Ture: Intercepts the event and passes it to this layer’s onTouchEvent for processing
- False: not intercepted, sent to the child View for processing by the child View’s dispatchTouchEvent
- Super. OnInterceptTouchEvent (ev) : the default don’t intercept
3. Event handling: onTouchEvent
If not, the current View cannot receive any more events in the same sequence of events and the event will be reassigned to its parent, that is, the parent’s onTouchEvent will be called
Public Boolean onTouchEvent(MotionEvent ev)
return:
- True: the current event is consumed after onTouchEvent processing
- False: does not respond to the event, continuously passed to the upper level onTouchEvent method processing, until a View’s onTouchEvent returns true, the event is considered to be consumed, if the top-level View still returns false, the event is not consumed. This is handled by the Activity’s onTouchEvent.
- Super.ontouchevent (EV): Consumes the current event by default, consistent with returning true.
3. Event distribution mechanism
When analyzing the event distribution mechanism, the sequence of events should be dissected step by step. DecorView->ViewGroup->View. Since Windows and DecorView can be thought of as an Activity->ViewGroup process, the event distribution mechanism is analyzed from the source code in three parts:
- Activity’s distribution mechanism for click events
- ViewGroup distribution mechanism for click events
- View’s distribution mechanism for click events
1. Activity event distribution mechanism
We know that when a click event occurs, the event is always delivered first to the current Activity, which is delivered by the Activity’s dispatchTouchEvent. The Activity passes the event to the Window object for distribution, which is passed to the DecorView. Here is a source code analysis to verify this process:
Source: Activity# dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
// Click events usually start with press events, so always true
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
// If the Activity Window's dispatchTouchEvent returns true
. / / the Activity dispatchTouchEvent returns true, click on the event passed down to stop
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
// If the Window's dispatchTouchEvent returns false, click the event to pass to activity.onTouchEvent
return onTouchEvent(ev);
}
Copy the code
OnUserInteraction () is performed first when the click event occurs; What is this method? No hurry. We’ll follow it.
// Empty method, which is triggered when the Activity is at the top of the stack by tapping the screen on home, Back,menu
public void onUserInteraction(a) {}Copy the code
It can be seen from the source code that this method is empty in the Activity. When the Activity is at the top of the stack, the touch screen clicking on home,back,menu will trigger this method, so this method can realize the screensaver function. Let’s go back to the Activity’s dispatchTouchEvent method and call getWindow().superDispatchTouchEvent(EV) to hand the event to the Activity’s attached Window for distribution. Returns true if the final event is consumed, and if the event is unhandled, the Activity calls its own onTouchEvent() method to handle the event.
GetWindow is a Window object. In the source code of Window, we can find that in fact Window is an abstract class. It is obvious that its methods are naturally abstract methods, so we must find out its concrete implementation class.
Source: Window# superDispatchTouchEvent
/** * Abstract base class for a top-level window look and behavior policy. An * instance of this class should be used as the top-level view added to the * window manager. It provides standard UI policies such as a background, title * area, default key processing, etc. * * The only existing implementation of this abstract class is * android.view.PhoneWindow, which you should instantiate when needing a * Window. */
public abstract class Window {...// Abstract methods
public abstract boolean superDispatchTouchEvent(MotionEvent event); . }Copy the code
From the source we can find in front of the Window class annotation is explained, this time will test our English ability Liao! It’s actually pretty easy to understand in general, as long as we focus on the comments in the latter part.
The only existing implementation of this abstract class isandroid.view.PhoneWindow, which you should instantiate when needing a Window.
From here we can see that the only implementation class is PhoneWindow. Without further discussion, let’s look at the implementation of the superDispatchTouchEvent method in PhoneWindow.
Source: PhoneWindow# superDispatchTouchEvent
private DecorView mDecor;
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
Copy the code
From the source code, you can see that PhoneWindow passes the event directly to a DecorView. Who is that DecorView? As shown below.
public class DecorView extends FrameLayout implements RootViewSurfaceTaker.WindowCallbacks {...public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event); }... }@RemoteView
public class FrameLayout extends ViewGroup {... }Copy the code
GetDecorView ().findViewById(Android.r.i.C.ontent).getChildAt(0) This method gets the layout set in the setContentView. DecorView inherits from FrameLayout, which in turn inherits from ViewGroup. In a DecorView, the superDispatchTouchEvent method calls the dispatchTouchEvent class using super. Instead of calling the ViewGroup’s dispatchTouchEvent (FrameLayout does not have a dispatchTouchEvent), the DecorView passes the event to the ViewGroup for processing. In other words, the event has been passed to the top-level View that is set in the Activity via setContentView (the top-level View is usually the ViewGroup).
The flow chart is as follows:
2. Distribution mechanism of ViewGroup events
ViewGroup events are sent from dispatchTouchEvent(), so we will start with this part of the source code analysis, because this method has a lot of code, the following will be posted as needed:
Source: ViewGroup# dispatchTouchEvent
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {...// Check for interception.
final boolean intercepted; // Whether to intercept
/* * When the event is handled by a child of the ViewGroup, mFirstTouchTarget is assigned and refers to the child */
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
ViewGroup determines whether to intercept only on ACTION_DOWN or mFirstTouchTarget! = null. MFirstTouchTarget is only known from the following code. This is what it does: When the event is handled by a child View of the ViewGroup, the mFirstTouchTarget points to that child View. So when an event is intercepted by this ViewGroup, the subclass will not handle the event, so mFirstTouchTarget =null, so when ACTION_MOVE and ACTION_UP events come in, If the judgment condition is false, the onInterceptTouchEvent of the ViewGroup will not be called again, and intercepted is given true. Therefore, all other events in the same sequence will be handled by the ViewGroup by default. We can also find the following statement in the source code:
final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
Copy the code
FLAG_DISALLOW_INTERCEPT is a tag, the tag is through requestDisallowInterceptTouchEvent (Boolean disallowIntercept) methods to set, generally used in child View. If FLAG_DISALLOW_INTERCEPT is set, ViewGroup will not be able to block click events other than ACTION_DOWN. This is because ViewGroup is distributing ACTION_DOWN events. The FLAG_DISALLOW_INTERCEPT bit will be reset. Let’s look at the source code.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
//
cancelAndClearTouchTargets(ev);
resetTouchState(); Flag_disallow_interception WPT is deleted from flag_disallow_interception
}
Copy the code
If the event is ACTION_DOWN, the ViewGroup will always call its onInterceptTouchEvent to ask if it wants to intercept the event.
RequestDisallowInterceptTouchEvent method on ACTION_DOWN other events, and is in the case of not intercept ACTION_DOWN events to be effective.
Now let’s look at the event distribution when ViewGroup no longer intercepts the event, source code as follows:
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) { // Iterate over all child elements of a ViewGroup
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if(childWithAccessibilityFocus ! =null) {
if(childWithAccessibilityFocus ! = child) {continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
/** ** Determines whether a child element can receive a click event: ** If the child element is playing an animation and the click event coordinates fall within the child element's region ** If an element meets both conditions, the event is handed to it to handle **/
if(! canViewReceivePointerEvents(child) || ! isTransformedTouchPointInView(x, y, child,null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if(newTouchTarget ! =null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
/ / dispatchTransformedTouchEvent actual call is dispatchTouchEvent way to child elements
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if(preorderedList ! =null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break; }}}else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
// Record that the ACTION_DOWN event has been processed
alreadyDispatchedToNewTouchTarget = true;
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
Copy the code
As you can see from the above code, when you do not intercept an event, it first iterates through all the children of the ViewGroup and then determines whether the children can receive the click event. This is determined by whether the child element is playing the animation and whether the coordinates of the click event fall within the child element’s region. If find a target children View to deal with the event, call the dispatchTransformedTouchEvent () method. Let’s look at the important implementation logic of this method:
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else{... handled = child.dispatchTouchEvent(event); }Copy the code
You can see that since the child in the above is not equal to null, the dispatchTouchEvent method of the child element is called directly, causing the event to be passed to the child View, and then distribution continues.
You think this is the end of it? If the child element dispatchTouchEvent returns true, the operator will perform the following operations:
newTouchTarget = addTouchTarget(child, idBitsToAssign);
// Record that the ACTION_DOWN event has been processed
alreadyDispatchedToNewTouchTarget = true;
break;
Copy the code
These lines complete the assignment of the mFirstTouchTarget and terminate the traversal of the child elements. If dispatchTouchEvent returns false, then the ViewGroup will distribute the event to the next element (if there are any children). MFirstTouchTarget = addTouchTarget;
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
MFirstTouchTarget = mFirstTouchTarget; mFirstTouchTarget = mFirstTouchTarget; = null.
So now we’re done distributing the ViewGroup round, but we’re not done yet, so what if after going through all the child elements the event isn’t handled properly?
There are two situations where there is no proper treatment:
- A ViewGroup has no child elements
- The child handles the click event, but returns false in dispatchTouchEvent (default returns true, only if you override the View method or return false in onTouchEvent)
Then the ViewGroup will handle the click event itself (and do the same when the ViewGroup intercepts the event).
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}
Copy the code
As you can see at that time or calls for dispatchTransformedTouchEvent method, but this time the third parameter is not a child but null, so they will call the following code:
//dispatchTransformedTouchEvent
handled = super.dispatchTouchEvent(event);
Copy the code
Super is just the dispatchTouchEvent method in the View, so clicking on the event starts sending it to the View.
ViewGroup doesn’t call onTouchEvent, ViewGroup doesn’t override onTouchEvent
The flow chart is as follows:
3. Distribution mechanism of View events
The ViewGroup event distribution mechanism starts with dispatchTouchEvent.
Source: View# dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {...boolean result = false; .// Check whether the window is blocked. If it is blocked, return false. For example, sometimes two views overlap and one of them is blocked.
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
// Check whether mOnTouchListener is set. If onTouchListener is set and the onTouch method returns true,
//则result = true
if(li ! =null&& li.mOnTouchListener ! =null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
// If result = ture, onTouchEvent is not called, so onTouchListener takes precedence over onTouchEvent
if(! result && onTouchEvent(event)) { result =true; }}...return result;
}
Copy the code
Since the View is a single element, there are no child elements that can pass events down and only handle the events themselves, so the code is also significantly reduced. Result indicates whether to consume the event, and then judge onTouchListener. If the onTouch method in onTouchListenter returns true, then the onTouchEvent method will not be called again, indicating that onTouchListener has a higher priority than onTouchEvent.
And then let’s look at the implementation of onTouchEvent.
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
// Click event processing in unavailable state still consumes click event
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if(action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) ! =0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
// If the VIew has a proxy set, the proxy's onTouchEvent method will be executed
if(mTouchDelegate ! =null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true; }}if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
.....
booleanprepressed = (mPrivateFlags & PFLAG_PREPRESSED) ! =0; .// After all judgment
performClickInternal();
break;
case MotionEvent.ACTION_DOWN:
....
break;
case MotionEvent.ACTION_CANCEL:
....
break;
case MotionEvent.ACTION_MOVE:
....
break;
}
// If the control is clickable, it must return true
return true;
}
// Return false if the control is unclickable
return false;
}
Copy the code
As you can see from the above code, whenever a View’s CLICKABLE, LONG_CLICKABLE, or CONTEXT_CLICKABLE is true, it consumes the event regardless of whether it is disabled. Then, if the control is clickable, each of the four event types is handled accordingly, notably the ACTION_UP event. You can see from the source code that the performClickInternal method is triggered when the ACTION_UP event occurs. What about the internal implementation of this method? As follows:
private boolean performClickInternal(a) {
// Must notify autofill manager before performing the click actions to avoid scenarios where
// the app has a click listener that changes the state of views the autofill service might
// be interested on.
notifyAutofillManagerOnClick();
return performClick();
}
Copy the code
We can see that performClick is still called at the end, and inside performClick:
public boolean performClick(a) {
// We still need to call this method to handle the cases where performClick() was called
// externally, instead of through performClickInternal()
notifyAutofillManagerOnClick();
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;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
Copy the code
As soon as we register the click event for the View via setOnClickListener, li.monClickListener is assigned a value and the onClick method is called.
The flow chart is as follows:
We can find the onTouch from the flowchart, onTouchEvent, onClick priority: the onTouch > onTouchEvent > onClick
conclusion
So far, we have combed through the distribution mechanism of click events through source code. The general process of event distribution is as follows:
- When a click event occurs, it is always first passed to the current Activity, which is distributed by the Activity’s dispatchTouchEvent. The Activity passes the event to the Window, and the PhoneWindow class, the Window’s unique implementation, passes the event to the DecorView. The DecorView then passes the event to its parent ViewGroup, which is the View set by setContentView and therefore called the top-level View. The ViewGroup can either handle the event itself or pass it to its child View. But eventually the View’s dispatchTouchEvent is called to handle the event.
- In the View dispatchTouchEvent, if onTouchListener is set, its onTouch method is called, and if onTouch returns true, onTouchEvent is not called again. If there is a click event, the onClick method is called in onTouchEvent. If the child View’s onTouchEvent returns false, it does not consume the event, The event is passed back to the onTouchEvent of the next-level ViewGroup. If none of the viewGroups returns true, it is eventually passed back to the Activity’s onTouchEvent.
Reference blog:
- Android event distribution mechanism in detail walkthrough, you deserve it
- Android development knowledge (9) : Android event processing mechanism: event distribution, transmission, interception, processing mechanism of principle analysis (2)