Reprint please indicate the source: juejin.cn/post/690645…
Remember in the previous article, I took you together from the perspective of source code analysis of the Android View event distribution mechanism, I believe that read friends of View event distribution has a more profound understanding.
Have not read the friends, please refer to the Android event distribution mechanism to fully analyze, take you from the source point of view of a thorough understanding (on).
So today we’re going to pick up where we left off last time and analyze ViewGroup event distribution from a source point of view.
First of all, what is a ViewGroup? How is it different from a regular View?
As the name implies, a ViewGroup is a collection of views, which contains many child Views and child VewGroups. It is the parent or indirect parent of all Android layouts. Things like LinearLayout and RelativeLayout inherit from ViewGroup. But a ViewGroup is actually a View, except that it has the ability to contain child views and define layout parameters. The ViewGroup inheritance structure is shown as follows:
As you can see, the various layouts that we use a lot in our projects are all subclasses of ViewGroup.
After a brief introduction to ViewGroup, let’s now use a Demo to demonstrate the event distribution process of VewGroup in Android.
First we customize a layout, named MyLayout, inherited from the LinearLayout, as shown below:
public class MyLayout extends LinearLayout { public MyLayout(Context context, AttributeSet attrs) { super(context, attrs); }}Copy the code
Then, open the main layout file activity_main.xml and add our custom layout:
<com.example.viewgrouptouchevent.MyLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/my_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<Button
android:id="@+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button1" />
<Button
android:id="@+id/button2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button2" />
</com.example.viewgrouptouchevent.MyLayout>
Copy the code
As you can see, we added two buttons to MyLayout, and registered listening events for both buttons and MyLayout in MainActivity:
myLayout.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { Log.d("TAG", "myLayout on touch"); return false; }}); button1.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Log.d("TAG", "You clicked button1"); }}); button2.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Log.d("TAG", "You clicked button2"); }});Copy the code
We printed a sentence in the onTouch method of MyLayout and the onClick method of Button1 and Button2. Now run the project and it looks like this:
Click on Button1, Button2, and the blank area, and the print will look like this:
You will notice that the onTouch method registered with MyLayout does not execute when the button is clicked, but only when the blank area is clicked. Button’s onClick method consumes the event, so the event is no longer passed down.
The touch event in Android is passed to the View first and then to the ViewGroup. It’s too early to tell, but let’s do another experiment.
There is an onInterceptTouchEvent method in ViewGroup.
/** * Implement this method to intercept all touch screen motion events. This * allows you to watch events as they are dispatched to your children, and * take ownership of the current gesture at any point. * * <p>Using this function takes some care, as it has a fairly complicated * interaction with {@link View#onTouchEvent(MotionEvent) * View.onTouchEvent(MotionEvent)}, and using it requires implementing * that method as well as this one in the correct way. Events will be * received in the following order: * * <ol> * <li> You will receive the down event here. * <li> The down event will be handled either by a child of this view * group, or given to your own onTouchEvent() method to handle; this means * you should implement onTouchEvent() to return true, so you will * continue to see the rest of the gesture (instead of looking for * a parent view to handle it). Also, by returning true from * onTouchEvent(), you will not receive any following * events in onInterceptTouchEvent() and all touch processing must * happen in onTouchEvent() like normal. * <li> For as long as you return false from this function, each following * event (up to and including the final up) will be delivered first here * and then to the target's onTouchEvent(). * <li> If you return true from here, you will not receive any * following events: the target view will receive the same event but * with the action {@link MotionEvent#ACTION_CANCEL}, and all further * events will be delivered to your onTouchEvent() method and no longer * appear here. * </ol> * * @param ev The motion event being dispatched down the hierarchy. * @return Return true to steal motion events from the children and have * them dispatched to this ViewGroup through onTouchEvent(). * The current target will receive an ACTION_CANCEL event, and no further * messages will be delivered here. */ public boolean onInterceptTouchEvent(MotionEvent ev) { return false; }Copy the code
If you don’t look at the source code you can really be surprised by this comment, so long English comments to see the head are big. But the source code should be so simple! A single line of code returns a false!
Well, since this is a Boolean return, there are only two possibilities. Let’s override this method in MyLayout and return true, as follows:
public class MyLayout extends LinearLayout { public MyLayout(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return true; }}Copy the code
Now run the project again, then Button1, Button2, and the blank area respectively, and print something like this:
You will find that no matter where you click, you will always trigger MyLayout touch events, button click events are completely shielded! Why is that? If the Touch event is passed to the View and then to the ViewGroup, how can MyLayout block the Button click event?
It seems that only by reading the source code, to understand the Android ViewGroup event distribution mechanism, to solve our hearts of doubt, but here I want to tell you that the Android touch event transmission, is absolutely transmitted to the ViewGroup first, And pass it to the View. Remember that whenever you touch any control, you must call its dispatchTouchEvent method. That’s true, but it’s not complete. What happens is that when you click on a control, you call the dispatchTouchEvent method of the layout, and then you find the control that was clicked in the dispatchTouchEvent method of the layout, Call the control’s dispatchTouchEvent method. 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 go to its parent LinearLayout and find that there is no such method. So you go back to the Parent ViewGroup of the LinearLayout, and you finally see this method in the ViewGroup, where the dispatchTouchEvent method for the button is called. The revised schematic diagram is as follows:
So what are we waiting for? Take a look at the dispatchTouchEvent method in ViewGroup. The code looks like this:
public boolean dispatchTouchEvent(MotionEvent ev) { final int action = ev.getAction(); final float xf = ev.getX(); final float yf = ev.getY(); final float scrolledXFloat = xf + mScrollX; final float scrolledYFloat = yf + mScrollY; final Rect frame = mTempRect; boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! = 0; if (action == MotionEvent.ACTION_DOWN) { if (mMotionTarget ! = null) { mMotionTarget = null; } if (disallowIntercept || ! onInterceptTouchEvent(ev)) { ev.setAction(MotionEvent.ACTION_DOWN); final int scrolledXInt = (int) scrolledXFloat; final int scrolledYInt = (int) scrolledYFloat; final View[] children = mChildren; final int count = mChildrenCount; for (int i = count - 1; i >= 0; i--) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() ! = null) { child.getHitRect(frame); if (frame.contains(scrolledXInt, scrolledYInt)) { final float xc = scrolledXFloat - child.mLeft; final float yc = scrolledYFloat - child.mTop; ev.setLocation(xc, yc); child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; if (child.dispatchTouchEvent(ev)) { mMotionTarget = child; return true; } } } } } } boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) || (action == MotionEvent.ACTION_CANCEL); if (isUpOrCancel) { mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; } final View target = mMotionTarget; if (target == null) { ev.setLocation(xf, yf); if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) ! = 0) { ev.setAction(MotionEvent.ACTION_CANCEL); mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; } return super.dispatchTouchEvent(ev); } if (! disallowIntercept && onInterceptTouchEvent(ev)) { final float xc = scrolledXFloat - (float) target.mLeft; final float yc = scrolledYFloat - (float) target.mTop; mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; ev.setAction(MotionEvent.ACTION_CANCEL); ev.setLocation(xc, yc); if (! target.dispatchTouchEvent(ev)) { } mMotionTarget = null; return true; } if (isUpOrCancel) { mMotionTarget = null; } final float xc = scrolledXFloat - (float) target.mLeft; final float yc = scrolledYFloat - (float) target.mTop; ev.setLocation(xc, yc); if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) ! = 0) { ev.setAction(MotionEvent.ACTION_CANCEL); target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; mMotionTarget = null; } return target.dispatchTouchEvent(ev); }Copy the code
This is a long method, so let’s just focus on it. First you can see a conditional judgment in line 13 if disallowIntercept and! OnInterceptTouchEvent (EV) enters this condition if either of them is true. DisallowIntercept refers to whether to disable the function of the event to intercept, default is false, but can be by call requestDisallowInterceptTouchEvent method to modify this value. So when the first value is false it depends entirely on the second value to get inside the condition. What is the second value? The onInterceptTouchEvent method returns the opposite value. That is, if we return false in the onInterceptTouchEvent method, we will set the second value to true, thus getting inside the condition. If we return true in the onInterceptTouchEvent method, I’m going to make the second value false and get out of the conditional.
We’ve just overwritten the onInterceptTouchEvent method in MyLayout to return true, so that all button clicks are blocked. The button click event is handled inside the condition judgment on line 13!
So let’s focus on the inner workings of the conditional judgment. In line 19, a for loop iterates through all the child views in the current ViewGroup, and then in line 24, determines whether the View currently iterated through is the View being clicked, and if so, goes inside the condition judgment. Then call the View’s dispatchTouchEvent at line 29, and the process is completely resolved with the Android event distribution mechanism, which takes you through the source code. We have thus confirmed that this is indeed where the button click event is handled.
Then notice that the dispatchTouchEvent call to the child View returns a value. We already know that if a control is clickable, then the return value of dispatchTouchEvent must be true when the control is clicked. This results in the condition being true on line 29, and the dispatchTouchEvent method on the ViewGroup returns true directly on line 31. As a result, the following code cannot be executed, which also confirms the result printed in our previous Demo. If the button click event is executed, the Touch event of MyLayout will be blocked.
What if instead of clicking on a button, we click on a blank area? This will definitely not return true at line 31, but will continue to execute the following code. So let’s move on, and at line 44, if target is null, then we’re inside the condition judgment, where target would normally be null, so super.DispatchTouchEvent (ev) is called at line 50. Where does this code call? The dispatchTouchEvent method in the View, of course, because the parent class of the ViewGroup is the View. The processing logic is the same again, so the onTouch method registered in MyLayout is executed. The code that follows is generally not available, and we will not continue the analysis.
Let’s look at the process diagram of ViewGroup event distribution to help you understand it better:
Now that the analysis of the entire ViewGroup event distribution process is over, let’s finally comb through it briefly.
-
Android event distribution is passed first to the ViewGroup and then from the ViewGroup to the View.
-
The onInterceptTouchEvent method returns true, indicating that the event is not allowed to continue to be passed to the child View. False means the event will not be intercepted. False is returned by default.
-
If the child View consumes the event passed, the ViewGroup will not receive any events.
Well, the Android event distribution mechanism is completely resolved to this end, combined with the next two, I believe that you have a very deep understanding of the event distribution.