(Conversion) Android Event Distribution Mechanism is fully parsed to give you a thorough understanding of the source code perspective (II)

Keywords: Android xml REST

Original address: http://blog.csdn.net/guolin_blog/article/details/9153747


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 brokeDUstudent on Fri, 29 Mar 2019 13:45:29 -0700