The Android Event Distribution Mechanism is fully parsed to give you a thorough understanding of the source code perspective.

Keywords: Android Attribute xml REST

The original author is divided into two parts to explain, when reproduced, directly reproduced into one article, the original place is as follows:

Top episode: http://blog.csdn.net/guolin_blog/article/details/9097463

The following episode: http://blog.csdn.net/guolin_blog/article/details/9153761


Actually, I've been preparing to write an article about it. Android Event distribution mechanism articles, from my first blog, have used the knowledge of Android event distribution scatteredly in many places. Many friends have asked me a variety of questions, such as: what is the difference between onTouch and onTouchEvent, and how to use it? Why can't ListView scroll when a sliding menu is introduced to ListView? Why do the pictures in the picture rotator use Button instead of ImageView? Wait... I did not give a very detailed answer to these questions, because I know that if you want to understand these questions thoroughly, it is essential to master the Android event distribution mechanism, and the Android event distribution mechanism is absolutely not a two-word one.


After a long period of preparation, I finally decided to start writing such an article. At present, although there are many related articles on the internet, I don't think there are any articles written in particular detail (maybe I haven't found them yet). Most of them just talk about theory, and then run the results with demo. And I'm going to take you from the source point of view for analysis, I believe you can have a deeper understanding of Android event distribution mechanism.


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


Let's start now. For example, you currently have a very simple project with only one Activity and only one button in the Activity. As you may already know, if you want to register a click event for this button, you only need to call:

  1. button.setOnClickListener(new OnClickListener() {  
  2.     @Override  
  3.     public void onClick(View v) {  
  4.         Log.d("TAG""onClick execute");  
  5.     }  
  6. });  
Write the implementation in the onClick method so that it can be executed when the button is clicked. As you may already know, if you want to add a touch event to this button, you only need to call:
  1. button.setOnTouchListener(new OnTouchListener() {  
  2.     @Override  
  3.     public boolean onTouch(View v, MotionEvent event) {  
  4.         Log.d("TAG""onTouch execute, action " + event.getAction());  
  5.         return false;  
  6.     }  
  7. });  

The onTouch method can do more things than onClick, such as judging finger pressing, lifting, moving and so on. So if I register for both events, which one will execute first? Let's try and see. Run the program and click on the button. The print result is as follows:




As you can see, onTouch takes precedence over onClick, and onTouch executes twice, once ACTION_DOWN and once ACTION_UP (you may also have multiple ACTION_MOVE executions if you shake your hand). So the order of event passing is onTouch, then onClick.


Careful friends should notice that the onTouch method has a return value. Here we return false. If we try to change the return value of the onTouch method to true and run it again, the result is as follows:




We find that the onClick method is no longer executed! Why? You can first understand that the onTouch method returns true and assumes that the event is consumed by onTouch, so it will not continue to pass down.


If all of the above points are clear to you so far, then you should have mastered the basic usage of Android event transmission. But don't be satisfied with the status quo, let's analyze it from the source code point of view, what is the principle of the above phenomenon.


First of all, you need to know that as long as you touch any control, you will call the dispatchTouchEvent method of the control. Then when we click on the button, we will call the dispatchTouchEvent method in the Button class, but you will find that there is no such method in the Button class. Then go to its parent TextView and find it. You will find that there is no such method in the TextView, so you have to continue to look in the parent View of TextView. At this time, you finally find it in the View. This method has been found. The schematic diagram is as follows:




Then let's look at the source code of the dispatchTouchEvent method in View:

  1. public boolean dispatchTouchEvent(MotionEvent event) {  
  2.     if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
  3.             mOnTouchListener.onTouch(this, event)) {  
  4.         return true;  
  5.     }  
  6.     return onTouchEvent(event);  
  7. }  

This method is very simple, only a few lines of code! We can see that in this method, the first step is to make a judgment, if mOnTouchListener!= null, (mViewFlags & ENABLED_MASK) === ENABLED and mOnTouchListener.onTouch(this, event) are true, then return true, or else the onTouchEvent method is executed and returned.


