Android-event distribution-principle analysis

Android-event distribution-principle analysis

Everyone is unique, can shine and heat up, even if the current self is still weak, but you must believe that you will be able to support the sky it's time to. Copy code


     I believe that many people are well-acquainted, isn't it the three methods? Are the three methods dispatchTouchEvent, onInterceptTouchEvent and onTouchEvent? Isn t this easy? But if I ask you, have you really seen the source code of the event distribution? Can you use events to distribute the next pull-to-refresh component? Do you have any thoughts based on the pull-down refresh? How would you answer me? Do you still say that you really do event distribution? I will explain in the event distribution, and raise some questions at the same time, as well as cases related to our daily development. (Here, everyone can also read Ren Zhigang's artistic exploration, a very good book)

  • What is the relationship between dispatchTouchEvent, onInterceptTouchEvent and onTouchEvent, and what is the order of dispatch?
  • What are the applicable scenarios for the three methods and when should I rewrite which method?

Method introduction

  • dispatchTouchEvent: During the distribution, if the event can be sent to the current View, then this method must be called back. Inside this method, onInterceptTouchEvent and onTouchEvent will be used to comprehensively determine whether the current event will be consumed.

  • onInterceptTouchEvent: Shows whether to intercept this event, this method will only be called once (I believe you already know it, but why is it only called once? And what is the use of this method for us? I will analyze it later)

  • onTouchEvent: A method is used to handle events, for example, we need to handle the sliding of the finger here, as well as the monitoring of various touch positions, and so on. . . (One more question, since this place is for event processing, can I handle it in the above two methods?) And an event sequence, if you need to consume, then this event sequence will be given to you, if you don t consume , Then this sequence of events cannot be received. (What is called an event sequence, that is, from pressing down to lifting your finger), but what method do I have? I receive this event and I don't want the subsequent events. How to deal with it? ? ?

Source code analysis

Overview of the event distribution process and key points of attention

  The event distribution will go through the following stages activity->window->view, of which we need to pay attention to the window->view step. At present, I have not encountered some special operations in the activity->window stage. The question is again, what is the process? Here I will talk about my personal opinion.

  • What is the relationship between window and view, why do we need the level of window, and what problems do we need to solve for the event delivery and distribution of the size relationship between window and view? (I will explain them one by one during the analysis)
  • View event distribution is actually the event transfer between ViewGroup and View, which is the three methods described above. It will be mentioned in the process of analyzing the source code, and I will also explain to you the core code of the Ptr pull-down to refresh this library.
Activity->window distribution

