In fact, I’ve been planning to write an article on The Android event distribution mechanism, and I’ve been using Android event distribution in bits and pieces since my first blog post. Many friends have also asked me various questions, such as: What is the difference between onTouch and onTouchEvent, and how to use it? Why can’t we add a sliding menu to the ListView so that it can’t scroll? Why do images in image rotator use Button instead of ImageView? And so on… I don’t give very detailed answers to these questions, because I know that understanding the Android event distribution mechanism is essential to fully understanding these questions, and the Android event distribution mechanism is definitely not easy to explain.

After a long time of preparation, I finally decided to write such an article. Although there are a lot of relevant articles on the Internet, I don’t think any of them are particularly detailed (maybe I haven’t found them yet). Most of them just talk about theories, and then run the results with the demo. I’m going to take you through the source code analysis, and I believe you can understand the Android event distribution mechanism more deeply.

Read the source code pay attention to from shallow to deep, step by step, so we also start from the simple, this first take you to explore the View event distribution, the next chapter to explore the more difficult ViewGroup event distribution.

Let’s get started! Let’s say you currently have a very simple project with only one Activity and only one button in the Activity. As you probably already know, if you want to register a click event for this button, you just call:

button.setOnClickListener(new OnClickListener() {

public void onClick(View v) {

		Log.d("TAG", "onClick execute");
Copy the code

So you write an implementation inside the onClick method that can be executed when the button is clicked. As you probably already know, if you want to add another touch event to the button, you just call:

button.setOnTouchListener(new OnTouchListener() {

public boolean onTouch(View v, MotionEvent event) {

		Log.d("TAG", "onTouch execute, action " + event.getAction());
Copy the code

The onTouch method can do a lot more than onClick, such as determining when a finger is pressed, lifted, moved, etc. So if I register both events, which one will be executed first? Let’s try it out. Run the program and click the button, and print the following:

As you can see, onTouch takes precedence over onClick, and onTouch executes twice, once ACTION_DOWN and once ACTION_UP(you may also execute ACTION_MOVE multiple times if your hand shakes). So events are passed through onTouch and then onClick.

If we try to change the value of onTouch to true and run it again, the result is as follows:

We find that the onClick method no longer executes! Why is that? You can assume that the onTouch method returns true and assume that the event was consumed by onTouch and will not be passed down.

If all of this is clear to you by now, then you have a basic understanding of Android event passing. But don’t be content with the status quo. Let’s look at how the above phenomenon works from a source code perspective.

The first thing you need to know is that whenever you touch any control, the dispatchTouchEvent method for that control is called. When we click the Button, we call the dispatchTouchEvent method in the Button class. We don’t have this method in the Button class, so we can look in the parent TextView. You’ll notice that this method doesn’t exist in TextView either, so you’ll have to continue to look for it in the parent View of TextView. At this point, you’ll finally find this method in the View, as shown below:

View dispatchTouchEvent ();

public boolean dispatchTouchEvent(MotionEvent event) { if (mOnTouchListener ! = null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) { return onTouchEvent(event);Copy the code

This method is very simple, only a few lines of code! We can see that, within this method, the first judgment is made if mOnTouchListener! = null, (mViewFlags & ENABLED_MASK) == ENABLED and monTouchListener.ontouch (this, event) Otherwise, execute the onTouchEvent(Event) method and return.

Let’s look at the first condition, where is the variable mOnTouchListener assigned? We found the following method in the View:

public void setOnTouchListener(OnTouchListener l) {
Copy the code

Bingo! MOnTouchListener is assigned in the setOnTouchListener method, which means that whenever we register a touch event for the control, mOnTouchListener will be assigned.

The second condition (mViewFlags & ENABLED_MASK) == ENABLED determines whether the currently clicked control is enable. Buttons are ENABLED by default, so this condition is always true.

The third condition, monTouchListener.ontouch (this, event), is the onTouch method that registers the touch event in the callback controller. That is, if we return true in the onTouch method, all three conditions will be true and the whole method will return true. If we return false in the onTouch method, we will execute the onTouchEvent(event) method again.

The onTouch method is executed first in dispatchTouchEvent, so onTouch must be executed before onClick. If true is returned in the onTouch method, the dispatchTouchEvent method will return true without further execution. The print confirms that if onTouch returns true, onClick will no longer be executed.

According to the above source code analysis, from the principle of explaining the results of our previous examples. OnClick must be called in the onTouchEvent(event) method. Let’s take a look at the source code of onTouchEvent, as follows:

public boolean onTouchEvent(MotionEvent event) { final int viewFlags = mViewFlags; if ((viewFlags & ENABLED_MASK) == DISABLED) { return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); if (mTouchDelegate ! = null) { if (mTouchDelegate.onTouchEvent(event)) { if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { switch (event.getAction()) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PREPRESSED) ! = 0; if ((mPrivateFlags & PRESSED) ! = 0 || prepressed) { boolean focusTaken = false; if (isFocusable() && isFocusableInTouchMode() && ! isFocused()) { focusTaken = requestFocus(); if (! mHasPerformedLongPress) { removeLongPressCallback(); if (mPerformClick == null) { mPerformClick = new PerformClick(); if (! post(mPerformClick)) { if (mUnsetPressedState == null) { mUnsetPressedState = new UnsetPressedState(); mPrivateFlags |= PRESSED; postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (! post(mUnsetPressedState)) { mUnsetPressedState.run(); case MotionEvent.ACTION_DOWN: if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); mPrivateFlags |= PREPRESSED; mHasPerformedLongPress = false; postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); case MotionEvent.ACTION_CANCEL: mPrivateFlags &= ~PRESSED; case MotionEvent.ACTION_MOVE: final int x = (int) event.getX(); final int y = (int) event.getY(); if ((x < 0 - slop) || (x >= getWidth() + slop) || (y < 0 - slop) || (y >= getHeight() + slop)) { if ((mPrivateFlags & PRESSED) ! = 0) { removeLongPressCallback(); mPrivateFlags &= ~PRESSED;Copy the code

The onTouchEvent method is a lot more complicated than the dispatchTouchEvent method we just did, but that’s okay, so let’s just focus on the highlights.

First, on line 14, we can see that if the control is clickable it goes into the switch judgment on line 16, and if the current event is finger lift, it goes into the motionEvent.action_up case. After all that judgment, the performClick() method is executed on line 38, so let’s go inside this method and take a look:

public boolean performClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); if (mOnClickListener ! = null) { playSoundEffect(SoundEffectConstants.CLICK); mOnClickListener.onClick(this);Copy the code

As long as mOnClickListener is not null, its onClick method is called. Where is mOnClickListener assigned? After searching, the following methods were found:

public void setOnClickListener(OnClickListener l) {
Copy the code

It’s all so clear! MOnClickListener is assigned when we register a click event for the control by calling the setOnClickListener method. Then each time the control is clicked, the onClick method of the clicked control is called back in the performClick() method.

This View of the entire event distribution process let us figure out! But do not rejoice too early, now is not over, there is a very important knowledge point need to explain, is the touch event level transfer. We all know that if you register a touch event with a control, every time you click on it, a series of ACTION_DOWN, ACTION_MOVE, ACTION_UP events will be triggered. Note that if you return false on ACTION_DOWN, the rest of the action will not be executed. When dispatchTouchEvent is sent, a subsequent action will only be triggered if the previous action returns true.

Speaking of which, a lot of friends must have a huge question. Isn’t that a contradiction? In the previous example, when false was returned in the onTouch event, both ACTION_DOWN and ACTION_UP were executed. Let’s take a closer look at what we actually returned in the previous example.

Using the source code we analyzed earlier, we first return false in the onTouch event, which must enter the onTouchEvent method, and then we will look at the details of the onTouchEvent method. Since we clicked the button, we’re inside the if judgment at line 14, and you’ll see that whatever action is currently in place must eventually go to line 89 and return true.

Do you feel cheated? The onTouchEvent method returns true for you even though it returned false in the onTouch event. This is why ACTION_UP was executed in the previous example.

Replace the button with ImageView and register it with a touch event and return false. As follows:

imageView.setOnTouchListener(new OnTouchListener() {

public boolean onTouch(View v, MotionEvent event) {

		Log.d("TAG", "onTouch execute, action " + event.getAction());
Copy the code

Run the app, click ImageView, and you’ll see the following:

After ACTION_DOWN is executed, subsequent actions are not executed. Why is that? Because ImageView, unlike buttons, is unclickable by default, it does not enter the if at line 14 of the onTouchEvent. Jumping straight to line 91 returns false, and all subsequent actions cannot be executed.

Okay, so that’s all I want to say about View event distribution. Now let’s review the three questions mentioned at the beginning, I believe everyone will have a deeper understanding.

1. What is the difference between onTouch and onTouchEvent and how to use it?

As can be seen from the source code, both methods are called in the View dispatchTouchEvent, onTouch takes precedence over onTouchEvent execution. If the event is consumed in the onTouch method by returning true, the onTouchEvent will not be executed.

Note also that onTouch can be executed only if the value of mOnTouchListener cannot be empty and the currently clicked control must be enable. So if you have a control that is non-enable, registering the onTouch event for it will never be executed. For this type of control, if we want to listen for its touch event, we must do so by overriding the onTouchEvent method in the control.

2. Why can’t we add a sliding menu to the ListView so that it can’t scroll?

If you’ve read the Android Slider Framework, you should know that the slider menu works by registering a Touch event to the ListView. If you return true after processing the slide logic in the onTouch method, then the ListView’s scroll event is blocked and cannot be clicked, so the solution is to return false in the onTouch method.

3. Why use Button instead of ImageView for images in image roaster?

Ask this question of friends is to see the Android implementation of picture scroll control, including TAB function, let your application like Taobao dazzle up this article. I used a Button in the image rotator mainly because a Button is clickable and an ImageView is unclickable. If you want to use ImageView, you can change it in two ways. First, return true in the onTouch method of the ImageView to ensure that all other actions after ACTION_DOWN are executed in order for the image to scroll. Second, add an Android :clickable=”true” attribute to the ImageView in the layout file so that after the ImageView becomes clickable, even if false is returned in onTouch, Other actions after ACTION_DOWN can also be executed.

This is the end of today’s explanation, I believe that you now have a further understanding of the Android event distribution mechanism, in the next article I will take you together to explore the Android event distribution mechanism, interested friends please continue to read the Android event distribution mechanism fully explained. Take you through the source code perspective (below).