Let's first look at the first condition, where does the variable mOnTouchListener assign its value? After searching, we found the following methods in View:

  1. public void setOnTouchListener(OnTouchListener l) {  
  2.     mOnTouchListener = l;  
  3. }  

Bingo! Find out that mOnTouchListener is assigned in the setOnTouchListener method, that is to say, as long as we register touch events for the control, mOnTouchListener must be assigned.


The second condition (mViewFlags & ENABLED_MASK) === ENABLED is to determine whether the control currently clicked is enable, and the buttons are enable by default, so this condition is constant to true.


The third condition is more critical. mOnTouchListener.onTouch(this, event), in fact, is the onTouch method to go back to the regulator to register touch events. That is to say, if we return true in the onTouch method, all three conditions will be established, and the whole method will return true directly. If we return false in the onTouch method, we will execute the onTouchEvent(event) method again.


Now we can combine the previous examples to analyze. The onTouch method is the first one to be executed in dispatch Touch Event, so onTouch must take precedence over onClick, which also confirms the printing result. If true is returned in the onTouch method, the dispatchTouchEvent method will be returned directly to true and will not continue to execute. The print results also confirm that if onTouch returns true, onClick will no longer execute.


Based on the analysis of the above source code, the operation results of our previous examples are explained in principle. The above analysis also reveals an important information, that is, the onClick call must be in the onTouchEvent(event) method! Now let's look at the source code of onTouchEvent, as follows:

  1. public boolean onTouchEvent(MotionEvent event) {  
  2.     final int viewFlags = mViewFlags;  
  3.     if ((viewFlags & ENABLED_MASK) == DISABLED) {  
  4.         // A disabled view that is clickable still consumes the touch  
  5.         // events, it just doesn't respond to them.  
  6.         return (((viewFlags & CLICKABLE) == CLICKABLE ||  
  7.                 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));  
  8.     }  
  9.     if (mTouchDelegate != null) {  
  10.         if (mTouchDelegate.onTouchEvent(event)) {  
  11.             return true;  
  12.         }  
  13.     }  
  14.     if (((viewFlags & CLICKABLE) == CLICKABLE ||  
  15.             (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  
  16.         switch (event.getAction()) {  
  17.             case MotionEvent.ACTION_UP:  
  18.                 boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  
  19.                 if ((mPrivateFlags & PRESSED) != 0 || prepressed) {  
  20.                     // take focus if we don't have it already and we should in  
  21.                     // touch mode.  
  22.                     boolean focusTaken = false;  
  23.                     if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {  
  24.                         focusTaken = requestFocus();  
  25.                     }  
  26.                     if (!mHasPerformedLongPress) {  
  27.                         // This is a tap, so remove the longpress check  
  28.                         removeLongPressCallback();  
  29.                         // Only perform take click actions if we were in the pressed state  
  30.                         if (!focusTaken) {  
  31.                             // Use a Runnable and post this rather than calling  
  32.                             // performClick directly. This lets other visual state  
  33.                             // of the view update before click actions start.  
  34.                             if (mPerformClick == null) {  
  35.                                 mPerformClick = new PerformClick();  
  36.                             }  
  37.                             if (!post(mPerformClick)) {  
  38.                                 performClick();  
  39.                             }  
  40.                         }  
  41.                     }  
  42.                     if (mUnsetPressedState == null) {  
  43.                         mUnsetPressedState = new UnsetPressedState();  
  44.                     }  
  45.                     if (prepressed) {  
  46.                         mPrivateFlags |= PRESSED;  
  47.                         refreshDrawableState();  
  48.                         postDelayed(mUnsetPressedState,  
  49.                                 ViewConfiguration.getPressedStateDuration());  
  50.                     } else if (!post(mUnsetPressedState)) {  
  51.                         // If the post failed, unpress right now  
  52.                         mUnsetPressedState.run();  
  53.                     }  
  54.                     removeTapCallback();  
  55.                 }  
  56.                 break;  
  57.             case MotionEvent.ACTION_DOWN:  
  58.                 if (mPendingCheckForTap == null) {  
  59.                     mPendingCheckForTap = new CheckForTap();  
  60.                 }  
  61.                 mPrivateFlags |= PREPRESSED;  
  62.                 mHasPerformedLongPress = false;  
  63.                 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  
  64.                 break;  
  65.             case MotionEvent.ACTION_CANCEL:  
  66.                 mPrivateFlags &= ~PRESSED;  
  67.                 refreshDrawableState();  
  68.                 removeTapCallback();  
  69.                 break;  
  70.             case MotionEvent.ACTION_MOVE:  
  71.                 final int x = (int) event.getX();  
  72.                 final int y = (int) event.getY();  
  73.                 // Be lenient about moving outside of buttons  
  74.                 int slop = mTouchSlop;  
  75.                 if ((x < 0 - slop) || (x >= getWidth() + slop) ||  
  76.                         (y < 0 - slop) || (y >= getHeight() + slop)) {  
  77.                     // Outside button  
  78.                     removeTapCallback();  
  79.                     if ((mPrivateFlags & PRESSED) != 0) {  
  80.                         // Remove any future long press/tap checks  
  81.                         removeLongPressCallback();  
  82.                         // Need to switch from pressed to not pressed  
  83.                         mPrivateFlags &= ~PRESSED;  
  84.                         refreshDrawableState();  
  85.                     }  
  86.                 }  
  87.                 break;  
  88.         }  
  89.         return true;  
  90.     }  
  91.     return false;  
  92. }  

Compared with the dispatch TouchEvent method just now, the onTouchEvent method is much more complicated, but it doesn't matter. We can just focus on it.


First, in line 14, we can see that if the control is clickable, it goes into line 16 switch judgment, and if the current event is to raise a finger, it goes into the case of MotionEvent.ACTION_UP. After a variety of judgments, the performClick() method in line 38 will be executed, so let's go into this method and see:

  1. public boolean performClick() {  
  2.     sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  
  3.     if (mOnClickListener != null) {  
  4.         playSoundEffect(SoundEffectConstants.CLICK);  
  5.         mOnClickListener.onClick(this);  
  6.         return true;  
  7.     }  
  8.     return false;  
  9. }  
As you can see, as long as mOnClickListener is not null, it will call its onClick method. Where is mOnClickListener assigned? After searching, the following methods are found:
  1. public void setOnClickListener(OnClickListener l) {  
  2.     if (!isClickable()) {  
  3.         setClickable(true);  
  4.     }  
  5.     mOnClickListener = l;  
  6. }  

Everything is so clear! When we register a click event for a control by calling the setOnClickListener method, we assign a value to mOnClickListener. Then whenever the control is clicked, the onClick method of the clicked control is called back in the performClick() method.


So the whole process of event distribution in View is clear to us now! ____________ But don't be happy too soon. It's not over yet. There's another important point to note, which is the hierarchical transmission of touch events. We all know that if a touch event is registered with a control, a series of ACTION_DOWN, ACTION_MOVE, ACTION_UP events will be triggered each time it is clicked. It's important to note that if you return false when executing ACTION_DOWN, the next series of actions will not be executed. Simply put, when dispatch TouchEvent distributes events, only the former action returns true will trigger the latter action.


At this point, many friends must have great doubts. Isn't that self-contradictory? In the previous example, it was clear that false was returned in the onTouch event, but did not ACTION_DOWN and ACTION_UP all be executed? In fact, you are just confused by the illusion. Let's take a closer look at what we returned from the previous examples.

Referring to the source code we analyzed earlier, if we first return false in the onTouch event, we will definitely go into the onTouchEvent method. Then we will look at the details of the onTouchEvent method. Since we click the button, we go inside the if judgment of line 14, and you will find that whatever the current action is, we will eventually go to line 89 and return a true.


Is there a sense of deception? Clearly in the onTouch event returned false, the system or in the onTouchEvent method to help you return true. For this reason, ACTION_UP can be executed in the previous example.


Then we can change a control, replace the button with ImageView, register a touch event for it, and return false. As follows:

  1. imageView.setOnTouchListener(new OnTouchListener() {  
  2.     @Override  
  3.     public boolean onTouch(View v, MotionEvent event) {  
  4.         Log.d("TAG""onTouch execute, action " + event.getAction());  
  5.         return false;  
  6.     }  
  7. });  

Run the program and click ImageView. You will find the following results:




After ACTION_DOWN is executed, the subsequent series of actions will not be executed. And why? Because ImageView, unlike buttons, is default non-clickable, it cannot enter the if interior at line 14 of onTouchEvent's judgement, jumping directly to line 91 and returning false, which results in the subsequent actions being unable to execute.


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


1. What's the difference between onTouch and onTouchEvent and how to use them?

As you can see from the source code, both methods are invoked in the dispatch TouchEvent of View, and onTouch takes precedence over onTouchEvent. If the event is consumed in the onTouch method by returning true, the onTouchEvent will no longer execute.


In addition, it should be noted that onTouch can be executed with two prerequisites: the first mOnTouchListener value cannot be empty, and the second control that is currently clicked must be enable. So if you have a control that is not enabled, registering an onTouch event for it will never be executed. For this kind of control, if we want to listen for its touch event, we must implement it by overwriting the onTouchEvent method in the control.


2. Why can't ListView scroll when a sliding menu is introduced to ListView?

If you read Android implements picture scroll control, including tab function, so that your application is as dazzling as Taobao. This article. At that time, I used Button in the picture rotator, mainly because Button was clickable and ImageView was not. If you want to use ImageView, you can make two changes. First, return true in the onTouch method of ImageView, so that other actions after ACTION_DOWN can be executed, in order to achieve the effect of picture scrolling. Second, add an android:clickable="true" attribute to ImageView in the layout file so that when ImageView becomes clickable, other actions after ACTION_DOWN can be executed even if false is returned in onTouch.


This is the end of today's lecture. I believe you have a further understanding of Android event distribution mechanism. In the following article, I will take you to explore the event distribution mechanism of ViewGroup in Android. If you are interested, please continue reading. The Android Event Distribution Mechanism is fully parsed to give you a thorough understanding of the source code (part two) .


I remember in the previous article, I took you together from the source point of view of the analysis. Android In view of the event distribution mechanism, I believe that friends who have read the event distribution of View already have a deep understanding.


If you haven't read it yet, please refer to it first. The Android Event Distribution Mechanism is fully parsed to give you a thorough understanding of the source code perspective (I) .


So today we'll continue with the last unfinished topic, analyzing event distribution of ViewGroup from the source point of view.


First, let's talk about what is ViewGroup? What's the difference between it and ordinary View?


As the name implies, ViewGroup is a set of Views. It contains many sub-Views and sub-VewGroup. It is the parent or indirect parent of all layouts in Android. Linear Layout, Relative Layout, etc. are inherited from ViewGroup. But ViewGroup is also actually a View, except that it has more functions than View, including sub-Views and defining layout parameters. The view group inheritance schematic is as follows:




As you can see, the various layouts we often use in our usual projects are all subclasses of ViewGroup.


After a brief introduction to ViewGroup, let's demonstrate the event distribution process of VewGroup in Android through a Demo.


First, we define a layout named MyLayout, inherited from Linear Layout, as follows:

  1. public class MyLayout extends LinearLayout {  
  2.   
  3.     public MyLayout(Context context, AttributeSet attrs) {  
  4.         super(context, attrs);  
  5.     }  
  6.   
  7. }  

Then, open the main layout file activity_main.xml and add our custom layout to it:

  1. <com.example.viewgrouptouchevent.MyLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:id="@+id/my_layout"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="match_parent"  
  6.     android:orientation="vertical" >  
  7.   
  8.     <Button  
  9.         android:id="@+id/button1"  
  10.         android:layout_width="match_parent"  
  11.         android:layout_height="wrap_content"  
  12.         android:text="Button1" />  
  13.   
  14.     <Button  
  15.         android:id="@+id/button2"  
  16.         android:layout_width="match_parent"  
  17.         android:layout_height="wrap_content"  
  18.         android:text="Button2" />  
  19.   
  20. </com.example.viewgrouptouchevent.MyLayout>  
As you can see, we added two buttons to MyLayout, and then registered listening events for both buttons and MyLayout in MainActivity:
  1. myLayout.setOnTouchListener(new OnTouchListener() {  
  2.     @Override  
  3.     public boolean onTouch(View v, MotionEvent event) {  
  4.         Log.d("TAG""myLayout on touch");  
  5.         return false;  
  6.     }  
  7. });  
  8. button1.setOnClickListener(new OnClickListener() {  
  9.     @Override  
  10.     public void onClick(View v) {  
  11.         Log.d("TAG""You clicked button1");  
  12.     }  
  13. });  
  14. button2.setOnClickListener(new OnClickListener() {  
  15.     @Override  
  16.     public void onClick(View v) {  
  17.         Log.d("TAG""You clicked button2");  
  18.     }  
  19. });  

We printed a sentence in MyLayout's onTouch method and Button1, Button2's onClick method. Now run the project, and the results are as follows:




Click on Button 1, Button 2 and the blank area, respectively. The print results are as follows:




You will find that MyLayout's registered onTouch method does not execute when the button is clicked, but only when the blank area is clicked. You can first understand that Button's onClick method consumes events, so events do not continue to pass down.


Does that mean that touch events in Android are passed to View and then to ViewGroup? It's too early to draw a conclusion. Let's do another experiment.


Looking at the documentation, you can see that there is an onInterceptTouchEvent method in ViewGroup. Let's look at the source code of this method:

  1. /** 
  2.  * Implement this method to intercept all touch screen motion events.  This 
  3.  * allows you to watch events as they are dispatched to your children, and 
  4.  * take ownership of the current gesture at any point. 
  5.  * 
  6.  * <p>Using this function takes some care, as it has a fairly complicated 
  7.  * interaction with {@link View#onTouchEvent(MotionEvent) 
  8.  * View.onTouchEvent(MotionEvent)}, and using it requires implementing 
  9.  * that method as well as this one in the correct way.  Events will be 
  10.  * received in the following order: 
  11.  * 
  12.  * <ol> 
  13.  * <li> You will receive the down event here. 
  14.  * <li> The down event will be handled either by a child of this view 
  15.  * group, or given to your own onTouchEvent() method to handle; this means 
  16.  * you should implement onTouchEvent() to return true, so you will 
  17.  * continue to see the rest of the gesture (instead of looking for 
  18.  * a parent view to handle it).  Also, by returning true from 
  19.  * onTouchEvent(), you will not receive any following 
  20.  * events in onInterceptTouchEvent() and all touch processing must 
  21.  * happen in onTouchEvent() like normal. 
  22.  * <li> For as long as you return false from this function, each following 
  23.  * event (up to and including the final up) will be delivered first here 
  24.  * and then to the target's onTouchEvent(). 
  25.  * <li> If you return true from here, you will not receive any 
  26.  * following events: the target view will receive the same event but 
  27.  * with the action {@link MotionEvent#ACTION_CANCEL}, and all further 
  28.  * events will be delivered to your onTouchEvent() method and no longer 
  29.  * appear here. 
  30.  * </ol> 
  31.  * 
  32.  * @param ev The motion event being dispatched down the hierarchy. 
  33.  * @return Return true to steal motion events from the children and have 
  34.  * them dispatched to this ViewGroup through onTouchEvent(). 
  35.  * The current target will receive an ACTION_CANCEL event, and no further 
  36.  * messages will be delivered here. 
  37.  */  
  38. public boolean onInterceptTouchEvent(MotionEvent ev) {  
  39.     return false;  
  40. }  

If you don't look at the source code, you might be frightened by this comment. It's too long to read in English. But the source code is so simple! Only one line of code returns a false!


Well, since it's a Boolean return, there are only two possibilities. Let's rewrite this method in MyLayout and try returning a true. The code is as follows:

  1. public class MyLayout extends LinearLayout {  
  2.   
  3.     public MyLayout(Context context, AttributeSet attrs) {  
  4.         super(context, attrs);  
  5.     }  
  6.       
  7.     @Override  
  8.     public boolean onInterceptTouchEvent(MotionEvent ev) {  
  9.         return true;  
  10.     }  
  11.       
  12. }  

Now run the project again, and then Button 1, Button 2, and the blank area, respectively. The print results are as follows:




You will find that no matter where you click, it will always trigger the touch event of MyLayout. The click event of the button is completely blocked! Why is that? How can MyLayout block Button clicks if the touch event in Android is passed to View and then to ViewGroup?


It seems that only by reading the source code and understanding the event distribution mechanism of ViewGroup in Android can we solve our doubts, but I want to tell you first that the transmission of touch events in Android is definitely transmitted to ViewGroup first and then to View. Remember in The Android Event Distribution Mechanism is fully parsed to give you a thorough understanding of the source code perspective (I) As I explained, as long as you touch any control, you will call the dispatchTouchEvent method of the control. That's true, but it's not complete. In fact, when you click on a control, you first call the dispatchTouchEvent method of the layout of the control, then find the corresponding control clicked in the dispatchTouchEvent method of the layout, and then call the dispatchTouchEvent method of the control. If we click the button in MyLayout, we will call MyLayout's dispatchTouchEvent method first, but you will find that MyLayout does not have this method. Then look in its parent class LinearLayout and find that there is no such method. So you have to continue looking for LinearLayout's parent ViewGroup. You finally see this method in the ViewGroup, where the dispatchTouchEvent method of the button is called. The revised schematic diagram is as follows:




So what are we waiting for? Take a look at the source code of the dispatchTouchEvent method in ViewGroup. The code is as follows:

  1. public boolean dispatchTouchEvent(MotionEvent ev) {  
  2.     final int action = ev.getAction();  
  3.     final float xf = ev.getX();  
  4.     final float yf = ev.getY();  
  5.     final float scrolledXFloat = xf + mScrollX;  
  6.     final float scrolledYFloat = yf + mScrollY;  
  7.     final Rect frame = mTempRect;  
  8.     boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  
  9.     if (action == MotionEvent.ACTION_DOWN) {  
  10.         if (mMotionTarget != null) {  
  11.             mMotionTarget = null;  
  12.         }  
  13.         if (disallowIntercept || !onInterceptTouchEvent(ev)) {  
  14.             ev.setAction(MotionEvent.ACTION_DOWN);  
  15.             final int scrolledXInt = (int) scrolledXFloat;  
  16.             final int scrolledYInt = (int) scrolledYFloat;  
  17.             final View[] children = mChildren;  
  18.             final int count = mChildrenCount;  
  19.             for (int i = count - 1; i >= 0; i--) {  
  20.                 final View child = children[i];  
  21.                 if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
  22.                         || child.getAnimation() != null) {  
  23.                     child.getHitRect(frame);  
  24.                     if (frame.contains(scrolledXInt, scrolledYInt)) {  
  25.                         final float xc = scrolledXFloat - child.mLeft;  
  26.                         final float yc = scrolledYFloat - child.mTop;  
  27.                         ev.setLocation(xc, yc);  
  28.                         child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
  29.                         if (child.dispatchTouchEvent(ev))  {  
  30.                             mMotionTarget = child;  
  31.                             return true;  
  32.                         }  
  33.                     }  
  34.                 }  
  35.             }  
  36.         }  
  37.     }  
  38.     boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||  
  39.             (action == MotionEvent.ACTION_CANCEL);  
  40.     if (isUpOrCancel) {  
  41.         mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;  
  42.     }  
  43.     final View target = mMotionTarget;  
  44.     if (target == null) {  
  45.         ev.setLocation(xf, yf);  
  46.         if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
  47.             ev.setAction(MotionEvent.ACTION_CANCEL);  
  48.             mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
  49.         }  
  50.         return super.dispatchTouchEvent(ev);  
  51.     }  
  52.     if (!disallowIntercept && onInterceptTouchEvent(ev)) {  
  53.         final float xc = scrolledXFloat - (float) target.mLeft;  
  54.         final float yc = scrolledYFloat - (float) target.mTop;  
  55.         mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
  56.         ev.setAction(MotionEvent.ACTION_CANCEL);  
  57.         ev.setLocation(xc, yc);  
  58.         if (!target.dispatchTouchEvent(ev)) {  
  59.         }  
  60.         mMotionTarget = null;  
  61.         return true;  
  62.     }  
  63.     if (isUpOrCancel) {  
  64.         mMotionTarget = null;  
  65.     }  
  66.     final float xc = scrolledXFloat - (float) target.mLeft;  
  67.     final float yc = scrolledYFloat - (float) target.mTop;  
  68.     ev.setLocation(xc, yc);  
  69.     if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
  70.         ev.setAction(MotionEvent.ACTION_CANCEL);  
  71.         target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
  72.         mMotionTarget = null;  
  73.     }  
  74.     return target.dispatchTouchEvent(ev);  
  75. }  

