Event distribution mechanism
Two phases of the event distribution mechanism:
- Dispatch: Events are distributed from parent view to child view. After being intercepted, they are not transmitted and go back
- Traceback: Events traceback from the child view to the parent view, not after being consumed
Key methods:
ViewGroup.dispatchTouchEvent
Distribute events to subviewsViewGroup.onInterceptTouchEvent
If true is returned, the distribution event is intercepted, no longer passed, and the current view is enteredonTouchEvent
View.dispatchTouchEvent
Default event distribution, callonTouchEvent
View.onTouchEvent
Usually override this method to handle events, returning true to consume the event, not passing it, and returning false to trace backViewParent.requestDisallowInterceptTouchEvent(true)
You can ensure that events are not intercepted before they are distributed to subviews
Assuming the view level is A.B.C.D, the default procedure for event dispatch backtracking is:
A.dispatchTouchEvent
B.dispatchTouchEvent
C.dispatchTouchEvent
D.dispatchTouchEvent
D.onTouchEvent
C.onTouchEvent
B.onTouchEvent
A.onTouchEvent
Copy the code
Suppose B intercepts the event:
A.dispatchTouchEvent
B.dispatchTouchEvent -> B.onInterceptTouchEvent
B.onTouchEvent
A.onTouchEvent
Copy the code
Suppose C.onTouchEvent consumes the event:
A.dispatchTouchEvent
B.dispatchTouchEvent
C.dispatchTouchEvent
D.dispatchTouchEvent
D.onTouchEvent
C.onTouchEvent
Copy the code
Event distribution mechanism pseudocode:
class Activity {
fun dispatchTouchEvent(ev) {
if (parent.dispatchTouchEvent(ev)) {
return true
}
return onTouchEvent(ev)
}
fun onTouchEvent(ev):Boolean {...}
}
class ViewGroup : View {
fun dispatchTouchEvent(ev) {
var handled = false
if(! onInterceptTouchEvent(ev)) { handled = child.dispatchTouchEvent(ev) }return handled || super.dispatchTouchEvent(ev)
}
fun onInterceptTouchEvent(ev):Boolean{... }fun onTouchEvent(ev):Boolean {...}
}
class View {
fun dispatchTouchEvent(ev) {
var result = false
if (handleScrollBarDragging(ev)) {
result = true
}
if(! result && mOnTouchListener.onTouch(ev)) { result =true
}
if(! result && onTouchEvent(ev)) { result =true
}
return result
}
fun onTouchEvent(ev):Boolean {...}
}
Copy the code
ViewGroup. DispatchTouchEvent source code analysis
- start:
ACTION_DOWN
The event starts a new sequence of events that clears the previous touch state - intercept:
2.1. TheACTION_DOWN
Event If no subview currently consumes events, the event sequence has been intercepted
2.2. If an event is not intercepted and the subview does not apply for an interception prohibition, use onInterceptTouchEvent to intercept the event - Distribute: If the event is not intercepted or cancelled, the subview distributes the event and looks for the touch target of the current event 3.1. The view touch target that can consume the current event is found in the Touch target linked list -> mark it as the current touch target and delay until Step 4 to distribute the event to it 3.2. A view that is not in the Touch target list consumes the event -> marks it as the current touch target and sets it to the touch target list header 3.3. The view consuming the current event was not found, but the Touch target list is not empty -> Marks the end of the touch target list as the current touch target
- Distribution: If the touch target list is not empty, traverse the touch target chain to try to pass the event or cancel the touch target (the event is blocked)
- Backtracking: If the touch target list is empty (there are currently no subviews consuming the event sequence), the event is forwarded to the base class dispatchTouchEvent for processing
Note: the TouchTarget (ViewGourp.TouchTarget) describes a touched child view and its captured pointer ids
public boolean dispatchTouchEvent(MotionEvent ev) {
// omit code...
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 1. The 'ACTION_DOWN' event starts a new sequence of events and clears the previous touch state...
}
// omit code...
final boolean intercepted;
/ / 2. Intercept
if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! =null) {
final booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! =0;
if(! disallowIntercept) {If the event is not intercepted and the subview does not apply for blocking prohibition, use onInterceptTouchEvent to intercept the event
intercepted = onInterceptTouchEvent(ev);
// omit code...
} else {
intercepted = false; }}else {
// 2.1. Non-action_down events If there are currently no sub-view consumption events, the event sequence has been intercepted
intercepted = true;
}
// omit code...
if(! canceled && ! intercepted) {// omit code...
// 3. Dispatch: If the event is not intercepted or cancelled, the subview dispatches the event and looks for the touch target of the current event
for (int i = childrenCount - 1; i >= 0; i--) {
// omit code...
newTouchTarget = getTouchTarget(child);
if(newTouchTarget ! =null) {
// 3.1. The view touch target that can consume the current event is found in the Touch target list -> mark it as the current touch target and defer sending the event to it until Step 4
// omit code...
break;
}
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// omit code...
// 3.2. A view not in the Touch target list consumes an event -> marks it as the current touch target and sets it as the touch target list header
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
// omit code...
}
if (newTouchTarget == null&& mFirstTouchTarget ! =null) {
// 3.3. The view consuming the current event was not found, but the Touch target list is not empty -> Mark the end of the touch target list as the current touch target
newTouchTarget = mFirstTouchTarget;
while(newTouchTarget.next ! =null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
// omit code...
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
Backtracking: If the touch target list is empty (there are currently no subviews consuming the event sequence), the event is forwarded to the base class dispatchTouchEvent
handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
} else {
// omit code...
// 4. Distribution: The touch target list is not empty, then the touch target chain is traversed to try to pass the event or cancel the touch target (the event is blocked)
TouchTarget target = mFirstTouchTarget;
while(target ! =null) {
final TouchTarget next = target.next;
// omit code...
if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {
handled = true;
}
// omit code...target = next; }}// omit code...
}
// omit code...
return handled;
}
Copy the code
View.dispatchTouchEvent and view. onTouchEvent source code analysis
- Scroll bars consume mouse events
- OnTouchListener consumes the touch event
- OnTouchEvent consumes touch events
- TouchDelegate consumes touch events
public boolean dispatchTouchEvent(MotionEvent event) {
// omit code...
boolean result = false;
// omit code...
if (onFilterTouchEventForSecurity(event)) {
// The scroll bar consumes mouse events
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
// OnTouchListener consumes the touch event
ListenerInfo li = mListenerInfo;
if(li ! =null&& li.mOnTouchListener ! =null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
// View's default event handling logic, where events may be set to be consumed by the TouchDelegate
if(! result && onTouchEvent(event)) { result =true; }}// omit code...
return result;
}
public boolean onTouchEvent(MotionEvent event) {
// omit code...
if(mTouchDelegate ! =null) {
// TouchDelegate consumes touch events
if (mTouchDelegate.onTouchEvent(event)) {
return true; }}// omit code...
return false;
}
Copy the code