This is the first in a series of articles on Android Touch events.

  1. “Recursion” and “Return” of Android Touch Event Distribution (1)
  2. “Recursion” and “Return” of Android Touch Event Distribution (II)

The big leader will go through a “handing” process when arranging tasks: the big leader first tells the task to the small leader, and then the small leader tells the task to Xiao Ming. There may also be a “return” process: Xiaoming tells the small leader that he can’t do it, and the small leader tells the big leader that he can’t finish the task. And then, there was no and…

Android touch events are similar to how a leader arranges tasks, and also undergo “recursion” and “return.” This article will try to read the source code to analyze the recursive process of ACTION_DOWN events.

(PS: the bold italics in the following words represent the inner play to guide the source code reading)

Distribute the touch event starting point

Write a demo that includes viewgroups, views, and activities, and log all touch related methods. When touch events occur, the Activity. DispatchTouchEvent () is always the first to be called, with the method of:

public class Activity{
    private Window mWindow;
    
    //' distribute touch events'
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        //' Let PhoneWindow help distribute touch events'
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
    
    //' Get PhoneWindow object '
    public Window getWindow(a) {
        return mWindow;
    }
    
    final void attach(...). {.../ / 'structure PhoneWindow'
        mWindow = new PhoneWindow(this, window, activityConfigCallback); . }}Copy the code

The Activity passes the event to the PhoneWindow:

public class PhoneWindow extends Window implements MenuBuilder.Callback {
    //' Top view of a window '
    private DecorView mDecor;
    
    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        //' pass the touch event to the DecorView for distribution '
        returnmDecor.superDispatchTouchEvent(event); }}//'DecorView inherits from ViewGroup'
public class DecorView extends FrameLayout implements RootViewSurfaceTaker.WindowCallbacks{
    public boolean superDispatchTouchEvent(MotionEvent event) {
        / / 'event was eventually ViewGroup. DispatchTouchEvent () distribute touch events'
        return super.dispatchTouchEvent(event); }}Copy the code
  • PhoneWindowContinue passing events toDecorView, and finally calledViewGroup.dispatchTouchEvent()
  • Here’s a quick summary: Touch events are passed from the Activity through the PhoneWindow to the top-level view, DecorView. DecorView calls the ViewGroup. DispatchTouchEvent ().

“Delivery” of Touch Events

  • Based on the analysis ofThe View map”, also encountered the “dispatchXXX” functionViewGroup.dispatchDraw(), which is used to traverse the children and trigger them to draw themselves.thedispatchTouchEvent()Will it also traverse the children and pass touch events to them?Take a look at the source code with this question:
public abstract class ViewGroup extends View implements ViewParent.ViewManager {
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if(! canceled && ! intercepted) { ...//' traversal the child '
            for (int i = childrenCount - 1; i >= 0; i--) {
                //' Traverse the child in index order or custom draw order '
                final int childIndex = customOrder
                      ? getChildDrawingOrder(childrenCount, i) : I;
                final View child = (preorderedList == null)? children[childIndex] : preorderedList.get(childIndex); .//' Skip if the child is not in the touch area '
                if(! canViewReceivePointerEvents(child) || ! isTransformedTouchPointInView(x, y, child,null)) {
                      ev.setTargetAccessibilityFocus(false);
                      continue; }...//' convert touch coordinates and distribute to children (child argument is not null)'
                if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                    // The code here is also critical}... }}if (mFirstTouchTarget == null) {
                // The code here is also crucial
        } else {
            // The code here is also crucial}}private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final booleanhandled; .//' Do the necessary coordinate conversion and then distribute the touch event '
        if (child == null) {
            // The code here is also critical
        } else {
            //' convert the ViewGroup coordinate system to its child's coordinate system (move the origin from the top left corner of the ViewGroup to the top left corner of the child) '
            final float offsetX = mScrollX - child.mLeft;
            final float offsetY = mScrollY - child.mTop;
            transformedEvent.offsetLocation(offsetX, offsetY);
            if (! child.hasIdentityMatrix()) {
                transformedEvent.transform(child.getInverseMatrix());
            }
            //' Distribute touch events to children 'handled = child.dispatchTouchEvent(transformedEvent); }...returnhandled; }}Copy the code

He was right! Parent control in ViewGroup. DispatchTouchEvent () will traverse the child and will be distributed to be touch events point of child controls, if child controls and children, touch events “pass” will be continuing, until the leaf node. The final leaf of type View calls view.dispatchTouchEvent () :

public class View implements Drawable.Callback.KeyEvent.Callback.AccessibilityEventSource {
    public boolean dispatchTouchEvent(MotionEvent event) {...if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            //'1. Notify touch listener OnTouchListener'
            ListenerInfo li = mListenerInfo;
            if(li ! =null&& li.mOnTouchListener ! =null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            / / '2. Call onTouchEvent ()'
            //' onTouchEvent() will only be called if onTouchListener.onTouch () returns false '
            if(! result && onTouchEvent(event)) { result =true; }}...//' Return value is the return value of onTouch() or onTouchEvent() '
        return result;
    }
    
    ListenerInfo mListenerInfo;
    
    //' listener container class '
    static class ListenerInfo {...privateOnTouchListener mOnTouchListener; . }//' Set touch listener '
    public void setOnTouchListener(OnTouchListener l) {
        //' store listeners in listener container '
        getListenerInfo().mOnTouchListener = l;
    }
    
    //' get listener management instance '
    ListenerInfo getListenerInfo(a) {
        if(mListenerInfo ! =null) {
            return mListenerInfo;
        }
        mListenerInfo = new ListenerInfo();
        returnmListenerInfo; }}Copy the code
  • View.dispatchTouchEvent()Is the end of passing touch events and the beginning of consuming touch events.
  • The consumption of touch events is marked by invocationOnTouchListener.onTouch()orView.onTouchEvent(), the former has a higher priority than the latter. Only if there is no settingOnTouchListeneroronTouch()returnfalseWhen,View.onTouchEvent()Before it gets called.
  • At this point, draw a diagram summarizing the “delivery” of touch events:

  • The N following the ViewGroup layer in the figure indicates that there may be multiple ViewGroup layers between the Activity layer and the View layer.
  • There are three levels from top to bottom in the diagram, and touch events start at the top and move down the arrow.
  • Another way of handling touch events has been omitted for simplicity:OnTouchListener.onTouch
  • The passing of a graphical touch event is just one of many passing scenarios: the clicked View is nested within a ViewGroup, which is inside an Activity.

“Return” of touch Events

The reason a touch event returns after a touch event is that the function that distributes the touch event has not finished executing. Look at the source code again in the opposite direction of the call chain:

public class View{
    //' Return true to indicate that the touch event is consumed, otherwise it is not consumed '
    public boolean onTouchEvent(MotionEvent event) {...if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            //' omits the default handling of different touch events'.//' The touch event has been consumed as long as the control is clickable '
            return true;
        }
        //' Do not consume touch events if the control is not clickable '
        return false; }}Copy the code

View.dispatchtouchevent () does not finish executing after calling view.onTouchEvent (). The return value of view.onTouchEvent () affects the return value of view.dispatchTouchEvent () :

public class View  {
    public boolean dispatchTouchEvent(MotionEvent event) {...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; }}//' Returns the Boolean value of whether the current View consumes the touch event '
        return result;
    }
Copy the code

. Similarly, ViewGroup dispatchTouchEvent () calls for the dispatchTouchEvent () is not performed, The dispatchTouchEvent () returns a value will affect ViewGroup. DispatchTouchEvent () returns a value:

public abstract class ViewGroup extends View implements ViewParent.ViewManager {
    //' touch the header '
    privateTouchTarget mFirstTouchTarget; .@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if(! canceled && ! intercepted) { ...//' traversal the child '
            for (int i = childrenCount - 1; i >= 0; i--) {
                ...
                //' convert touch coordinates and distribute to children (child argument is not null)'
                if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                      ...
                      //' A child is willing to consume touch events and insert them into the "touch chain"
                      newTouchTarget = addTouchTarget(child, idBitsToAssign);
                      //' Indicates that the touch event has been distributed to the new touch target '
                      alreadyDispatchedToNewTouchTarget = true;
                      break; }... }}if (mFirstTouchTarget == null) {
                //' If no child wants to consume touch events, consume them themselves (child argument null)'
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
        } else {
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                //' traverse the touch chain to distribute touch events to all children who want to receive them '
                while(target ! =null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        //' Return true if the touch event has already been distributed to the new touch target '
                        handled = true;
                    } else {
                        //' The code here is very important, continue to wait for the next analysis. '} predecessor = target; target = next; }}...//' Returns a Boolean value for whether the touch event was consumed by the child or itself '
        return handled;
    }
    
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final booleanhandled; .// Perform any necessary transformations and dispatch.
        //' Do the necessary coordinate conversion and then distribute the touch event '
        if (child == null) {
            //'ViewGroup children don't want to consume touch events so it treats itself as a View (call view.dispatchTouchEvent ())'
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            // Distribute touch events to children}...return handled;
    }
    
    /** * Adds a touch target for specified child to the beginning of the list. * Assumes the target child is not already Present. * 'Add View to touch chain header' *@param child  View
     * @param pointerIdBits
     * @returnNew touch target */
    private TouchTarget addTouchTarget(View child, int pointerIdBits) {
        TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        returntarget; }}Copy the code
  • The code above fills in the foreshadows bought in the previous section. It turns out that when kids are willing to consume touch events,ViewGroupIt will be connected to the “touch chain”. If there is no node in the touch chain, it means that no child is willing to consume the eventViewGroupYou can only consume events yourself.ViewGroupisViewSubclasses, they consume touch events in exactly the same way, all throughView.dispatchTouchEvent()callView.onTouchEvent()orOnTouchListener.onTouch().
  • One more “back” step up the backtrace chain:
public class Activity {
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            // If there are controls in the layout willing to consume touch events, return true and onTouchEvent() will not be called
            return true;
        }
        returnonTouchEvent(ev); }}Copy the code

