The basic concept

  1. All Touch events are encapsulated with motionEvents, including the Touch type, position (absolute position relative to the screen, relative position relative to the View), time, history, and finger number (multi-touch), etc.
  2. There are many types of events. The commonly used event types are: ACTION_DOWN, ACTION_UP, ACTION_MOVE, ACTION_CANCEL, etc.
  3. There are three types of event handling: event passing, dispatchTouchEvent(); OnInterceptTouchEvent (); Consume, onTouchEvent(), OnTouchListener;

Delivery process

Online there are a lot of information regarding the distribution process of event code tracking, such as www.jianshu.com/p/38015afcd.

Interested students can refer to it and go through it in detail. Here I make a textual description:

Passing detail description

  1. Events from the Activity. DispatchTouchEvent (), In turn by getWindow (). SuperDispatchTouchEvent (event), mDecor. SuperDispatchTouchEvent (event), From Activity-> PhoneWindow ->DecorView, DecorView is the top-level ViewGroup of the entire ViewTree;
  2. In the entire ViewGroup, events are passed from the top level to the child views;
  3. The parent ViewGroup can use onInterceptTouchEvent() to prevent events from being passed down.
  4. If not, the child View can consume (process) the event via onTouchEvent();
  5. If the event is not intercepted from the top down, and the bottom child View does not consume the event, the event will be passed back up, and the parent ViewGroup can consume the event in onTouchEvent(). If it is still not consumed, Finally, the Activity’s onTouchEvent() function;
  6. The underlying View has priority consumption of events;
  7. If the View does not consume ACTION_DOWN, subsequent events from the click are not passed;
  8. If the View consumes ACTION_DOWN, subsequent events from that click will be given directly to the View. In this case, subsequent events are ACTION_MOVE and ACTION_UP events. At this point, the parent ViewGroup’s onIntercept function will still be called, it can still intercept, but its own onIntercept will not be called;
  9. The child View can call getParent in onTouchEvent (). RequestDisallowInterceptTouchEvent (true), The parent ViewGroup’s onIntercept will not be called in subsequent events;
  10. If the first event, ACTION_DOWN, is intercepted by the parent ViewGroup, the child View will not get the opportunity to consume the event;
  11. OnTouchListener consumes events prior to onTouchEvent();
  12. Consumption means that the corresponding function returns true;
  13. A ViewGroup has an onIntercept method, but a View does not.
  14. All event processing starts with ACTION_DOWN and ends with ACTION_UP or ACTION_CANCEL. ACTION_UP is the end of normal event processing logic. ACTION_CANCEL is actively issued by the parent ViewGroup. When the parent ViewGroup blocks an event other than ACTION_DOWN, it sends an ACTION_CANCEL event to the child View that is consuming ACTION_DOWN and waiting for a subsequent event, notifying the child View to end its wait for the event.

TouchTarget

With regard to points 7 and 8, how does a ViewGroup quickly hit a dispatchTouchEvent and distribute it to its child View? This is done through the TouchTarget structure.

private static final class TouchTarget { private static final int MAX_RECYCLED = 32; Private Static Final Object sRecycleLock = New Object[0]; Private static TouchTarget sRecycleB private static TouchTarget sRecycleBin; Private static int sRecycledCount; public static final int ALL_POINTER_IDS = -1; // all ones public View child; Public int pointerIdBits; public int pointerIdBits; Public TouchTarget next; privateTouchTarget() {}... }Copy the code

A variable is maintained in ViewGroup: MFirstTouchTarget, which is a linked list maintained in a ViewGroup that records the child views of the current sequence of events that respond to it. MFirstTouchTarget points to the head of the list.

Let’s look at the assignment of mFirstTouchTarget:

// This happens in the dispatchTouchEvent method in ViewGroupif (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { ... mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); newTouchTarget = addTouchTarget(child, idBitsToAssign); } // When the target child View of the response event is added to the list, 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

Look again at mFirstTouchTarget in the dispatchTouchEvent method:

 	@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        
        if(onFilterTouchEventForSecurity(ev)) { final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; 1, if the event is ACTION_DOWN event, reset touchTargets state, emits an ACTION_CANCEL event in cancelAndClearTouchTargets methodif(actionMasked == MotionEvent.ACTION_DOWN) { cancelAndClearTouchTargets(ev); resetTouchState(); } if one event is intercepted, the rest of the sequence will be intercepted. The onInterceptTouchEvent method will not be executed again. If the ACTION_DOWN event is intercepted, i.e. the onInterceptTouchEvent(ev) of the current ViewGroupreturn true; MFirstTouchTarget must be null, and subsequent events will not pass 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 {
                intercepted = true; } 3. If the event is neither cancelled nor intercept, traverse the sub-view for event distributionif(! canceled && ! intercepted) { ... } 4. If dispatchTouchEvent is returned during event distributionfalseOr if the current ViewGroup has no children, it will go this way. MFirstTouchTarget == null indicates that the child View does not consume the event, so no mFirstTouchTarget is assigned. Where child == null, the code further executes super.dispatchTouchEvent(event), the dispatchTouchEvent method in the Viewif (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else{5, mFirstTouchTarget! = null, indicating that the event is consumed by View. At this time, events will be successively distributed to the linked list View saved by mFirstTouchTarget. TouchTarget target = mFirstTouchTarget;while(target ! = null) { final TouchTarget next = target.next; . target = next; }}}if(! handled && mInputEventConsistencyVerifier ! = null) { mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1); }return handled;
    }
