The last one talks about the basic concepts of Android event distribution, followed by the last one and the following one.
Start by reviewing the previous article
In the previous article, we talked about three important methods of event distribution for Android and the relationship between them. Here we review the process of event distribution for Activity.
1. First, when an event occurs, it is first passed to the Activity.
2. The dispatchTouchEvent method is called when the event is passed to the Activity, and if the current Activity does not intercept the event, it will call the dispatchTouchEvent method in its child View to loop until the child View intercepts the event.
3. If the current Activity intercepts this event, it will be judged that if OnTouchListener is set, onTouch will be invoked, otherwise OnTouchEvent will be invoked, if OnlickListener is set in OnTouchEvent, onClick will be invoked, which also indicates that OnlickListener has the lowest priority and OnTouchListener has the highest priority.Level is higher than OnTouchEvent
1. ViewGroup's event distribution process
- Event Delivery Process
Activity ->window ->Top View
When an event is passed to the top-level View, the dispatchTouchEvent method of the top-level View is invoked. In the dispatchTouchEvent, the decision to intercept the current event is made in the following two cases. Here is the source code of the View's decision to intercept the current event
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 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;
}
Explain the source code above
1,
The current event type is MotionEvent.ACTION_DOWN or mFirstTouchTarget!= null.The current event type is MotionEvent.ACTION_DOWN, so mFirstTouchTarget!= What is null? In fact, mFirstTouchTarget assigns a value to mFirstTouchTarget when ViewGroup does not intercept events and hands them over to a child View for processing.
2,
If (actionMasked == MotionEvent.ACTION_DOWN)
|| mFirstTouchTarget!= null) when the condition returns false, the onInterceptTouchEvent(ev) method in ViewGroup will not be called, which means that the current ViewGroup will handle the event by default.
The above are two cases in dispatchTouchEvent to determine whether to intercept the current event.
2. How ViewGroup passes events down to child Views
If ViewGroup does not intercept the current event and leaves it to the child View to handle it, the code below will be called.
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);
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 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;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
The code above is a bit more, but the logic is clear. First, iterate through all the child elements of the ViewGroup to see if they can receive a click event. If the child element can receive a click event, it calls the dispatchTouchEvent method of the child element.This handles the event to the child elements.This completes a round of event distribution.
If the ViewGroup has no child elements or if the child elements handle the click event, the ViewGroup will handle the click event itself in either case.
2. View's handling of click events
View's event distribution (excluding ViewGroup here) is similar to ViewGroup, but much simpler than ViewGroup because it has no child elements to pass events down
Similarly, when an event is generated, its delivery process is Activity -> window -> top-level View. When passed to View, VIew's dispatchTouchEvent method is invoked. Here is the source code for View's dispatchTouchEvent method
public boolean dispatchTouchEvent(MotionEvent event) {
...
boolean result = false;
...
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;
}
Here I've omitted some of the source code just to show you the important part of the code. First, he will determine if onTuchListener is set. If OnTouchListener's onTouch method returns true, the OnTuchEvent method will not be called. This confirms again that OnTouchListener has a higher priority than OnTuchEvent
- How OnTouchListener and OnTuchEven handle events
OnTouchListener and OnTuchEven handle events the same way, so here I'm just going to talk about the OnTuchEven method
Here are some of OnTuchEven's source code for event handling
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
From the source code above, it can be seen that as long as one of the CLICKABLE and LONG_CLICKABLE of the View is true, he will consume this event, that is, OnTuchEven returns true. It is important to note here that LONG_CLICKABLE defaults to false, and the return value of CLICKABLE is related to the state of the View, if the current View is clickable thenCLICKABLE is true, and CLICKABLE is false if the current View is not clickable
The event distribution mechanism for android is complete here.