This article reprints the article, read the original source code can be obtained, at the end of the article has the original link
PS: The source code is based on Android API 26 to analyze
Clicking event is actually the MotionEvent. For the process of the MotionEvent event, it is actually the event of the click event. If a MotionEvent is generated, the system needs to pass this process event to a specific view, and the passing is the passing process. The distribution process consists of three important methods in the ViewGroup: DispatchTouchEvent, OnIntercepchTouchEvent, and OnTouchEvent.
Let’s look at the source code for the DispatchTouchEvent method in the ViewGroup;
@Override public boolean dispatchTouchEvent(MotionEvent ev) { ...... // Check for interception. 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; }... // Dispatch to touch targets. if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { ...... }... return handled; }
Found in the ViewGroup dispatchTouchEvent method calls the ViewGroup dispatchTransformedTouchEvent method, we click continue to view the method;
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) { final boolean handled; // Canceling motions is a special case. We don't need to perform any transformations // or filtering. The important part is the action, not the contents. final int oldAction = event.getAction(); if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; }... return handled;
}
DispatchTouchEvent method is called because the child parameter is passed empty, so super. DispatchTouchEvent method is called. Super is the View, so we click on the DispatchTouchEvent method in the View to see.
public boolean dispatchTouchEvent(MotionEvent event) {
. 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; }}... return result;
}
When onTouchEvent is called, you can see the relationship between DispatchTouchEvent of ViewGroup, onInterceptTouchEvent and onTouchEvent method, which is shown in the pseudocode below.
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean isExpense = false;
if (onInterceptTouchEvent(ev)) {
isExpense = onTouchEvent(event);
} else {
isExpense = child.dispatchTouchEvent(ev);
}
return isExpense;
}
First the event is passed to the ViewGroup and its DispatchTouchEvent method is called. If the ViewGroup’s onInterceptTouchEvent method returns true, it intercepts the current event. So the ViewGroup is going to call the onTouchEvent method; If the ViewGroup’s onInterceptTouchEvent method returns false, then it is possible that the current event will be passed to its children, and then the child’s event method will be called, and the event will be consumed. From the above source the event occurs when the event is successfully handled by a child of the ViewGroup, MFirstTouchTarget will be called as well and when the child element is handled and the event is not intercepted MFirstTouchTarget is handed over to the child. = null; So when mFirstTouchTarget dispatchTransformedTouchEvent = = null when they call, will call the View onTouchEvent, intercept ViewGroup own consumption events.
Activity->Window (implementation class PhoneWindow) -> decorView (current interface component container FrameLayout) -> component file View (file in setContentView method), OK, Let’s look at the source code for the DispatchTouchEvent method in the Activity.
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
From the above code, an event can occur, and the event is always passed to the Activity, then to the Window, and then to the Window, then to the decorView. When the decorView receives the event, it propagates the event according to the event propagation mechanism. If the onTouchEvent of a View returns false, then the onTouchEvent of its parent container will be called, and so on. If none of the views handle the event, the Activity will handle it, and the Activity’s onTouchEvent method will be called. To verify that the window implementation class is PhoneWindow, click on the Activity’s getWindow source method.
public Window getWindow() {
return mWindow;
}
Notice that the getWindow method returns only a Window object, notice that the Activity instantiates MWindow in the attach method;
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor, Window window, ActivityConfigCallback activityConfigCallback) { ...... mWindow = new PhoneWindow(this, window, activityConfigCallback); mWindow.setWindowControllerCallback(this); mWindow.setCallback(this); mWindow.setOnWindowDismissedCallback(this); mWindow.getLayoutInflater().setPrivateFactory(this); if (info.softInputMode ! = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) { mWindow.setSoftInputMode(info.softInputMode); } if (info.uiOptions ! = 0) { mWindow.setUiOptions(info.uiOptions); }...
}
The implementation class for Window Window is PhoneWindow; “DecorView is the current interface component Framelayout” is not shown in the source code, and the appealing reader can read the source code for himself.
Let’s review the source code for the DispatchTouchEvent method in the View;
public boolean dispatchTouchEvent(MotionEvent event) {
. 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; }}... return result;
}
From the DispatchTouchEvent method in the View, we can’t conclude that if a View needs to handle an event, if it uses the setonTouchListener statement, then onTouch in the onTouchListener will be called; If the onTouch method returns false, the View’s onTouchEvent method will be called; If it returns true, then the onTouchEvent method will not be called, so give the View a setonTouchListener statement, which takes precedence over the onTouchEvent method. In the current View, if there is an onClickListener statement, its onClick method will be called if onTouchEvent returns a value of super.onTouchEvent(event), So setOnClickEvent(event), so the priority of setOnClickEventListener, it’s that high before it fires.
Let’s test the above conclusion with a demo.
(1) Create a new class MyView in KT language and inherit it from View:
class MyView: View {
companion object { var TAG:String = "Activity" } constructor(context: Context): super(context){ } constructor(context: Context, attrs: AttributeSet): super(context, attrs){ } constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int): super(context, attrs, defStyleAttr){ } override fun onTouchEvent(event: MotionEvent?) : Boolean { Log.d(TAG,"------------onTouchEvent--") return super.onTouchEvent(event) }
}
Create a new component file activity_main.xml:
android:id="@+id/btn"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#00FF00"
xmlns:android="http://schemas.android.com/apk/res/android" />
(3) Create a new KT class, MainActivity, which inherits from AppCompatActivity:
class MainActivity: AppCompatActivity() {
var mMyView: View? = null ; companion object { var TAG: String = "Activity" } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) mMyView = findViewById(R.id.btn) mMyView!! .setOnTouchListener(object : View.OnTouchListener { override fun onTouch(v: View? , event: MotionEvent?) : Boolean { Log.d(TAG,"------------onTouch--") return true } }) mMyView!! .setOnClickListener(object : View.OnClickListener { override fun onClick(v: View?) { Log.d(TAG,"------------onClick--") } }) }
}
The interface to start the program is as follows:
The picture
When I click on it, the log print looks like this (indicating that the onTouchEvent and onClick methods were not called) :
The picture
When I change the return value of the onTouch method to false, and then run the program and click lightly, the log prints like this:
The picture
When I change the onTouchEvent side’s report view value to true, run the program and click lightly, the log print looks like this (indicating that the onClick method is not called)
The picture
Why does the onTouchEvent method return only super? OnTouchEvent (event) calls the onClick method. We click on the super. onTouchEvent(event) statement and find that it is the onTouchEvent method of the View, which is implemented as follows:
public boolean onTouchEvent(MotionEvent event) {
. switch (action) { case MotionEvent.ACTION_UP: mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; if ((viewFlags & TOOLTIP) == TOOLTIP) { handleTooltipUp(); } if (! clickable) { removeTapCallback(); removeLongPressCallback(); mInContextButtonPress = false; mHasPerformedLongPress = false; mIgnoreNextUpEvent = false; break; } 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. boolean focusTaken = false; if (isFocusable() && isFocusableInTouchMode() && ! isFocused()) { focusTaken = requestFocus(); } if (prepressed) { // The button is being released before we actually // showed it as pressed. Make it show the pressed // state now (before scheduling the click) to ensure // the user sees it. setPressed(true, x, y); } if (! mHasPerformedLongPress && ! mIgnoreNextUpEvent) { // This is a tap, so remove the longpress check removeLongPressCallback(); // Only perform take click actions if we were in the pressed state if (! focusTaken) { // Use a Runnable and post this rather than calling // performClick directly. This lets other visual state // of the view update before click actions start. if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (! post(mPerformClick)) { performClick(); } } } if (mUnsetPressedState == null) { mUnsetPressedState = new UnsetPressedState(); } if (prepressed) { postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (! post(mUnsetPressedState)) { // If the post failed, unpress right now mUnsetPressedState.run(); } removeTapCallback(); } mIgnoreNextUpEvent = false; break; . }... return false;
}
As the finger is raised, the view method’s onTouchEvent method will call the performClick method, and the performClick method interface will call the onClick method of the onClickListener:
public boolean performClick() {
final boolean result; final ListenerInfo li = mListenerInfo; if (li ! = null && li.mOnClickListener ! = null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this); result = true; } else { result = false; } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); notifyEnterOrExitForAutoFillIfNeeded(true); return result;
}