View, ViewGroup, and Activity, although they have different logic for distributing touch events, have the same basic structure as this code:

/ / ""
if(Distribute the event to the child){return if the child consumes the event (pass up the fact that the touch event was consumed)}/ / "to"If the child does not consume events, consume events themselvesCopy the code

The call to “distribute events to children” means “pass”, passing touch events to the lower level. The return of the “distribute events to children” function means “return”, which traces the consumption results of the touch events back to the upper level for further action.

In the same way, summarize the “return” of touch events with pictures:

  • This figure is a completion of the scenario described in Figure 1. The black line in the figure represents the transfer path of the touch event, and the gray line represents the trace path of the touch event.
  • becauseView.onTouchEvent()Returns true to consume the touch event, soViewGroup.onTouchEvent()As well asActivity.onTouchEvent()Will not be called.

  • This diagram is an extension of the scenario described in Figure 1. The black line in the figure represents the transfer path of the touch event, and the gray line represents the trace path of the touch event.
  • The scene corresponding to the diagram is: clickedViewDoes not consume touch events whileViewGroupinonTouchEvent()In returntrueConsume touch events yourself.

  • This diagram is an extension of the scenario described in Figure 1. The black line in the figure represents the transfer path of the touch event, and the gray line represents the trace path of the touch event.
  • The scene corresponding to the diagram is: clickedViewandViewGroupBoth do not consume touch events, and only by the endActivityTo consume touch events.

conclusion

  • ActivityWhen the touch event is received, it is passed toPhoneWindowAnd pass it toDecorViewBy theDecorViewcallViewGroup.dispatchTouchEvent()Distribute from the top downACTION_DOWNTouch events.
  • ACTION_DOWNEvents throughViewGroup.dispatchTouchEvent()fromDecorViewThrough a number ofViewGroupIt goes on and on until it gets thereView.View.dispatchTouchEvent()Is invoked.
  • View.dispatchTouchEvent()Is the end of the delivery event, the beginning of the consumption event. It will be calledonTouchEvent()orOnTouchListener.onTouch()To consume events.
  • Each level can be passed inonTouchEvent()orOnTouchListener.onTouch()returntrueTo tell their parent control that the touch event was consumed. Only if the underlying control does not consume touch events does the parent control have a chance to consume them.
  • The delivery of touch events is a top-down “recursive” process from the root view, while the consumption of touch events is a bottom-up “recursive” process.

Reading this, you may still have a lot of questions about the touch event:

  1. ViewGroupDoes the layer have a way to block touch events?
  2. ACTION_DOWNIt’s just the start of the touch sequence, backwardACTION_MOVE,ACTION_UP,ACTION_CANCELHow is it transmitted?

These questions will continue in the next article.