PS: If you think you are trying hard, then tell me what the result of your effort is!
The previous article here described the general process of Android event distribution. Below, we will introduce the relevant methods of events from three aspects: Activity, ViewGroup, and View. The sections are as follows:
- Activity
- ViewGroup
- View
Activity#
There are mainly two methods related to event passing in Activity: dispatchTouchEvent() and onTouchEvent(). The event passing starts with the dispatchTouchEvent() method of Activity.
Event Distribution#
The event distribution method in Activity is dispatchTouchEvent(), and its source code is as follows:
// Event distribution
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// Empty method
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
// The onTouchEvent() method defaults to return false
return onTouchEvent(ev);
}
In the code above, it is clear that only the ACTION_DOWN event is handled, indicating that only the ACTION_DOWN event will trigger event distribution. It then calls the superDispatchTouchEvent(ev) method of the Window class, which is an abstract method. When this method is called, it will invoke the method in the specific subclass, where the specific subclass of the Window class is the PhoneWindow class. The specific implementation of the superDispatchTouchEvent(ev) method is as follows:
// Abstract method in the Window class
public abstract boolean superDispatchTouchEvent(MotionEvent event);
// Specific implementation of superDispatchTouchEvent() method in the Window subclass PhoneWindow
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
Clearly, mDecor.superDispatchTouchEvent(event) is called here, where mDecor is a PhoneWindow.DecorView object, which is also the top-level view of the window. It is the actual root view of the Activity, inheriting from FrameLayout. Through super.dispatchTouchEvent, it will dispatch the touch event to each child view of the Activity, which is the view we set in the Activity.onCreate method using setContentView. The reference code is as follows:
// DecorView class declaration
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
...
public boolean superDispatchTouchEvent(MotionEvent event) {
// Here it calls the dispatchTouchEvent method in FrameLayout
return super.dispatchTouchEvent(event);
}
...
}
Since the FrameLayout does not override the dispatchTouchEvent(event) method, we need to refer to the implementation of this method in ViewGroup, which is its parent class, for the specific event distribution process that needs further study.
Event Handling#
The event handling method in Activity is onTouchEvent(), and its source code is as follows:
// Event handling, defaults to return false
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
// Defaults to return false
return false;
}
For the onTouchEvent() method, the event is passed to the parent control. Even if it returns false, the event is still considered consumed.
Note: During event distribution in Activity, only when return super.dispatchTouchEvent(ev) is executed will the event continue to be passed down. Returning true or false means the event has been consumed, thus terminating the event propagation.
ViewGroup#
There are mainly three methods related to event passing in ViewGroup: dispatchTouchEvent(), onInterceptTouchEvent(), and onTouchEvent(), as detailed below:
Event Distribution#
The event distribution method in ViewGroup is dispatchTouchEvent(), and its source code is as follows:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...
// The dispatchTouchEvent() method defaults to return false
boolean handled = false;
// The method decides whether to intercept event distribution
onInterceptTouchEvent(ev);
...
// By default, canceled and intercepted are false
if (!canceled && !intercepted) {
...
// This method passes the event to the child View
dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
...
}
return handled;
}
The purpose of this method is to traverse the child Views in ViewGroup and hand over the event (ACTION_DOWN) to the child Views for processing. It mainly calls the onInterceptTouchEvent() and dispatchTransformedTouchEvent() methods, where onInterceptTouchEvent() defaults to return false. Below is the main source code of the dispatchTransformedTouchEvent() method:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits){
...
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
// Distributing events to child View
handled = child.dispatchTouchEvent(event);
}
...
return handled;
}
Clearly, the dispatchTransformedTouchEvent() method mainly handles event distribution to child Views. If there are no child Views, it calls the parent View's dispatchTouchEvent(event) method.
Event Interception#
The event distribution method onInterceptTouchEvent() in ViewGroup is as follows:
public boolean onInterceptTouchEvent(MotionEvent ev) {
...
// Defaults to return false
return false;
}
This method defaults to return false, indicating that it does not intercept event distribution to child Views. This method is called in the dispatchTouchEvent() method of ViewGroup.
Event Handling#
ViewGroup does not have its own onTouchEvent() event handling method. ViewGroup inherits from View, and its event handling method is the event handling method of View, which is as follows:
public boolean onTouchEvent(MotionEvent event) {
...
// Defaults to return false
return false;
}
This method handles related events. If it returns true, it indicates that the event has been handled, and specific usage will be recorded in the following text.
View#
There are mainly two methods related to event distribution in View: dispatchTouchEvent() and onTouchEvent(), as detailed below:
Event Distribution#
The event distribution method in View is dispatchTouchEvent(), and the main code is as follows:
public boolean dispatchTouchEvent(MotionEvent event) {
...
// The default return value is false
boolean result = false;
...
if (onFilterTouchEventForSecurity(event)) {
...
// Calls the onTouchEvent method
if (!result && onTouchEvent(event)) {
result = true;
}
}
...
return result;
}
In the above code, the default return value of the dispatchTouchEvent() method is false, indicating that the event continues to be distributed. In fact, the return value of the dispatchTouchEvent method is related to the return value of the onTouchEvent method. If onTouchEvent returns true, the return value result of dispatchTouchEvent becomes true, indicating that the event has been consumed. This can also be understood as the value of onTouchEvent being true, which itself indicates that the event has been consumed. Below are the conditions for executing the onTouchEvent method:
public boolean onFilterTouchEventForSecurity(MotionEvent event) {
//noinspection RedundantIfStatement
if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
&& (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
// The window is obscured, discard the touch event (rarely executed)
return false;
}
return true;
}
Clearly, the above method will generally return true, so it will definitely execute the onTouchEvent method. The call to the View's dispatchTouchEvent method can actually be simplified as follows:
public boolean dispatchTouchEvent(MotionEvent event) {
...
// The default return value is false
boolean result = false;
...
return onTouchEvent(event)
}
Event Handling#
The event handling method in View is onTouchEvent(), and the main code is as follows:
public boolean onTouchEvent(MotionEvent event) {
...
// The condition for returning true here is that TouchDelegate is empty by default, which is mainly about the touch area of the View
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
// This condition will only return true if a click event is set, meaning the event is consumed
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
// Handling of various events
...
}
return true;
}
// Defaults to return false, indicating that the event is not consumed
return false;
}
When the event is passed to the child View, there are two possible outcomes: either the current View consumes the event, or it does not consume the event and passes it back up. If it does not intercept until it reaches Activity and does not process it, the event will ultimately be discarded.
This article is the second part of the Android event distribution mechanism, mainly recording the methods related to events in Activity, ViewGroup, and View, namely dispatchTouchEvent(), onTouchEvent(), and onInterceptTouchEvent() methods in Activity, ViewGroup, and View, understanding the event relationships in Android from the perspective of code. The specific event distribution process will be discussed in the next article.