This article is edited and output jointly by Aixueyuan platform
Original author: Love School – Mobius Ring
Before I begin to describe the questions, I wrote this article partly to build my knowledge of Android, and partly because I really think this is a must-ask for Android interviews. There are a lot of blogs and books on the Internet about this and they are very good. Is the so-called only their own understanding is their own, so after reading their article, plus their own understanding is hereby recorded a ~, in order to deepen understanding and memory! If understand wrong place, please leave a message to explain, we discuss together, thank you!
Contact information: Email ([email protected])
1. Essential knowledge
To put it bluntly, event distribution is a series of event transmission and processing processes that occur during the interaction between users and applications (finger and screen contact).
1.1 Objects involved in event distribution –MotionEvent
Typical event types:
ACTION_DOWN -- The moment your finger touches the screen (press down) ACTION_MOVE -- The moment your finger moves on the screen (move) ACTION_UP -- The moment your finger lifts (lift)Copy the code
A sequence of events: a sequence of events from the time the finger presses the View until the finger leaves the View.
ACTION_DOWN-> ACTION_UP ACTION_DOWN->... ACTION_MOVE... ->ACTION_UPCopy the code
1.2 Methods involved in event distribution
1. dispatchTouchEvent(MotionEvent ev)
Used for event distribution. The return result is affected by the onTouchEvent method of the current View and the dispatchTouchEvent method of the child View, indicating whether the current event is consumed.
2. onInterceptTouchEvent(MotionEvent ev)
Called inside the dispatchTouchEvent method above to verify whether the current event is intercepted. One important thing to note here is that if the current View intercepts an event (ACTION_DOWN in general), this method will not be called again within the same sequence of events (as discussed above) — that is, no double-interception verification will be done. Note: Activity and View do not have this method inside
3. onTouchEvent(MotionEvent ev)
Called inside the dispatchTouchEvent method above, the result is returned indicating whether the current event is consumed. Note that if the current method returns false (no consumption), then the current View cannot receive the event again in the same sequence of events.
The relationship of the above methods can be represented by the following pseudocode:
public boolean dispatchTouchEvent(MotionEvetn e){
if(onInterceptTouchEvent(ev)){// Whether to interceptreturnonTouchEvent(e); // Intercepting event handling: whether to consume}returnchild.dispatchTouchEvent(e); // Do not intercept: subclass View distribution}Copy the code
We can get a rough idea of the event passing rules through the above pseudocode: For a root ViewGroup, when the click event is generated, it is first passed to it. Its dispatchTouchEvent is called. If the onInterceptTouchEvent method returns true, it intercepts the current event. And then the event is handed to the onTouchEvent method of this ViewGroup. If the onInterceptTouchEvent method returns false, the current event is not intercepted, the current event is passed to its child View, and the View’s dispatchTouchEvent method is called, and so on until the event is finally handled.
1.3 Event transfer Process Follows the following procedure
Activity -> Windown(PhoneWindow) -> DecorView(FrameLayout) -> contentView(setContentView) ->.. ViewGroup.. ->ViewCopy the code
2. Event distribution source code analysis
According to the above analysis of the process of event transfer, we will tear open its mysterious veil step by step and understand its call relationship from the inside.
2.1 Activity Distribution process of click events
Click events are represented by MontionEvent. When a click action occurs, it is first sent to the current Activity. The Activity’s dispatchTouchEvent method dispatches the event. The Window passes the event to the DecorView, which is generally the underlying container of the current interface (that is, the parent container of the View set by setContentView), which can be obtained via activity.getwindow ().getDecorView(). So we’ll start with the Activity’s dispatchTouchEvent.
Source code -1: Activity#dispatchTouchEvent
/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
*
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
Copy the code
Now analyze the above code, through the source code to understand the event to the Activity attached Window for distribution, ifgetWindow().superDispatchTouchEvent(ev)
returntrue
, the event ends here, and returnsfalse
, indicating that all views of the lower levelonTouchEvent
All returned to thefalse
, then the ActivityonTouchEvent
Will be called (as above)
GetWindow ().superDispatchTouchEvent(ev)
Source code -2: Windows #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.
*
* <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, whichyou should instantiate when needing a * Window. */ public abstract class Window { /** * Used by custom windows, such as Dialog, to pass the touch screen event * further down the view hierarchy. Application developers should * not need to implement or call this. * */ public abstract boolean superDispatchTouchEvent(MotionEvent event); . }Copy the code
Look at the source code posted above found posted a lot of notes, because hereWindow
Is an abstract class, so what’s its implementation class? YesPhoneWindow
Why? Here you can read the above in detailWindow
Class, as noted hereWindow
The only implementation ofandroid.view.PhoneWindow
Good guy, hidden enough deep, then please move, thank you ~
Phonewindows #superDispatchTouchEvent
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
Copy the code
Here logic is clear! It’s only one line of code, but that’s enough to illustrate the point. Here we hand over the specific logic toDecorView
(This is the top View of the window –>ViewGroup), i.eActivity#setContentView
Set up theView
isDecorView
A child of the View. So far the event has arrivedDecorView
Here, due toDecorVieW
In theFrameLayout
And is the parentView
, then conclude that eventually the event will pass toView
To this point is not our focus, how does the event pass through the topView
Pass-through consumption is our highlight, please continue, thank you ~
2.2 The distribution process of click events by top-level View
How to distribute click events in a View has been described above. Here is the ViewGroup source code directly. The source code is as follows:
The content of dispatchTouchEvent method is explained in the following sections:
Source code -4: ViewGroup#dispatchTouchEvent — intercept logic processing
// Handle an initial down.
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();
}
// Check for interception.
final boolean intercepted;
if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! = null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! = 0;if(! disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore actionin 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
- Intercept condition: The event type is
ACTION_DOWN || mFirstTouchTarget ! = null
; - MFirstTouchTarget: Each start (
ACTION_DOWN
) are initialized tonull
, when the event byViewGroup
Refers to the child element of the successfully processed - When the event by
ViewGroup
Intercept when conditionmFirstTouchTarget ! = null
If it does not hold, it is assumedACTION_MOVE
andACTION_UP
When the event arrives, because the first interception condition is not metonInterceptTouchEvent
Call no more: Verify that once the current View intercepts an event, all other events in the same sequence of events are no longer checked and are handled directly by the View. FLAG_DISALLOW_INTERCEPT
Flag bit: Once this flag bit is set (requestDisallowInterceptTouchEvent
),ViewGroup
Will not be able to intercept exceptACTION_DOWN
Other click events (ACTION_DOWN
Event resets the flag bit, invalidating the flag bit set in the child View.- In the face of
ACTION_DOWN
The event,ViewGroup
It always calls its ownonInterceptTouchEvent
Method to ask yourself if you want to intercept events, as you can see from the source code above.
ViewGroup#dispatchTouchEvent
// Handle an initial down.
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 forthe previous gesture // due to an app switch, ANR, or some other state change. cancelAndClearTouchTargets(ev); // Reset mFirstTouchTarget = null resetTouchState(); // Reset FLAG_DISALLOW_INTERCEPT flag}Copy the code
As you can see from the code above,ViewGroup
Will be inACTION_DOWN
The state is reset when the event arrives, so the childView
callrequestDisallowInterceptTouchEvent
Does not affectViewGroup
rightACTION_DOWN
Event handling.
Conclusion:
ViewGroup
Decide to intercept events (ACTION_DOWN
), subsequent click events will be handed over to it by default and will not be calledonInterceptTouchEvent
Methods.FLAG_DISALLOW_INTERCEPT
The purpose of this sign is to makeViewGroup
No more intercepting events, provided of courseViewGroup
Don’t interceptACTION_DOWN
Events.FLAG_DISALLOW_INTERCEPT
It provides a new way to solve sliding conflict resolution.
ViewGroup#dispatchTouchEvent
final int childrenCount = mChildrenCount;
if(newTouchTarget == null && childrenCount ! = 0) { finalfloat x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
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;
}
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 pointerin addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
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 indexfor (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break; }}}else{ mLastTouchDownIndex = childIndex; } mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); // Save the current subview :mFirstTouchTarget newTouchTarget = addTouchTarget(Child, idBitsToAssign); alreadyDispatchedToNewTouchTarget =true;
break;
}
// The accessibility focus did not handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if(preorderedList ! = null) preorderedList.clear(); / /... }Copy the code
ViewGroup#dispatchTouchEvent — sub-view sends main logic call
/**
* Transforms a motion event into the coordinate space of a particular child view,
* filters out irrelevant pointer ids, and overrides its action if necessary.
* If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
*/
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Canceling motions is a special case. We do not need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
returnhandled; } / /... }Copy the code
The ability of a child View to receive click events is measured in two ways:
- Whether the child element is playing an animation
- Click whether the coordinates of the event fall within the region of the child element
The code above shows thatViewGroup
Do not intercept the case, the event to the childView
That is, the main call method isdispatchTransformedTouchEvent
Inside, it actually calls the child element’sdispatchTouchEvent
Method (available through the aboveSource – 7It can be seen from specific analysis that ifchild.dispatchTouchEvent(event)
returntrue
, thenmFirstTouchTarget
(addTouchTarget
Method inside operation) is assigned and breaks out of the for loopmFirstTouchTarget
Assignment, will affectViewGroup
Intercept policy, as shown below:
ViewGroup#dispatchTouchEvent — assign mFirstTouchTarget
/**
* Adds a touch target for specified child to the beginning of the list.
* Assumes the target child is not already present.
*/
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
If it isnull
All subsequent events in the same sequence will be intercepted by default. (No secondary interception verification)
Iterating through all child elements does not handle two cases:
ViewGroup
No child elements;- The child element handles the click event, but none of the child elements consume the event.
The ViewGroup will call super.dispatchTouchEvent(EVet). This can be seen from the above source code -8. It is obvious that the ViewGroup inherits from the View, so we will go to the View’s dispatchTouchEvent method. Click the event to be handled by the View, so continue with the analysis below.
2.3 View’s processing of click events
It has no onInterceptTouchEvent method and can’t pass down the event. It has to handle the event itself. Its dispatchTouchEvent method is as follows:
-9: View#dispatchTouchEvent — View click event handler
/**
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
//...
boolean result = false; / /...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;
}
}
//...
return result;
}
Copy the code
As can be seen from the above code:The OnTouchListener the onTouch
thanonTouchEvent(event)
High priority if setOnTouchListener
andmOnTouchListener.onTouch
returntrue
thenonTouchEvent(event)
Will not be called, and will be called otherwiseonTouchEvent(event)
, see below:
Source code -10: View#onTouchEvent — click on the event specific handling
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;
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 does not respond to them.
return clickable;
}
if(mTouchDelegate ! = null) {if (mTouchDelegate.onTouchEvent(event)) {
return true; }}if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if(! clickable) { removeTapCallback(); removeLongPressCallback(); mInContextButtonPress =false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break; } boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) ! = 0;if((mPrivateFlags & PFLAG_PRESSED) ! = 0 || prepressed) { // take focusif we do not have it already and we should in
// touch mode.
boolean focusTaken = false;
if(isFocusable() && isFocusableInTouchMode() && ! isFocused()) { focusTaken = requestFocus(); }if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
if(! mHasPerformedLongPress && ! mIgnoreNextUpEvent) { // This is a tap, so remove the longpress check removeLongPressCallback(); // Only perform take click actionsif we were in the pressed state
if(! focusTaken) { // Use a Runnable and post this rather than calling // performClick directly. This lets other visual state // of the view update before click actions start.if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if(! post(mPerformClick)) { performClick(); }}}if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if(! post(mUnsetPressedState)) { // If the post failed, unpress right now mUnsetPressedState.run(); } removeTapCallback(); } mIgnoreNextUpEvent =false;
break; / /... }return true;
}
return false;
}
Copy the code
As you can see from the above code, there are two consuming factors that affect the event:CLICKABLE
andLONG_CLICKABLE
As long as there is atrue
, then it will consume the event, i.eonTouchEvent
Method returnstrue
, the actual call method isperformClick();
, called within itOnClickListener#onClick
Methods.
To this click on the event distribution mechanism of the source analysis is finished, but Android learning has just begun, there is a long way to go, the following attached stolen from elsewhere, feel good can see
2.4 View Event Distribution Flow chart
Refer to relevant articles and books
Android Event Distribution
Parsing the Android event distribution mechanism
Book: Exploring the Art of Android Development by Ren Yugang
If you think it’s written well, please raise your hand and give it a thumbs-up 😜.