This method has a long code, so we only focus on it. First, you can see a conditional judgment in line 13. If one of the disallow Intercept and! On Intercept TouchEvent (ev) is true, you will enter the conditional judgment. Disallow Intercept refers to whether the function of event interception is disabled, default is false, or this value can be modified by calling the request Disallow Intercept TouchEvent method. So when the first value is false, it depends entirely on the second value to decide whether it can enter the interior of conditional judgment. What is the second value? It's the reverse of the return value of the onInterceptTouchEvent method! That is to say, if we return false in the onInterceptTouchEvent method, we will let the second value be true, thus entering the interior of the conditional judgment. If we return true in the onInterceptTouchEvent method, we will let the second value be false, thus jumping out of the conditional judgment.


Now you can think about it. Since we just rewrote the onInterceptTouchEvent method in MyLayout to return to true, so that all button clicks are blocked, we have every reason to believe that the processing of button clicks is done within the 13-line condition judgment.


So let's focus on how the interior of conditional judgment is realized. In line 19, through a for loop, all the sub-Views under the current ViewGroup are traversed, and then in line 24, the view that is currently traversed is judged to be the View that is being clicked. If so, it goes inside the condition judgement. Then in line 29, the dispatch TouchEvent of the View is invoked, and the process follows. The Android Event Distribution Mechanism is fully parsed to give you a thorough understanding of the source code perspective (I) The explanation is the same. We also confirm that the processing of button click events is actually done here.