If you look closely, you can actually find that both activity and window inherit the abstract class of window and implement the interface of this abstract class to implement event delivery (this place is actually very worthwhile for us to learn, you can simply understand it as the proxy mode , The event handling of activity is handed over to the internal proxy class PhoneWindow, because they inherit the same class, can we follow suit in the process of writing code in the future, instead of class A handed over to class B to do things, use different The name of the method, isn't it disgusting?)

Window class (it is recommended to check the description of this class by yourself): For example, this window class is an abstract class of top-level windows, and provides a standard UI setting method. This UI is generally used for keyboards. For example, the keyboard properties we often set. Its implementation class is PhoneWindow only, and this example should be used as a top-level view (Google translated) added to the window manager.

   I don t understand the last sentence. Let me talk about my own understanding: In fact, you can understand it this way. In fact, our interface display does not have a window. The simple understanding of the window is a constraint. My view is placed in the window ( Convenient for everyone to understand). Why do you say that? We can constrain the size of the window. If the window is so big, my view can't be displayed. Isn't it (there is a cropping property, and the View can be displayed, not considered for the time being), your click event is window- >View, the window is set with the position and size. When you touch the window, the event will be sent to your view. I wonder if you can understand that? You can understand a constraint, don't go too far. (I will also introduce a WindowManger to you later, specifically to introduce the window, it is convenient for everyone to understand, no need to pay too much attention)

/** * 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) { Analysis One if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } Analysis 2: if (getWindow().superDispatchTouchEvent(ev)) { return true; } Analysis 3: return onTouchEvent(ev); } Copy code

First look at the note: The general meaning is that when your phone touches the screen and the event starts to be distributed, this method is the entrance.

  • Analysis 1: When the activity receives the down event, it will trigger the onUserInteraction method, which is provided for the activity implementation class to override. If you want to listen for the user to start touching the screen, you can implement it.
  • Analysis 2: The event will be passed to the dispatchTouchEvent of the window for processing. If it returns true, it proves that the window is sent first, then it returns true. This verifies the activity->window process.
  • Analysis 3: If the above is not executed, it means that the window does not consume events, then handle it by yourself and use your own onTouchEvent method. Obviously, the event processing that we need to focus on is in the window.

As we said above, there is only one implementation class for window, and that is PhoneWindow. Let's take a look at PhoneWindow.

@Override public boolean superDispatchTouchEvent(MotionEvent event) { //Analysis one return mDecor.superDispatchTouchEvent(event); } Copy code

Analysis 1: Obviously, it is passed to the mDecor View, which verifies the window->view process we mentioned above. Introduction: mDecor is a custom View that inherits FrameLayout. The core is two Views, one is the top status bar, the other is the setContentView that we overwrite, and the set View. In fact, we can also understand this well, this is us I haven t seen the View that displays the window for all of the display on my mobile phone? No, it proves that the window is actually a constraint, and Android is used for hierarchical level, that is, window overlay, one page opens another page, in fact, you can understand window overlay, and your own window is responsible for handling your own events. (I don t know if you understand, I will introduce it later)

Then we need to look at the event distribution of mDecor, right?

@Override public boolean dispatchTouchEvent(MotionEvent ev) { final Window.Callback cb = mWindow.getCallback(); //Analysis one return cb != null && !mWindow.isDestroyed() && mFeatureId <0 ? cb.dispatchTouchEvent(ev): super.dispatchTouchEvent(ev); } Copy code

Analysis 1: This return value uses Window s internal class CallBack. This CallBack is used to intercept events. It is not the core event distribution process that we need to be related. What we need to care about is still super.dispatchTouchEvent(ev), because FrameLayout is not implemented. For this method, we need to check in ViewGroup. But there are too many codes in ViewGroup. It is important to say that, in other words, it is basically impossible to clear what each line of code means. We only need to understand and sort out the core logic.

//Check for interception. final boolean intercepted; //Analysis one if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; //Analysis two if (!disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action);//restore action in case it was changed } else { intercepted = false; } } else { //Analysis Three //There are no touch targets and this action is not an initial down //so this view group continues to intercept touches. intercepted = true; } Copy code

Analysis 1: When the current event is a down event, or mFirstTouchTarget!=null, it will be judged whether to intercept the event. 1. the current is down event, that is, the judgment will be made at the beginning of the touch, and the second judgment is the latter judgment. What does this judgment mean? It is the first TouchTarget that handles the event. TouchTarget is a package that wraps the current view and multiple points. The touch information and the class of next view information will be introduced later. For the time being, this is the first View class that handles events.

Analysis 2: I believe everyone is familiar with this flag bit. It is hoped that the parent control will not intercept the event and distribute it to itself for processing. If this flag bit is set, I think the parent control will not process it, so it will distribute the event and handle it by itself. If you still don't handle it, you need to call your own onInterceptTouchEvent method to make a judgment, and it's easy to understand.

Analysis 3: If it is not a down event, or there is no child View to intercept the consumption event, then the parent control needs to intercept it and hand it to the parent control to handle it. If the onTouchListener of the parent control returns true, or the current control is ENABLED, etc., it returns true. In this case, at the same time your onTouchEvent also returns true (the event is consumed by yourself) then the message will be digested by itself, otherwise, it will be passed upwards (there will be an analysis later).

Here we can get the conclusion: if there is a parent control and a child View that consumes the down event, the subsequent event will go to this judgment, actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null, it will return true, because mFirstTouchTarget does not Is empty, so the variable of the intercepted parent control will always be false, indicating that if this control does not handle this event, then this view has an event sequence. I believe you will be very clear here.

Next, we will analyze the variable mFirstTouchTarget. The official comment is First touch target in the linked list of touch targets. It is well understood that the parent control may have many child views, which must be a linked list, but this View is the first one to process View of the event.

//Check for cancelation. //Analysis one final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; //Update list of touch targets for pointer down, if needed. final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; if (!canceled && !intercepted) { //If the event is targeting accessibility focus we give it to the //view that has accessibility focus and if it does not handle it //we clear the flag and dispatch the event to all children as usual. //We are looking up the accessibility focused host to avoid keeping //state since these events are very rare. View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus() ? findChildWithAccessibilityFocus(): null; if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { //Multiple touch related, you can bypass final int actionIndex = ev.getActionIndex();//always 0 for down final int idBitsToAssign = split? 1 << ev.getPointerId(actionIndex) : TouchTarget.ALL_POINTER_IDS; //Clean up earlier touch targets for this pointer id in case they //have become out of sync. removePointersFromTouchTargets(idBitsToAssign); final int childrenCount = mChildrenCount; //It was empty at the beginning. We assume that this parent control has a child View, otherwise there is no need for analysis. //If there is no child View, it will go back to what I analyzed before, handle it by myself, and throw it up if I can t handle it. if (newTouchTarget == null && childrenCount != 0) { //Get touch coordinates, design to multi-finger, nothing to say final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); //Find a child that can receive the event. //Scan children from front to back. //Analysis two 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); //The view that is biased to get the focus, if it is found and matched exactly, it will execute the normal view in this View //Event distribution, this may traverse PS many times and twice: I don t know why goole did this, directly //Isn t the distribution bad? Is there a high probability that the focus will consume events? If so, it will indeed improve performance. //Reduce traversal, this is just my guess, I don't understand. //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 (!child.canReceivePointerEvents() || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; } //Since mFirstTouchTarget is still empty, the View that consumes the event has not been found yet, //So newTouchTarget is also empty. This method is found in the View that can respond to the event //Target View, because not all child views can respond to events, childcount needs to be traversed once, //Then determine if there is any in the linklist that responds to the event, return if there is, otherwise return null, assuming //newTouchTarget finds that it is not empty, then there is no need for this loop to go on, just//break 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); //Analysis Three 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); 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); } if (preorderedList != null) preorderedList.clear(); } //It is embarrassing at this time, if there is any found in the child view, it points to the previous mFirstTouchTarget if (newTouchTarget == null && mFirstTouchTarget != null) { //Did not find a child to receive the event. //Assign the pointer to the least recently added target. newTouchTarget = mFirstTouchTarget; while ( != null) { newTouchTarget =; } newTouchTarget.pointerIdBits |= idBitsToAssign; } } } Copy code

Brief summary: In fact, the core of this cycle is to find mFirstTouchTarget to see who can consume events.

Analysis 1: Let me emphasize here, everyone pay attention to this cancel event, what is the usage scenario, such as a button, a click is to click to release, right, if it is, I click an item, and then slide without letting go, trigger Pull-down refresh, this time should be pull-down refresh, this item should not respond to the onClick event, everyone can understand, cancel has this function, I will explain to you the pull-down refresh later, and this knowledge point is worth learning.

Analysis 2: This is a collection of lists that define sub-Views. It s a coincidence. Let s go in and have a look. What do you mean by customizing the sequence of events for the child View? If we have a ViewGroup with three child Views, we can define the drawing order through the ViewGroup's getChildDrawingOrder (from the code point of view, it is consistent with the event delivery), and you will find that the View.getZ property is Prior to getChildDrawingOrder (you rewrite getChildDrawingOrder to get an order, but you will also rearrange it according to View.getZ). Some people will ask, what is the use of this? It is useful. Assuming that the drawing order of the View is from left to right, you can completely rewrite this method to draw from right to left and achieve some fancy operations.

Analysis 3: This is the core code for passing the event to the child View.

Before the analysis, let s take a look at the annotation description of the dispatchTransformedTouchEvent method: filter some illegal touch ids, reset the action, what does it mean, that is, the action of MotionEvent will change (for example, if the current event is cancelled, it may be This action will be changed to cancel, which is easy to understand). At the same time, if the view is null, it will be handled by the father. In other words, the null view will not handle the event.

dispatchTransformedTouchEvent: This method will not be introduced too much. There is nothing. What we need to focus on is a variable handled. This variable indicates whether the event is processed, handled = super.dispatchTouchEvent(event), so the focus of our concern is It is the dispatchTouchEvent method again, and next, is to enter the source code of View for analysis.

public boolean dispatchTouchEvent(MotionEvent event) { //If the event should be handled by accessibility focus first. if (event.isTargetAccessibilityFocus()) { //We don't have focus or no virtual descendant has it, do not handle the event. if (!isAccessibilityFocusedViewOrHost()) { return false; } //We have focus and got the event, then use normal event dispatch. event.setTargetAccessibilityFocus(false); } boolean result = false; if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(event, 0); } final int actionMasked = event.getActionMasked(); //Do you feel that this position is very interesting, you can see that it is a hole left by Google, why use it? //(Actually, I clicked it out. I didn t have any other ideas. I just wanted to tell everyone that, in fact, many times, //The code we need, in fact, may have been encapsulated for me by others, or exposed the hole, we still need to read more and learn more) if (actionMasked == MotionEvent.ACTION_DOWN) { //Defensive cleanup for new gesture stopNestedScroll(); } if (onFilterTouchEventForSecurity(event)) { if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) { result = true; } //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; //Analysis one if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } //Analysis two if (!result && onTouchEvent(event)) { result = true; } } if (!result && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } //Call it again, look at the name of this method, it is to hold the list that is sliding, but someone might have a problem? //If it is the same to stop sliding, shouldn't it be inertial sliding at the moment of raising your hand? //What about my inertia? Then how do I know whether it was called by pressing or by lifting my finger? //In fact, I understand that this stopNestedScroll, when you touch and click to leave, your list list dragging will stop. From the gesture point of view //There is a problem, I left the touch and there is no sliding. As for the inertia for a good user experience, you can check the source code for how to achieve it. //Clean up after nested scrolls if this is the end of a gesture; //also cancel it if we tried an ACTION_DOWN but we didn't want the rest //of the gesture. if (actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL || (actionMasked == MotionEvent.ACTION_DOWN && !result)) { stopNestedScroll(); } return result; } Copy code

Analysis 1: onTouchListener, if we set onTouchListener to the View and the return value is true, then this View is likely to consume this event.

Analysis 2: onTouchEvent, this method is one of the three important methods of time distribution that we often say, we often deal with our gesture operations here, and deal with events such as down and up.

We can see that the priority of onTouchListener should be higher than onTouchEvent

In fact, there is nothing particularly difficult to understand. Everyone knows this simple process. I would like to introduce you to onTouchEvent. There are many things worth learning.

First ask a few questions:

  1. How to distinguish the current sliding or click event?
  2. How to implement click and longClick?
  3. How to cancel the response event of the current View, think about the cancel I mentioned before? ? ?

Reminder: You can start from the down event. I don t know why the foreigner s code is up.

public boolean onTouchEvent(MotionEvent event) { final float x = event.getX(); final float y = event.getY(); final int viewFlags = mViewFlags; final int action = event.getAction(); //What is the basis for the View to be clickable final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE; //Don t click directly setPressed=false, you can see that for the pressed function, Google is directly integrated in the View 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 touchDelegate can consume, then consume (tell the benefits of delegate, why delegate? Isn t this a design pattern anymore, hahahaha) if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } //If you can't click, there is no need to walk in if you hover or something if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) { switch (action) { case MotionEvent.ACTION_UP: mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; if ((viewFlags & TOOLTIP) == TOOLTIP) { handleTooltipUp(); } //Raise your finger, if it is not clickable, clear it once, the whole is similar to cancel if (!clickable) { removeTapCallback(); removeLongPressCallback(); mInContextButtonPress = false; mHasPerformedLongPress = false; mIgnoreNextUpEvent = false; break; } boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { //take focus if we don't 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); } //The long press event has not been consumed, the current process may be click if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) { //This is a tap, so remove the longpress check removeLongPressCallback(); //Only perform take click actions if 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(); } //start processing onClick if (!post(mPerformClick)) { performClickInternal(); } } } if (mUnsetPressedState == null) { mUnsetPressedState = new UnsetPressedState(); } if (prepressed) { postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (!post(mUnsetPressedState)) { //If the post failed, unpress right now; } removeTapCallback(); } mIgnoreNextUpEvent = false; break; case MotionEvent.ACTION_DOWN: if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) { mPrivateFlags3 |= PFLAG3_FINGER_DOWN; } mHasPerformedLongPress = false; //Even if you can't click, but you can hover, you need to judge the long press and display the hover effect if (!clickable) { //This method will be called multiple times to determine if it is longclick checkForLongClick( ViewConfiguration.getLongPressTimeout(), x, y, TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS); break; } if (performButtonActionOnTouchDown(event)) { break; } //Walk up the hierarchy to determine if we're inside a scrolling container. //Determine if the current View is in a slidable container boolean isInScrollingContainer = isInScrollingContainer(); //For views inside a scrolling container, delay the pressed feedback for //Should be in the container, finger touch, click may occur or sliding may occur, if it is sliding, you need to cancel the click event //a short period in case this is a scroll. if (isInScrollingContainer) { mPrivateFlags |= PFLAG_PREPRESSED; //mPendingCheckForTap is a runnable, mainly for delay. Some people say, since it is delayed, why do I need to create a runnable. The reason is that inside mPendingCheckForTap, there is a logic that needs to judge whether the current long press if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } mPendingCheckForTap.x = event.getX(); mPendingCheckForTap.y = event.getY(); //Obviously it is realized through handler and messagequeue, friends can see the details for themselves postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } else { //Not inside a scrolling container, so show the feedback right away setPressed(true, x, y); //Also judge whether it is long press checkForLongClick( ViewConfiguration.getLongPressTimeout(), x, y, TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS); } break; case MotionEvent.ACTION_CANCEL: if (clickable) { setPressed(false); } //Pay attention to it a little, if it is a cancel event, it can be understood as a clear operation, resetting the flag bit, and removing the runnable message of the messagequeue Think about it, if our parent view, when distributing events to the child view, change the string, will some events that the child view respond to will be canceled? removeTapCallback(); removeLongPressCallback(); mInContextButtonPress = false; mHasPerformedLongPress = false; mIgnoreNextUpEvent = false; mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; break; case MotionEvent.ACTION_MOVE: if (clickable) { drawableHotspotChanged(x, y); } final int motionClassification = event.getClassification(); final boolean ambiguousGesture = motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE; int touchSlop = mTouchSlop; //It's very interesting here, is it a swipe or a long press? When I was down, I delayed a while, and encountered this problem again when I moved. //ambiguousGesture=true, it means that the current event cannot be judged according to the current event, it is ambiguous, and when there is still longClick in the queue without consumption, it will walk in if (ambiguousGesture && hasPendingLongPressCallback()) { final float ambiguousMultiplier = ViewConfiguration.getAmbiguousGestureMultiplier(); //Very simple, it is to see if the current click point is in the View range if (!pointInView(x, y, touchSlop)) { //The default action here is to cancel long press. But instead, we //just extend the timeout here, in case the classification //stays ambiguous. removeLongPressCallback(); long delay = (long) (ViewConfiguration.getLongPressTimeout() * ambiguousMultiplier); //Subtract the time already spent delay -= event.getEventTime()-event.getDownTime(); checkForLongClick( delay, x, y, TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS); } touchSlop *= ambiguousMultiplier; } //Drag your finger out of the View and remove the longclick. Obviously //Be lenient about moving outside of buttons if (!pointInView(x, y, touchSlop)) { //Outside button //Remove any future long press/tap checks removeTapCallback(); removeLongPressCallback(); if ((mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; } //Is it a long press? final boolean deepPress = motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS; //If the current is a long press, and there is a long press runnable message in the queue, remove it, immediately send a delay=0 message, and immediately execute the longClick operation if (deepPress && hasPendingLongPressCallback()) { //process the long click action immediately removeLongPressCallback(); checkForLongClick( 0/* send immediately */, x, y, TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS); } break; } return true; } return false; } Copy code


  • Don't ask me why some lines of code are not commented. You can't deduct every line of code very carefully and enter the details of the code. You must first understand the core process. If you are interested, you can read it through.
  • I left a comment on the code that I think is necessary. It may be an explanation or a hint, or I think it is more interesting for everyone to learn. Therefore, it is best that you can follow my comments and walk through it yourself .

