The following is an example of event passing after clicking on a view.
First, analyze the dispatchTouchEvent() method in the view, which is the first method to execute by clicking on the view.
public boolean dispatchTouchEvent(MotionEvent event) { if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) { return true; } return onTouchEvent(event);}Note: It contains two callback functions onTouch() and onTouchEvent(); if the control is bound to the OnTouchListener and the control is enabled, then execute the onTouch() method. If the method returns true, it means that the touch event has been consumed by the OnTouchListener listener and will not be distributed downwards; but if false returns, it means that it has not been consumed, and continue to be distributed downwards to the onTouchEvent() of the control for processing.
Then analyze the onTouchEvent() method and perform further touch event processing.
if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { switch (event.getAction()) { case MotionEvent.ACTION_UP: ..... performClick(); //Respond to click event break; case MotionEvent.ACTION_DOWN: .... break; case MotionEvent.ACTION_CANCEL: .... break; case MotionEvent.ACTION_MOVE: ..... break; } return true; } return false;If the control is clickable or long_clickable, then it can respond to the corresponding event and return true after the response is completed to continue the response. For example, when clicking an event, first respond to ACTION_DOWN, then break and return true, then raise your hand, then distribute it from dispatchTouchEvent(), and then respond to ACTION_UP, and performClick() will respond to click event.
Respond to click events
public boolean performClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); if (mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); mOnClickListener.onClick(this); return true; } return false;}Execute mOnClickListener.onClick(this); that is, the onClick() function that binds the listener.
Key points:
How to use the difference between onTouch and onTouchEvent?
answer:
When the view control receives a touch event, if the control is bound to an onTouchListener listener and the control is enable, then execute the onTouch() method. If true, the touch event has been consumed and will no longer be passed down; if false is returned, then continue to call the onTouchEvent() event.
After the Android Touch event is passed to the DecorView (a FrameLayout) at the top of the Activity, it will be passed layer by layer through the ViewGroup to the view tree, and finally pass the event to the actual received View. Here are some important methods.
dispatchTouchEvent
When an event is passed to a ViewGroup, dispatchTouchEvent will be called. The code has been deleted
public boolean dispatchTouchEvent(MotionEvent ev) { boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; // Attention 1: Clear some states when pressed if (actionMasked == MotionEvent.ACTION_DOWN) { cancelAndClearTouchTargets(ev); // Note this method resetTouchState(); } // Attention 2: Check whether the final boolean intercepted needs to be intercepted; //If you just pressed or have a child View to handle it if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { intercepted = false; } } else { // It is not the beginning of an action sequence and there is no subView to handle it. Intercepted directly = true; } //The event is not cancelled and is not intercepted by the current ViewGroup. Go to see if there is a subView taken over if (!canceled && !intercepted) { //If this is the beginning of a series of actions or there is a new Pointer pressed, we need to find the subView that can handle this Pointer if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { final int actionIndex = ev.getActionIndex(); // always 0 for down //The above limitation of touch point 32 is that it causes final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) : TouchTarget.ALL_POINTER_IDS; final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); //Sort all childViews of the current ViewGroup, and place them in the upper layer to start. Final ArrayList<View> preorderedList = buildOrderedChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = customOrder ? getChildDrawingOrder(childCount, i) : i; final View child = (preorderedList == null) ? children[childIndex] : preorderedList.get(childIndex); // canViewReceivePointerEvents visible View can accept events // isTransformedTouchPointInView Calculate whether it falls on the click area if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; } // Can handle whether the View of this Pointer has processed the previous Pointer, then use 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; } } //Attention 3: Send it directly to the child View 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 child View receiving the event has been found before. If it is NULL, it means that no child View is taken over. The current ViewGroup needs to handle if (mFirstTouchTarget == null) { // ViewGroup handled = dispatchTransformedTouchEvent(ev, cancelChild, null, TouchTarget.ALL_POINTER_IDS); } else { if(alreadyDispatchedToNewTouchTarget) { //ignore some code if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } } } return handled;}The Attention in the above code will be involved in the later part, so pay attention to it.
It should be pointed out here that different Pointers in a series of actions can be assigned to different Views to respond. ViewGroup will maintain a PointerId and a list of TouchTargets that handle Views. A TouchTarget represents a subView that can handle Pointers. Of course, a View can handle multiple Pointers, such as both fingers are in a subView area. TouchTarget uses an int internally to store the PointerId it can process and an int32 bits, which is why the upper layer can only allow up to 32 points of touch at the same time.
Let’s look at the code at Attention 3. We often say that if the dispatchTouchEvent of the view returns false, then it cannot be followed by the action. Why is this? Because if false is returned at Attention 3, then it will not be recorded in the TouchTarget, ViewGroup believes that you do not have the ability to handle this event.
You can see here that ViewGroup really handles events in dispatchTransformedTouchEvent, follow in and take a look:
dispatchTransformedTouchEventprivate boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { //No subclass processing, then leave it to the viewgroup for processing if (child == null) { handled = super.dispatchTouchEvent(transformedEvent); } else { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; transformedEvent.offsetLocation(offsetX, offsetY); if (! child.hasIdentityMatrix()) { transformedEvent.transform(child.getInverseMatrix()); } handled = child.dispatchTouchEvent(transformedEvent); } return handled;}You can see that no matter what, the dispatchTouchEvent of the View will be called, which is where the click event is really handled.
dispatchTouchEvent public boolean dispatchTouchEvent(MotionEvent event) { if (onFilterTouchEventForSecurity(event)) { //Get the onTouch event of the View first, if onTouch returns True 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; }The onTouch event we set for the View is at a higher priority. If the onTouch execution returns true, then the onTouchEvent of the view will not go to the view. Some of our click events are handled in the onTouchEvent, which is why the onTouch returns true, and the click-related events of the view will not be processed.
A brief summary of this process
When ViewGroup accepts events passed down by superiors, if it is the beginning of a series of Touch events (ACTION_DOWN), ViewGroup will first check whether it needs to intercept the event (onInterceptTouchEvent, the default implementation of ViewGroup directly returns false to indicate that it does not intercept), and then ViewGroup traverses all its views. Find the View you are currently clicking and immediately call the dispatchTouchEvent of the target View. If the dispatchTouchEvent of the target View returns false, then it thinks that the target View is only in that position. It does not want to accept this event, but just wants to make a View quietly (I quietly watch you pretend*). At this time, ViewGroup will also go to the dispatchTouchEvent, Done!