preface

View as an Android application and user interaction entrance, in addition to display View, but also undertake the task of processing user operations, such as user click, long press, slide events. The mechanism for handling click events is the View’s event distribution mechanism.

View event distribution mechanism

When the user clicks on the screen, a click event is generated, and this event information is encapsulated in a class called MotionEvent. After the event is generated, the Android system will pass the event to the View hierarchy, and then the MotionEvent will be passed and distributed within the View hierarchy.

There are three important methods designed in the View distribution mechanism, and these three methods take on the task of the View event mechanism. They are:

  • DispatchTouchEvent (MotionEvent EV) – Dispatches events.
  • OnInterceptTouchEvent (MotionEvent EV) – Used to intercept events, called from dispatchTouchEvent, which exists in the ViewGroup.
  • OnTouchEvent (MotionEvent EV) – Used to handle events

View Click event occurs

When a click event occurs, the event is first passed to the current Activity, which calls the Activity’s dispatchTouchEvent method.

public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); If (getWindow().superdispatchTouchEvent (ev)) {return true; } // Call the Activity onTouchEvent method return onTouchEvent(ev); } //PhoneWindow public Boolean superDispatchTouchEvent(MotionEvent event) {return mDecor.superDispatchTouchEvent(event); } public Boolean superDispatchTouchEvent(MotionEvent event) {// Call the dispatchTouchEvent method return in ViewGroup super.dispatchTouchEvent(event); }Copy the code

As you can see, when an event is generated, the event is first intercepted in the current Activity. If the current Window does not intercept the event, the Activity’s onTouchEvent method is called.

Event distribution start

Also, we can see that the superDispatchTouchEvent method of the DecorView is called in the PhoneWindow. This method in turn calls the dispatchTouchEvent method. This is the start of event distribution at the View level.

You can see this in the code above. At the View level, event processing begins with the dispatchTouchEvent method of the ViewGroup. So let’s start from here.