Let us answer the above question

  • Question 1: Sliding or clicking, the user needs to do a delay operation when clicking, and if the longClick is executed, I let mHasPerformedLongPress=true, indicating that the longClick event is experienced, then the onClick in the up event will not be executed, in the move There will also be ambiguities when the current slide and long press will conflict. At this time, there will be a variable of mTouchSlop, the minimum slide distance, to determine which behavior is closer. The conflicts in View are mainly sliding and long press, and there are very interesting treatments in down and move.

  • Question 2: Click and longClick are implemented through post, and there will be a messageQueue inside, which is implemented through handler. If I let you implement a View by yourself, how would you handle the onClick event by yourself? Many people say that this View can receive the down and up time. Yes, it is very simple, but how do you solve the conflict with longClick? How would you implement longClick? You know, longClick will happen without move, and longClick will happen with move, right? With longClick, what should I do if I want to cancel? You are not placed in the message queue. If the parent View sends a cancel, how do you cancel it? What are the advantages of putting it in the queue? Will it be better? You can go and see for yourself.

  • Question 3: Cancel, a simple understanding, is a clear reset operation, which will remove the runnable of tap and longclick. I will analyze a library that will be used later.

Well, here, I have basically analyzed the event distribution for everyone, and then I will summarize it for you.

Event distribution summary

  • The order of event distribution is, activity---window---view

  • After the ViewGroup receives the event, it will call the dispatchTouchEvent of the ViewGroup to distribute, and the event is not intercepted by default. Internally, first, whether the child View has set the flag bit that prevents the parent ViewGroup from intercepting it, set it, intercepted=false, otherwise, check your own onIntercepted method, whether it needs to be intercepted, it is true if it is needed, and it is false if it is not needed, if intercepted= If it is true, it will be handled by itself, and it cannot be handled to the higher level. It can also be found that the onIntercept method is not called every time.

  • If the parent View does not intercept and the event is distributed, it will traverse its child View to see who can consume the event. If it can consume it, it will consume it. If it can't consume it, it will give it to the parent ViewGroup.

  • dispatchTouchEvent is called every time, onIntercept is not called every time, onTouchEvent is the actual behavior of final consumption, if you want to control the event distribution process, the core is to start with the first two methods.

  • What is the timing of calling each method? dispatchTouchEvent is used every time distribution. We can converge in this method and control all event distribution. It is the opening of event distribution. In this method, you can basically do whatever you want, including onIntercept and onTouchEvent. The onIntercept method is mainly used to replicate the interception logic to determine whether it needs to be intercepted. onTouchEvent is the behavior of a specific consumption event, often doing some gesture operations and so on. Suggestion: Try not to do interception and gestures in dispatchTouchEvent, put the responsibilities of the latter two methods in one place (not impossible), they are all mixed together, which is a bit disgusting.

Finally, you can consolidate it, or print the log information yourself to strengthen your study.