Android ViewGroup touch event Transfer Mechanism
Introduction
In the previous blog, we learned about the Android View touch event transfer mechanism. If you are not familiar with it, you can View the Android View touch event transfer mechanism. We will continue to learn about the Android touch event transfer mechanism today. This blog will discuss with you the touch event transfer mechanism of ViewGroup.
Example
The sample code is as follows:
public class MainActivity extends ActionBarActivity { private String TAG = MainActivity; private MyViewGroup parentView; private Button childView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); parentView = (MyViewGroup) findViewById(R.id.parent); childView = (Button) findViewById(R.id.child); childView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.e(TAG, childView=====onClick); } }); parentView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { Log.e(TAG, parentView=====onTouch); return false; } }); parentView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.e(TAG, parentView=====onClick); } }); }}
Customize MyViewGroup and rewrite the dispatchTouchEvent method to add print logs. Rewrite the onInterceptTouchEvent method to add print logs:
public class MyViewGroup extends LinearLayout { private String TAG = MyViewGroup; public MyViewGroup(Context context) { super(context); } public MyViewGroup(Context context, AttributeSet attrs) { super(context, attrs); } public MyViewGroup(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.e(TAG, MyViewGroup=====dispatchTouchEvent +ev.getAction()); return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.e(TAG, MyViewGroup=====onInterceptTouchEvent); return super.onInterceptTouchEvent(ev); }}
The layout is as follows:
Click the Button and the blank area, and the result is as follows:
08-01 17:02:56.792 14706-14706/com.xjp.viewgrouptouchdemo E/MyViewGroup﹕ MyViewGroup=====dispatchTouchEvent08-01 17:02:56.792 14706-14706/com.xjp.viewgrouptouchdemo E/MainActivity﹕ childView=====onClick
08-01 17:03:31.046 14706-14706/com.xjp.viewgrouptouchdemo E/MyViewGroup﹕ MyViewGroup=====dispatchTouchEvent08-01 17:03:31.046 14706-14706/com.xjp.viewgrouptouchdemo E/MainActivity﹕ parentView=====onTouch108-01 17:03:31.046 14706-14706/com.xjp.viewgrouptouchdemo E/MainActivity﹕ parentView=====onClick
From the print above, we can see that in the nested ViewGroup Button layout, only the touch event of the Button is executed when you click the Button, and the touch event of the ViewGroup is not executed. When you click a blank area other than the Button, the ViewGroup touch event is executed. Why does the ViewGroup nested View only execute the View touch event instead of the ViewGroup touch event? In this case, let's analyze the dispatchTouchEvent method in the ViewGroup source code. For convenience, I will analyze the Android2.0 source code here.
ViewGroup # dispatchTouchEvent
@Override 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) { // this is weird, we got a pen down, but we thought it was // already down! // XXX: We should probably send an ACTION_UP to the current // target. mMotionTarget = null; } // If we're disallowing intercept or if we're allowing and we didn't // intercept if (disallowIntercept || !onInterceptTouchEvent(ev)) { // reset this event's action (just to protect ourselves) ev.setAction(MotionEvent.ACTION_DOWN); // We know we want to dispatch the event down, find a child // who can handle it, start with the front-most child. 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)) { // offset the event to the view's coordinate system final float xc = scrolledXFloat - child.mLeft; final float yc = scrolledYFloat - child.mTop; ev.setLocation(xc, yc); if (child.dispatchTouchEvent(ev)) { // Event handled, we have a target now. mMotionTarget = child; return true; } // The event didn't get handled, try the next view. // Don't reset the event's location, it's not // necessary here. } } } } } boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) || (action == MotionEvent.ACTION_CANCEL); if (isUpOrCancel) { // Note, we've already copied the previous state to our local // variable, so this takes effect on the next event mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; } // The event wasn't an ACTION_DOWN, dispatch it to our target if // we have one. final View target = mMotionTarget; if (target == null) { // We don't have a target, this means we're handling the // event as a regular view. ev.setLocation(xf, yf); return super.dispatchTouchEvent(ev); } // if have a target, see if we're allowed to and want to intercept its // events if (!disallowIntercept && onInterceptTouchEvent(ev)) { final float xc = scrolledXFloat - (float) target.mLeft; final float yc = scrolledYFloat - (float) target.mTop; ev.setAction(MotionEvent.ACTION_CANCEL); ev.setLocation(xc, yc); if (!target.dispatchTouchEvent(ev)) { // target didn't handle ACTION_CANCEL. not much we can do // but they should have. } // clear the target mMotionTarget = null; // Don't dispatch this event to our own view, because we already // saw it when intercepting; we just want to give the following // event to the normal onTouchEvent(). return true; } if (isUpOrCancel) { mMotionTarget = null; } // finally offset the event to the target's coordinate system and // dispatch the event. final float xc = scrolledXFloat - (float) target.mLeft; final float yc = scrolledYFloat - (float) target.mTop; ev.setLocation(xc, yc); return target.dispatchTouchEvent(ev); }
Analysis:
1. Line 3-8 of the Code gets the coordinates of the current finger clicking on the screen. It is used to determine whether the current finger clicking is in the View area or ViewGroup area.
2. Line 3 of the Code gets the value of disallowIntercept. disallowIntercept indicates whether to disable the event interception function. The default value is false. You can call the requestDisallowInterceptTouchEvent method to modify it.
3. Line 3 of the Code. The finger-touch gesture is the first ACTION_DOWN operation, so the condition is met and the if condition is entered.
4. Line 13-19 of the Code clears the touch object on the screen of the current mobile phone, that is, setting mMotionTarget to null. It means that there is no touch object before the screen is clicked.
5. Line 3 of the Code. Because the default value of disallowIntercept is false, whether the condition is met depends entirely on the return value of onInterceptTouchEvent of the method. When we enter this method, we will find that the implementation only returns a false value. That is, if conditions are met.
6. Line 4 of the Code traverses the subview under the current ViewGroup through a for loop.
7. line 3 of the Code retrieves the coordinates of the Child View on the screen, and line 3 of the Code, determines whether the coordinates of the current screen's finger touch and click contain the coordinates and ranges of the retrieved sub-View on the screen? If it contains, it indicates that the child View is the place where the current finger is clicked, that is, the Button is clicked. Otherwise, the mouse does not touch the child View in the ViewGroup, that is, the Child View in the blank area is clicked.
8. Line 4 of the Code calls the dispatchTouchEvent method of the sub-View to handle the distribution of View touch events. Here, the step is the entrance to the Android View touch event transfer mechanism analyzed in the previous blog. In this blog, we know that the View # dispatchTouchEvent method returns true if the View is clickable or Changan clicks or setOnClickListener is set to listen to events. Otherwise, false is returned. So when the conditions are met, that is, the ViewGroup # dispatchTouchEvent method returns true when the subview sets the click event, and the mMotionTarget = child value of the touch object is assigned to the subviwe of the current click. Therefore, this also verifies the above sample code. When the button sets a click event, only the onClick event of the Button is executed, and no touch click event about the ViewGroup is executed.
9. line 6-72 of the Code. If the View # dispatchTouchEvent method above returns false, the subview cannot be clicked (you can refer to the previous blog). At this time, the mMotionTarget is still null, then the target = null condition is met. Execute the dispatchTouchEvnet method of the parent class, that is, the dispatchTouchEvent method of the View. Because the parent class of ViewGroup is View, the dispatchTouchEvent method of ViewGroup is executed here. The implication is that when a nested ViewGroup sub-View cannot be clicked and the setOnClickListener is not set to click the listener event, clicking View triggers the touch event of the sub-View first, and then triggers the touch event of the ViewGroup, the ViewGroup # dispatchTouchEvent method is executed, and the returned result is returned. The subsequent code is not executed.
10. Line of the Code is mainly used to execute the ACTION_UP and ACTION_CANCEL gesture operations of the sub-View. The logic is not analyzed here. For details, refer to the previous blog.
Summary:We know from the above analysis.
The onInterceptTouchEvent method is used to intercept the touch event of the ViewGroup child View. By default, false is returned. If the child View touch event is not intercepted, You can override this method, returns true to intercept the touch event passing of the sub-View. In this case, only the touch event of ViewGroup is passed. When the sub-View is unclickable and the setOnClickListener click listener is not set, the touch event of the sub-View is executed first, and then the touch event of the ViewGroup is executed.
The two conclusions are verified.
OnInterceptTouchEvent returns true
Modify the MyViewGroup code
public class MyViewGroup extends LinearLayout { private String TAG = MyViewGroup; public MyViewGroup(Context context) { super(context); requestDisallowInterceptTouchEvent(false); } public MyViewGroup(Context context, AttributeSet attrs) { super(context, attrs); } public MyViewGroup(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.e(TAG, MyViewGroup=====dispatchTouchEvent ); return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return true; }
Click the Button to print the result as follows:
08-01 18:36:56.815 29910-29910/com.xjp.viewgrouptouchdemo E/MyViewGroup﹕ MyViewGroup=====dispatchTouchEvent08-01 18:36:56.825 29910-29910/com.xjp.viewgrouptouchdemo E/MainActivity﹕ parentView=====onTouch108-01 18:36:56.825 29910-29910/com.xjp.viewgrouptouchdemo E/MainActivity﹕ parentView=====onClick
Printing shows that when the onInterceptTouchEvent method is rewritten to return true, the touch Click Event of the Button will not be executed. The preceding conclusion is also verified: When ViewGroup overwrites the onInterceptTouchEvent method to return true, that is, it intercepts the touch event transfer of the sub-View. At this time, only the touch event of ViewGroup is executed.
The sub-View cannot be clicked and the setOnClickListener is not set.
The code is modified as follows:
public class MainActivity extends ActionBarActivity { private String TAG = MainActivity; private MyViewGroup parentView; private ImageView childView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); parentView = (MyViewGroup) findViewById(R.id.parent); childView = (ImageView) findViewById(R.id.child); childView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { Log.e(TAG, childView=====onTouch); return false; } }); parentView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { Log.e(TAG, parentView=====onTouch +event.getAction()); return false; } }); parentView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.e(TAG, parentView=====onClick); } }); }}
Change the Button to ImageView without setting the setOnClickListener event. Click ImageView to print the log as follows:
08-01 18:52:44.720 31219-31219/com.xjp.viewgrouptouchdemo E/MyViewGroup﹕ MyViewGroup=====dispatchTouchEvent08-01 18:52:44.720 31219-31219/com.xjp.viewgrouptouchdemo E/MainActivity﹕ childView=====onTouch08-01 18:52:44.730 31219-31219/com.xjp.viewgrouptouchdemo E/MainActivity﹕ parentView=====onTouch0parentView=====onClick
Printing shows that the touch event of the sub-View ImageView is executed, and the touch event of the ViewGroup is also executed. ImageView is not clickable by default. Therefore, when a subview cannot be clicked or a setOnClickListener click event is set, clicking a subview is the first touch event of the View, and then execute the ViewGroup touch event. This also verifiesViewGroup # dispatchTouchEventThe 9th point of the Section.
The previous ViewGroup touch event transfer flowchart is attached.
To sum up, you can override the onInterceptTouchEvent method in ViewGroup to determine whether to intercept the sub-View transfer event. The system returns false by default, indicating that the sub-View event delivery is not blocked. If the rewrite returns true, blocks the touch events of the sub-View. When the sub-View is clickable or the setOnClickListener click event is set, android touch event distribution will not be passed to ViweGroup, that is, only the View touch event will be executed, the ViewGroup touch event is not executed. When a subview cannot be clicked and the setOnClickListener click event is not set, the Android touch event is first distributed to the View. The View executes the dispatchTouchEvent touch event first, and then transmits the event to the ViewGroup. The ViewGroup executes the dispatchTouchEvent touch event. Android event distribution is first transmitted to the View, and the View determines whether to pass the event to the ViewGroup.