- Android event types
public boolean onTouchEvent(MotionEvent event) { switch (()) { case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_MOVE: break; case MotionEvent.ACTION_UP: break; case MotionEvent.ACTION_CANCEL: break; } return true; }
As shown in the figure above, Android has four main event types, Down, Move, Up, and Cancel.
In a stream of events:
Down triggers once, and the finger is pressed on the screen.
The Move event will be triggered 0 or more times, and the fingers swipe on the screen.
The Up event will be triggered 0 times or once, and the finger is raised when the screen is raised.
Cancel event will be fired 0 times or once, when the slide exceeds the space boundary.
- What is the call chain of the android App layer event distribution mechanism?
The entrance to event distribution of App layer is in Activity. The dispatchTouchEvent is used in the WindowManagerService in the Framework layer. Events in WMS originate from the Native layer and Linux layer. There is a chance to analyze this event delivery in this area. This article mainly analyzes how the Android event distribution mechanism is displayed in the App.
public boolean dispatchTouchEvent(MotionEvent ev) { if (() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }
getWindow() returns a PhoneWindow object.
@Override public boolean superDispatchTouchEvent(MotionEvent event) { return (event); }
public boolean superDispatchTouchEvent(MotionEvent event) { return (event); }
mDecor is a DecorView, an internal class of PhoneWindow, inheriting FrameLayout.
FrameLayout inherits from ViewGroup. Let’s see how event delivery is handled in ViewGroup. The main logic of event delivery is in the ViewGroup.
Next, let’s see how ViewGroup and dispatchToucheEvent (MotionEvent ev) distribute events.
public class ViewGroup { @Override public boolean dispatchTouchEvent(MotionEvent ev) { boolean handled = false; final int action = (); final int actionMasked = action & MotionEvent.ACTION_MASK; // Check for interception. //After pressing, actionMasked=down when calling the first time, mFirstTouchTarget=null //Press actionMasked=move, if mFirstTouchTarget! =null indicates that there is a child view that handles the touch event final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { //Call the method of subclass rewrite If true means intercept, false means not intercept intercepted = onInterceptTouchEvent(ev); (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. //If there is no child view to handle the touch event and it is not the first down event pressed, the group will intercept the touch event intercepted = true; } TouchTarget newTouchTarget = null; //The flag to determine whether the view handles the event when pressed for the first time boolean alreadyDispatchedToNewTouchTarget = false; //If you do not intercept, enter event distribution if (!canceled && !intercepted) { //Note that subView will only be traversed when ACTION_DOWN if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { final ArrayList<View> preorderedList = buildTouchDispatchChildList(); final View[] children = mChildren; //Start traversal of subview for (int i = childrenCount - 1; i >= 0; i--) { final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex); //isTransformedTouchPointInView determines whether the event falls on child by comparing the coordinates. // true is, false is not if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { (false); // If the event does not fall on child, skip this loop and execute the next loop continue; } // Find TouchTarget by traversing the TouchTarget linked list. If found, break will jump out of the loop 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. |= idBitsToAssign; break; } //The first time I come in newTouchTarget==null, enter the following logic //If dispatchTransformedTouchEvent returns true, indicating that the child view has processed the event, this loop will be jumped out. // Will not be distributed to subviews anymore if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // Child wants to receive touch within its bounds. //Cache child into the linked list of TouchTarget newTouchTarget = addTouchTarget(child, idBitsToAssign); //Note that this flag bit is true, the following logic will be used alreadyDispatchedToNewTouchTarget = true; break; } } if (preorderedList != null) (); } } } // Dispatch to touch targets. //The following logic will be executed when ACTION_DOWN and ACTION_MOVE //mFirstTouchTarget==null means that no child View handles touch events, and the third parameter of dispatchTransformedTouchEvent is null. if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { // Dispatch to touch targets, excluding the new touch target if we already // dispatched to it. Cancel touch targets if necessary. //Travel the TouchTarget linked list, and the TouchTarget caches the view for processing events. //Wave will be called multiple times in one full event, so the corresponding view found in ACTION_DOWN is cached. // Instead of traversing the child ViewGroup every time, it will be too damaging to performance TouchTarget target = mFirstTouchTarget; //Start traversal of the linked list. Why is it a linked list? This is for multi-touch, and there will be multiple views to respond to touch events. while (target != null) { final TouchTarget next = ; //alreadyDispatchedToNewTouchTarget ==true, and the cached target and newTouchTarget are the same //Turnly set handled to true to prevent repeated calls of dispatchTransformedTouchEvent method when ACTION_DOWN if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { if (dispatchTransformedTouchEvent(ev, cancelChild, , )) { handled = true; } } target = next; } } //Return handled to the caller of dispatchTouchEvent. return handled; } /** * Gets the touch target for specified child view. * Returns null if not found. * Find the touch target by traversing the TouchTarget list */ private TouchTarget getTouchTarget(@NonNull View child) { for (TouchTarget target = mFirstTouchTarget; target != null; target = ) { if ( == child) { return target; } } return null; } /** * Adds a touch target for specified child to the beginning of the list. * Assumes the target child is not already present. * Related TouchTarget and view, and place this TouchTarget at the head of the linked list */ private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) { final TouchTarget target = (child, pointerIdBits); = mFirstTouchTarget; mFirstTouchTarget = target; return target; } private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; // Only look at the two most critical lines of code if (child == null) { //If child is null and no child view handles touch event, the dispatchTouchEvent of the parent class View is called. handled = (transformedEvent); } else { //If child is not empty, call the dispatchTouchEvent of the child view //This line of code is very critical. If child is ViewGroup, continue to distribute events in ViewGoup. This will be a recursive call //If child is a View, call the dispatchTouchEvent of the View to distribute the event. handled = (transformedEvent); } //The above two lines of code will eventually call the dispatchTouchEvent method of the View. return handled; } }
Next, let’s see how View dispatchTouchEvent handles
public class View { public boolean dispatchTouchEvent(MotionEvent ev) { boolean result = false; ListenerInfo li = mListenerInfo; //If the view has set mOnTouchListener, call it first. //If true is returned, it means that this event has been consumed and is not being passed down. Why do you say so? That's because, through his return value, a logical judgment was made if (li != null && != null && (mViewFlags & ENABLED_MASK) == ENABLED && (this, event)) { result = true; } //If result is false, call the method if (!result && onTouchEvent(event)) { result = true; } return result; } public boolean onTouchEvent(MotionEvent event) { switch (action) { case MotionEvent.ACTION_UP: if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { //Calling the performClick () method performClickInternal(); } break; case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_CANCEL: break; case MotionEvent.ACTION_MOVE: break; } return false; } public boolean performClick() { final boolean result; final ListenerInfo li = mListenerInfo; if (li != null && != null) { playSoundEffect(); (this); result = true; } else { result = false; } return result; } }
If you look carefully at the ViewGroup and View event distribution logic code. We can summarize it into the following points.
After receiving the DispatchTouchEvent call, onInterceptTouchEvent is called first, and the return value is used to determine whether the event is intercepted. We can rewrite this method in the code to intercept events.
After the event, ViewGoup will traverse the subView, find the View that handles the Touch event, and cache it in the TouchTarget linked list. In an event stream, the subView will only be traversed when it is down.
Only the benefits of doing improve performance. The cached linked list is also for multi-touch, and multiple views will respond to touch events.
When moving the event, it will traverse from this linked list and pass the event by calling the dispatchTransformedTouchEvent method.
- In the dispatchTransformedTouchEvent method, different distribution processes will be entered according to whether the value of child is null.
1) If null means that there is no child view and no response event, then call the dispatchTouchEvent--->onTouchEvent method of the parent class View.
2) If the child value is not null. Then call the dispatchTouchEvent method of child,
At this time, if child is a ViewGoup, then the distribution logic in the ViewGoup will be executed and the recursive call will be performed.
If child is a View, call the View's dispatchTouchEvent--->onTouchEvent method.
- In the dispatchTouchEvent method of View:
1) The listener's onTouch priority is higher than the view's onTouchEvent
2) The method will be called first and the event will be judged based on the return value.
3) If the above return value is false, the onTouchEvent method of the View will be called.
5. When the UP event is in the View's onTouchEvent method, the onClickListener listening event set by the View will be called.
That's why, when onTouch listening and lonClickListener listening are set to a View, the onClick event cannot be called when onTouch returns true.
This is the end of this article about the example analysis of Android event distribution mechanism. For more related content on Android event distribution, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!