Next, you need to note that there is a return value after calling dispatchTouchEvent of the child View. We already know that if a control is clickable, the return value of dispatch TouchEvent must be true when clicking on the control. This results in the conditional judgment on line 29, so the dispatchTouchEvent method given to ViewGroup on line 31 returns true directly. This results in the failure of the following code to execute, and also confirms the result of Demo printing in front of us. If the click event of the button is executed, the touch event of MyLayout will be intercepted.


What if we click on a blank area instead of a button? In this case, instead of returning true on line 31, you will continue to execute the following code. So let's move on to line 44, and if the target equals null, it goes inside the condition judgment, where normally the target is null, so super. dispatch TouchEvent (ev) is called on line 50. Where will this code be called? Of course, it's the dispatchTouchEvent method in View, because the parent class of ViewGroup is View. The subsequent processing logic is the same as the previous one, so the onTouch method registered in MyLayout will be executed. After that, the code is generally not accessible, and we will not continue to analyze.


Take a look at the flow chart of the entire ViewGroup event distribution process, I believe it can help you better understand:




Now that the analysis of the event distribution process for the entire ViewGroup is over, let's just sort it out briefly.


1. Android event distribution is first passed to ViewGroup, and then from ViewGroup to View.

2. In ViewGroup, event delivery can be intercepted by onInterceptTouchEvent method. The onInterceptTouchEvent method returns true to deny event continuation to sub-View, false to deny event interception and false to default.

3. If the passed events are consumed in the sub-View, no events will be received in the ViewGroup.


Well, the Android event distribution mechanism is completely resolved to this end, combined with the next two articles, I believe you have a very deep understanding of event distribution.

Posted by Ghettobusta on Wed, 27 Mar 2019 06:24:32 -0700