Android Event distribution(2)

I remember in the previous article, I took everyone from Android Event distribution(一), I believe that read friends on the View event distribution has a more profound understanding.

Have not read the friends, please refer to the Android Event distribution(一), take you from Android Event distribution().

So today we will continue the last unfinished topic, from the perspective of source analysis ViewGroup event distribution.

First of all, let’s explore what is ViewGroup? What is the difference between it and ordinary View?

As the name suggests, ViewGroup is a collection of View, which contains a lot of sub-view and sub-ViewGroup, Android is the layout of all the parent or indirect parent class, such as LinearLayout, RelativeLayout are inherited from ViewGroup. But ViewGroup is actually a View, but compared to View, it can contain more sub-view and define the layout parameters of the function. ViewGroup inheritance diagram is as follows:

1

You can see that we usually use the various layouts of the project, all belong to the subclass of ViewGroup.

A brief introduction to the ViewGroup, we now through a Demo to demonstrate the Android VewGroup event distribution process it.

First we come from defining a layout, named MyLayout, inherited from LinearLayout, as follows:

public class MyLayout extends LinearLayout {  
  
    public MyLayout(Context context, AttributeSet attrs) {  
        super(context, attrs);  
    }  
  
}  

Then, open the main layout file activity_main.xml, where we 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> 

You can see that we have added two buttons in MyLayout, and then register the listener for both of the buttons and MyLayout in the 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");  
    }  
});  

We are in MyLayout onTouch method, and Button1, Button2 onClick method are printed in a word. Now run the project, the effect is as follows:

2

Click Button1, Button2 and blank area, the print results are as follows:

3

You will find that when clicking the button, MyLayout registered onTouch method will not be implemented, only when the blank area will be implemented when the method. You can first understand the Button onClick method to consume the event, so the event will not continue to pass down.

It shows that the touch event in Android is first passed to View, and then passed to the ViewGroup? Now the conclusion is still too early, let us do an experiment again.

Look at the document you can see, ViewGroup has a onInterceptTouchEvent method, we look at the source of this method:

/** 
 * 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;  
}  

If you do not see the source you really may be scared of this note, so long English comments see the head are big. But the source is so simple! Only one line of code, returned a false!

Well, since it is a boolean return, then there are only two possibilities, we rewrite this method in MyLayout, and then return a true try, the code is as follows:

public class MyLayout extends LinearLayout {  
  
    public MyLayout(Context context, AttributeSet attrs) {  
        super(context, attrs);  
    }  
      
    @Override  
    public boolean onInterceptTouchEvent(MotionEvent ev) {  
        return true;  
    }  
      
}  

Now run the project again, then Button1, Button2 and blank area, the print results are as follows:

4

You will find that no matter where you click, will only trigger the MyLayout touch event, the button click event completely blocked! Why is this? If the touch event in Android is passed to View and passed to ViewGroup, how can MyLayout block the Button’s click event?

It seems that only through reading the source code, find out the Android Event distribution(一) in order to solve our hearts doubts, but here I would like to tell you first, Android touch event delivery, is definitely passed to ViewGroup, and then Passed to View.

I remember in the Android Event distribution(一) I have explained that as long as you touch any control, it will call the control of the dispatchTouchEvent metho.。This is true, but not yet complete. The actual situation is that when you click on a control, you will first call the deployment of the control dispatchTouchEvent method, and then in the layout of the dispatchTouchEvent method to find the corresponding control click, and then call the control of the dispatchTouchEvent method. If we click on the button in MyLayout, we will call MyLayout’s dispatchTouchEvent method, but you will find that MyLayout does not have this method. Then it is to find it in the parent class LinearLayout find that there is no such method. It would have to continue to find LinearLayout the parent class ViewGroup, you finally see in the ViewGroup this method, the button dispatchTouchEvent method is called here.

What are you waiting for? Let’s take a look at the source of 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);  
}  

This method code is relatively long, we only pick the point of view. First in the line 13 can see a condition to judge, if disallowIntercept and! OnInterceptTouchEvent (ev) both have a true, it will enter into the conditional judgment.DisallowIntercept is whether to disable the event blocking function, the default is false, you can also call the requestDisallowInterceptTouchEvent method to modify this value. Then when the first value is false, it will rely entirely on the second value to determine whether it can enter the condition to determine the internal, the second value is what? It is on the onInterceptTouchEvent method of the return value of the reverse! That is, if we return false in the onInterceptTouchEvent method, we will let the second value be true and enter the interior of the conditional judgment. If we return true in the onInterceptTouchEvent method, the second value will be false, so that Out of this condition to judge.

This time you can think about it, because we have just rewritten in MyLayout onInterceptTouchEvent method, so that this method returns true, resulting in all the button click events are blocked, then we have every reason to believe that the button click event Processing is in the 13th line of conditions to determine the internal!

Then we focus on the conditions to judge the internal is how to achieve. On the 19th line through a for loop, traverse the current ViewGroup under all the sub View, and then in the 24th line to determine the current traversal of the View is not click on the View, if it will enter the conditions to determine the internal, and then In the 29th line call the View dispatchTouchEvent, after the process and the Android event distribution mechanism completely resolved, take you from Android Event distribution(一) to explain the same. We also confirmed that the button click event processing is indeed carried out here.

And then need to pay attention to, call the Sub View dispatchTouchEvent after the return value. We already know that if a control is clickable, then click on the control, dispatchTouchEvent the return value must be true. So that the conditional judgment on line 29 is made, and true is returned directly to the dispatchTouchEvent method of ViewGroup on line 31. This will lead to the implementation of the code can not be followed, but also confirms the results of our previous Demo print, if the button click event is implemented, will be MyLayout touch events intercepted.

That if we click on the button, but the blank area? This situation will not return true on line 31, but will continue to implement the following code. Then we continue to look back, in line 44, if the target is equal to null, will enter the condition to determine the internal, where the target will be in general null, so in the first 50 lines call super.dispatchTouchEvent (ev). Where will this code call? Of course, View is the dispatchTouchEvent method, because ViewGroup’s parent class is View. After the processing logic and the above is the same, and therefore MyLayout registered onTouch method will be implemented. After the code in the general case is not accessible, and we will not continue to analyze down.

Now the entire ViewGroup event distribution process analysis is over, we finally come to a simple comb it now:

  1. Android event distribution is passed to ViewGroup, and then passed by ViewGroup to View.
  2. In the ViewGroup can be through the onInterceptTouchEvent method to intercept the event transfer, onInterceptTouchEvent method returns true that does not allow the event to continue to pass to the child View, return false on behalf of the event does not intercept, the default return false.
  3. If the event is consumed in the View, the ViewGroup will not be able to receive any events.
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s