public boolean dispatchTouchEvent(MotionEvent ev) { //...... boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; ActionMasked == motionEvent.action_down) {if (actionMasked == motionEvent.action_down) { Because the program may change due to switching, ANR, or some other state. Framework has been deleted up and cancel events cancelAndClearTouchTargets (ev); // resetTouchState(); } // Check for interception. // Check for interception. Final Boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! = null) {/ / the sign is also associated with requestDisallowInterceptTouchEvent, Final Boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT)! = 0; if (! 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; } / /... // 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) {/ / not intercept events, continue to distribute events View childWithAccessibilityFocus = ev. IsTargetAccessibilityFocus ()? findChildWithAccessibilityFocus() : null; 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 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); // 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); / / get click on the word View within the scope of the final View child = getAndVerifyPreorderedView (preorderedList, children, childIndex); if (childWithAccessibilityFocus ! = null) { if (childWithAccessibilityFocus ! = child) { continue; } childWithAccessibilityFocus = null; i = childrenCount - 1; } if (! canViewReceivePointerEvents(child) || ! isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; // getTouchTarget 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); 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; } / /... } if (preorderedList ! = null) preorderedList.clear(); } / /... } } // Dispatch to touch targets. if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary // Distribute the event if the parent intercepts the event. Distributed to the parent container handled = dispatchTransformedTouchEvent (ev, canceled, null, TouchTarget ALL_POINTER_IDS); } else { TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target ! = null) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; // If the parent container does not intercept, Distribute events to the corresponding View if son (dispatchTransformedTouchEvent (ev, cancelChild, target. The child, target pointerIdBits)) {handled = true; } / /... predecessor = target; target = next; }} / /... return handled; }Copy the code

As can be seen from the code, there are two cases of event distribution. One is that if the parent container does not intercept the event, it will distribute the event to the corresponding child View. The other is for the parent container to intercept the event and handle the event itself. In the first case, the ViewGroup iterates through the child View to determine whether the child View is in the clicked area, and if so, sends the event to the child View to distribute. In the second case, the ViewGroup intercepts the event. Eventually the two situations will call dispatchTransformedTouchEvent method. Next, analyze the effect of this method.

/ / eventually distribute events methods private Boolean dispatchTransformedTouchEvent (MotionEvent event, Boolean cancel, View the child, int desiredPointerIdBits) { final boolean handled; / /... Perform any necessary doubling and dispatch. If (child == null) {// Perform any necessary doubling and dispatch 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()); } // The parent does not intercept the event and dispatches it to the child View to handle = child.dispatchTouchEvent(transformedEvent); } // Done. transformedEvent.recycle(); return handled; }Copy the code

As you can see from the code, this implements the two cases of the event distribution in ViewGroup, parent container interception and non-interception. In the case of interception the child is null and the View’s dispatchTouchEvent method is called. Invoke child’s dispatchTouchEvent method for non-intercepting cases. Let’s look at the dispatchTouchEvent method in the View again.

public boolean dispatchTouchEvent(MotionEvent event) { //....... Is omitted the if part of the code (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

In the View dispatchTouchEvent method, if OnTouchListener is not null, the onTouch method of OnTouchListener is called first and returns true indicating that the event is consumed. Otherwise the onTouchEvent method is called. We’ll just analyze the onTouchEvent method here.

public boolean onTouchEvent(MotionEvent event) { //...... final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE; // If the View can be clicked, Click event if (clickable | | (viewFlags & TOOLTIP) = = TOOLTIP) {switch (action) {/ / handle UP event case MotionEvent. ACTION_UP: mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; if (! Clickable) {// Cancel the long press event removeLongPressCallback(); break; } boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) ! = 0; if ((mPrivateFlags & PFLAG_PRESSED) ! = 0 | | prepressed) {/ / get focus Boolean focusTaken = false; if (isFocusable() && isFocusableInTouchMode() && ! isFocused()) { focusTaken = requestFocus(); } if (! mHasPerformedLongPress && ! mIgnoreNextUpEvent) { // This is a tap, so remove the longpress check removeLongPressCallback(); if (! focusTaken) { if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (! Post (mPerformClick) {// Handle the click event performClick(); } } } } mIgnoreNextUpEvent = false; break; ACTION_DOWN: mHasPerformedLongPress = false; if (! clickable) { checkForLongClick(0, x, y); break; } if (isInScrollingContainer) { mPrivateFlags |= PFLAG_PREPRESSED; if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } mPendingCheckForTap.x = event.getX(); mPendingCheckForTap.y = event.getY(); postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } else { // Not inside a scrolling container, so show the feedback right away setPressed(true, x, y); checkForLongClick(0, x, y); } break; case MotionEvent.ACTION_CANCEL: //...... break; case MotionEvent.ACTION_MOVE: //...... break; } return true; } return false; }Copy the code

As you can see from the code, onTouchEvent handles the event that was distributed. The event types are ACTION_UP, ACTION_DOWN, ACTION_MOVE, and ACTION_CANCEL. OnTouchEvent handles events if the View is clickable. The View is clickable when it registers OnCLickListener and onLongClickLinster. You can see that the click event is handled in ACTION_UP by calling the perfromClick method. When the click event is called, the long press event has not reached the long press time. The long-press event is implemented in ACTION_DOWN, sending a delay message through the checkForLongClick method, and calling the long-press event when the long-press time is reached.

Principle of event distribution

After the above analysis, now summarize the principle of View event distribution. The event starts from the Activity into the PhoneWindow and ends up in the View hierarchy. Distributed from the top-level View(DecorView) in the View hierarchy.

  1. When a click event is generated, there is a top-level ViewGroup that distributes the event.
  2. By calling the dispatchTouchEvent method, the View’s dispatchTouchEvent method is called when the parent intercepts the event, which in turn calls either the onTouchEvent method or the onTouch method of the OnTouchListener.
  3. Otherwise, the dispatchTouchEvent method of the child View is called. If the subview is of type ViewGroup, continue to distribute events as in Step 1. Otherwise call the View’s dispatchTouchEvent method.

conclusion

View’s event distribution mechanism handles events generated by the user touching the screen. Generally speaking, through the event distribution of View, we often need to deal with DOWN, MOVE, UP events. By implementing these types of events, different interactive operations can be realized, thus enriching the interactive experience between View and user.