Copy the code

This place focuses on comment points 1, 2, 3, 4, and 5. Now let’s go back to seven, eight, two o ‘clock.

If ACTION_DOWN is not consumed by the View, subsequent events from the click are not passed. Obviously, if ACTION_DOWN is not consumed, it will not be saved to the TouchTarget list, and subsequent events will be distributed directly to this list.

If the View consumes ACTION_DOWN, subsequent events from that click will be given directly to the View. In this case, subsequent events are ACTION_MOVE and ACTION_UP events. At this point, the parent ViewGroup’s onIntercept function will still be called, it can still intercept, but its own onIntercept will not be called. This can be answered in comment 2, if the event is consumed, mFirstTouchTarget! = null, subsequent events can be distributed directly from the mFirstTouchTarget list, and subsequent events will skip the judgment intercepted, so their own onIntercept will not be called.

RecyclerView event delivery

Here, click Button in an Item in RecyclerView as an example:

Points under the Button

  1. Activity >phoneWindow–>ViewGroup–>ListView–> Botton –>phoneWindow–>ViewGroup–>ListView–> Botton
  2. Without interception, the event arrives at the button, creating a view list for event passing;
  3. Go to button’s dispatch method –>onTouch–> View is available –>Touch agent;

Move when you click the button

  1. Generate move event, RecyclerView will do move event interception;
  2. At this time, RecyclerView will consume the sliding event;
  3. Subsequent sliding events will be RecyclerView consumption;
  4. Button already handled the Down event and is now waiting for subsequent events. At this point, RecyclerView will issue a Cancel event to notify Button not to wait any longer

Finger raised in front of the establishment of a view linked list, RecyclerView parent view in obtaining events, will directly take the RecyclerView in the linked list for its event consumption

Interested students can take this step to track RecyclerView source code.

multi-touch

Multi-touch involves the handling of multiple finger click events, and two additional events are added here

  1. ACTION_POINTER_DOWN: Extra finger press (the View has been touched by other fingers before pressing)
  2. ACTION_POINTER_UP: A hand finger is lifted, but not the last one (there are still other fingers touching the View after lifting)

Event type: ACTION_POINTER_UP; active pointer index: 0; pointer: x: 200, y: 300, index: 0, id: 1; pointer: x: 300, y: 500, index: 1, id: 2

Multi-touch Touch event structure

  1. Touch events are grouped in sequence columns. Each group of events must start with ACTION_DOWN and end with ACTION_UP or ACTION_CANCEL.
  2. Like ACTION_POINTER_DOWN and ACTION_POINTER_UP and ACTION_MOVE, ACTION_POINTER_DOWN is only a part of the event sequence column and does not separate new event sequence columns.
  3. At the same time, a View either has no event sequence column or only one event sequence column;
  4. One of the problems to be solved by multi-touch is: the order of finger touch, finger differentiation, these two problems are distinguished by index and ID;
  5. Multi-touch problem 2: When you slide a finger in multi-touch, you need to know which finger is moving

Three types of multi-touch

  • Relay force only one pointer is active at a time, that is, the latest pointer. Typical :ListView, RecyclerView. Implementation: Record the newest pointer in ACTION_POINTER_DOWN and ACTION_POINTER_UP, and use this pointer to determine the position in subsequent ACTION_MOVE events.
  • Coordinate type All Pointers that touch the View work together. Typical :ScaleGestureDetector and the onScroll() method of GestureDetector. Implementation method: use the coordinates of all Pointers to update the focal coordinates of each DOWN, POINTER_DOWN, POINTER_UP, and UP events, and use the coordinates of all Pointers to determine the position of the MOVE event.
  • Each do not different things for their own war pointer, do not affect each other. Typical: support for multi-brush palette applications. Implementation method: Record the ID of each pointer in each DOWN and POINTER_DOWN event, and track them line by ID in MOVE event.

Sliding conflict handling

What is sliding conflict? Is the parent View and child View need to deal with sliding, such as the parent View need to slide around, child View need to slide up and down (ViewPager nested RecyclerView), a click on the event, who to deal with?

First we need to define the processing rules, and then we can implement the rules in the parent View’s onIntercept, the child View’s onTouchEvent and the parent View’s onTouchEvent function. For example, in the onIntercept of the parent View, if it is found to slide left or right, it is blocked, otherwise it is not blocked.

NestedScrollView nested RecyclerView is the same, NestedScrollView found is sliding up and down, directly intercept and process, RecyclerView has no chance to process.

Refer to the article

Piasy: Handling of event passing and sliding collisions

Jane books links