This article is edited and reprinted by love College platform. Please indicate the source

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 levelonTouchEventAll returned to thefalse, then the ActivityonTouchEventWill 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 hereWindowIs an abstract class, so what’s its implementation class? YesPhoneWindowWhy? Here you can read the above in detailWindowClass, as noted hereWindowThe only implementation ofandroid.view.PhoneWindowGood 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#setContentViewSet up theViewisDecorViewA child of the View. So far the event has arrivedDecorViewHere, due toDecorVieWIn theFrameLayoutAnd is the parentView, then conclude that eventually the event will pass toViewTo this point is not our focus, how does the event pass through the topViewPass-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
  1. Intercept condition: The event type isACTION_DOWN || mFirstTouchTarget ! = null;
  2. MFirstTouchTarget: Each start (ACTION_DOWN) are initialized tonull, when the event byViewGroupRefers to the child element of the successfully processed
  3. When the event byViewGroupIntercept when conditionmFirstTouchTarget ! = nullIf it does not hold, it is assumedACTION_MOVEandACTION_UPWhen the event arrives, because the first interception condition is not metonInterceptTouchEventCall 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.
  4. FLAG_DISALLOW_INTERCEPTFlag bit: Once this flag bit is set (requestDisallowInterceptTouchEvent),ViewGroupWill not be able to intercept exceptACTION_DOWNOther click events (ACTION_DOWNEvent resets the flag bit, invalidating the flag bit set in the child View.
  5. In the face ofACTION_DOWNThe event,ViewGroupIt always calls its ownonInterceptTouchEventMethod 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,ViewGroupWill be inACTION_DOWNThe state is reset when the event arrives, so the childViewcallrequestDisallowInterceptTouchEventDoes not affectViewGrouprightACTION_DOWNEvent handling.

Conclusion:

  1. ViewGroupDecide to intercept events (ACTION_DOWN), subsequent click events will be handed over to it by default and will not be calledonInterceptTouchEventMethods.
  2. FLAG_DISALLOW_INTERCEPTThe purpose of this sign is to makeViewGroupNo more intercepting events, provided of courseViewGroupDon’t interceptACTION_DOWNEvents.
  3. FLAG_DISALLOW_INTERCEPTIt 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 thatViewGroupDo not intercept the case, the event to the childViewThat is, the main call method isdispatchTransformedTouchEventInside, it actually calls the child element’sdispatchTouchEventMethod (available through the aboveSource – 7It can be seen from specific analysis that ifchild.dispatchTouchEvent(event)returntrue, thenmFirstTouchTarget(addTouchTargetMethod inside operation) is assigned and breaks out of the for loopmFirstTouchTargetAssignment, will affectViewGroupIntercept 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

mFirstTouchTargetIf it isnullAll 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:

  1. ViewGroupNo child elements;
  2. 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 onTouchthanonTouchEvent(event)High priority if setOnTouchListenerandmOnTouchListener.onTouchreturntruethenonTouchEvent(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:CLICKABLEandLONG_CLICKABLEAs long as there is atrue, then it will consume the event, i.eonTouchEventMethod returnstrue, the actual call method isperformClick();, called within itOnClickListener#onClickMethods.

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

Dear readers and friends, from today on our “Love School platform” will continue to launch the front and back end technology related to all kinds of knowledge points and difficult problems analysis, if you have the need, but we have no output of difficult problems, you can also give us a message! We will output according to your demand, thank you!