1. Introduction
1.1 Event Composition
In Android, touchEvents mainly include clicking, long pressing, dragging and sliding, etc. All events are composed of the following three parts
-
Press (ACTION_DOWN)
-
Move (ACTION_MOVE)
-
Lift (ACTION_UP)
Generally speaking, a complete Touch event should be composed of one Down, one Up and several moves.
1.2View distribution events
-
Public Boolean dispatchTouchEvent (MotionEvent EV) This method must be called if the event can be passed to the current View.
-
Public Boolean onInterceptTouchEvent (MotionEvent EV) Specifies whether an event is intercepted
-
Public Boolean onTouchEvent(MotionEvent EV) Handle click events
There are three ViewGroup events: onInterceptTouchEvent, dispatchTouchEvent, and onTouchEvent. There are only two events associated with a View: dispatchTouchEvent and onTouchEvent.
1.3 Sliding Conflict Processing:
(1) External intercepting method (onInterceptTouchEvent in the parent container to control whether to distribute to child elements), according to the Android specification
(2) the internal intercept method (in child elements through the control of the parent container requestDisallowInterceptTouchEvent control)
2. Process
In Android, events are passed from top to bottom and then from bottom to top. The event is processed from the Activity to the ViewGroup to the View. If the View does not consume the event, the event will be passed from the View to the ViewGroup to the Activity again and the event will be thrown and consumed. The flow chart is as follows:
-
When dispatchTouchEvent and TouchEvnet Return false, the event is passed back to the parent control’s onTouchEvent handler
-
OnInterceptTouchEvent defaults to false and does not intercept events. OnDispatchTouchEvnet defaults to True for event distribution. OnTouchEvent is true depending on whether a listener is set for the particular View and the implementation of the particular View
-
The Activity’s dispatchTouchEvent is dispatched regardless of whether it returns true or false
-
If the event returns true in the onTouchEvent of a particular View or ViewGroup, the event is consumed and the event-passing mechanism ends
Example 3.
You can modify the return value of the corresponding method based on the flowchart for log verification. Log output is not shown here.
public class MyView extends Button {
private String TAG = "MyView";
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
TAG += getTag();
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "MyView dispatchTouchEvent--ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "MyView dispatchTouchEvent--ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "MyView dispatchTouchEvent--ACTION_UP");
break;
}
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "MyView onTouchEvent--ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "MyView onTouchEvent--ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "MyView onTouchEvent--ACTION_UP");
break;
}
boolean touch = super.onTouchEvent(event);
Log.i(TAG, "MyView onTouchEvent--touch" + touch);
returntouch; }}Copy the code
public class MyViewGroup extends RelativeLayout {
private String TAG = "MyViewGroup";
public MyViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
TAG+=getTag();
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "MyViewGroup dispatchTouchEvent--ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "MyViewGroup dispatchTouchEvent--ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "MyViewGroup dispatchTouchEvent--ACTION_UP");
break;
}
return super.dispatchTouchEvent(ev) ;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d(TAG, "MyViewGroup onInterceptTouchEvent--ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "MyViewGroup onInterceptTouchEvent--ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "MyViewGroup onInterceptTouchEvent--ACTION_UP");
break;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "MyViewGroup onTouchEvent--ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "MyViewGroup onTouchEvent--ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "MyViewGroup onTouchEvent--ACTION_UP");
break;
}
boolean touch = super.onTouchEvent(event);
Log.i(TAG, "MyViewGroup onTouchEvent--touch" + touch);
returntouch; }}Copy the code
<? xml version="1.0" encoding="utf-8"? > <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.jd.test.myapplication.MainActivity">
<com.jd.test.myapplication.view.MyViewGroup
android:id="@+id/mg1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:tag="mg1">
<com.jd.test.myapplication.view.MyView
android:id="@+id/v3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tag="btn3"
android:text="btn3" />
</com.jd.test.myapplication.view.MyViewGroup>
</RelativeLayout>
Copy the code
public class ViewEventActivity extends Activity {
Button btn3;
MyViewGroup myViewGroup1;
private String TAG="ViewEventActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_event);
btn3= (Button) findViewById(R.id.v3);
myViewGroup1= (MyViewGroup) findViewById(R.id.mg1);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "DecorView dispatchTouchEvent--ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "DecorView dispatchTouchEvent--ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "DecorView dispatchTouchEvent--ACTION_UP");
break;
}
return super.dispatchTouchEvent(ev);
//return true;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "DecorView onTouchEvent--ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "DecorView onTouchEvent--ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "DecorView onTouchEvent--ACTION_UP");
break;
}
boolean touch=super.onTouchEvent(event);
Log.d(TAG, "DecorView touch boolean---"+touch);
return touch;
}
Copy the code
4. Source code analysis
1. The Activity’s TouchEvent process
The Activity dispatchTouchEvnet code calls the Window’s superDispatchTouchEvent(). Activity:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
Copy the code
Window is an abstract class, its implementation is PhoneWindow, and PhoneWidow’s lowest level view is mDecorView, which is a FrameLayout. It’s basically a ViewGroup. PhoneWindow:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
Copy the code
The DecorView superDispatchTouchEvent
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
Copy the code
If the Activity implements a dispatchTouchEvent callback, it calls the change callback. If it does not, it calls super’s dispatchTouchEvent to continue event distribution to child views
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Callback cb = getCallback();
returncb ! = null && ! isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev); }Copy the code
2.DispatchTouchEvent
ViewGroup:
/**
* {@inheritDoc}
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if(mInputEventConsistencyVerifier ! = null) { mInputEventConsistencyVerifier.onTouchEvent(ev, 1); } // Event handling for auxiliary functionsif (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
boolean handled = false;
if(onFilterTouchEventForSecurity(ev)) { final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; // Handle the original DOWN eventif(actionMasked == motionevent.action_down) {// Due to an app switch, ANR, or some other state change. cancelAndClearTouchTargets(ev); resetTouchState(); } // Check event interception final Boolean intercepted;if(actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget ! = null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) ! = 0;if(! disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // Restore the event to prevent it from changing}else {
intercepted = false; }}else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true; } // If the event is intercepted, normal event distribution takes placeif(intercepted || mFirstTouchTarget ! = null) { ev.setTargetAccessibilityFocus(false); } / / check whether the event is cancelled final Boolean canceled = resetCancelNextUpFlag (this) | | actionMasked = = MotionEvent. ACTION_CANCEL; // If necessary, check all target objects for DOWN events final Boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS)! = 0; TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget =false; // If the event is not cancelled, it is not interceptedif(! canceled && ! intercepted) {// Post the event directly to the corresponding View if there is a helper function involved, Distributed to all child or event View View childWithAccessibilityFocus = ev. IsTargetAccessibilityFocus ()? findChildWithAccessibilityFocus() : null;if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in casethey // have become out of sync. removePointersFromTouchTargets(idBitsToAssign); final int childrenCount = mChildrenCount; // If TouchTarget is empty and the child element is not 0if(newTouchTarget == null && childrenCount ! = 0) { finalfloat x = ev.getX(actionIndex);
final floaty = ev.getY(actionIndex); // View final ArrayList<View> preorderedList = buildOrderedChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; // Iterate over the child elementsfor (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder
? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if(childWithAccessibilityFocus ! = null) {if(childWithAccessibilityFocus ! = child) {continue; } childWithAccessibilityFocus = null; i = childrenCount - 1; } // If the child cannot receive a Pointer Event or the Event voltage is not within the child's boundary at allif(! canViewReceivePointerEvents(child) || ! isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); // Then jump out of the loop and continue iteratingcontinue; } // Find the Event held by the child element newTouchTarget = getTouchTarget(child);if(newTouchTarget ! = null) { // Child is already receiving touch within its bounds. // Give it the new pointerin addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break; } resetCancelNextUpFlag(child); // If the child element is still a ViewGroup, the recursive call repeats the process. // If the child element is still a View, the dispatTouchEvent of the View is called, and finally handled by onTouchEventif (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {// Child View receives events within its bounds mLastTouchDownTime = ev.getdowntime ();if(preorderedList ! = null) { // childIndex points into presorted list, find original indexfor (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break; }}}else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
// The accessibility focus didn't handle the event, so clear // the flag and do a normal dispatch to all children. ev.setTargetAccessibilityFocus(false); } if (preorderedList ! = null) preorderedList.clear(); } if (newTouchTarget == null && mFirstTouchTarget! = null) { // Did not find a child to receive the event. // Assign the pointer to the least recently added target. newTouchTarget = mFirstTouchTarget; while (newTouchTarget.next ! = null) { newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |= idBitsToAssign; } } } // Dispatch to touch targets. if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { // Dispatch to touch targets, excluding the new touch target if we already // dispatched to it. Cancel touch targets if necessary. TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target ! = null) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } } // Update list of touch targets for pointer up or cancel, if needed. if (canceled || actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { resetTouchState(); } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) { final int actionIndex = ev.getActionIndex(); final int idBitsToRemove = 1 << ev.getPointerId(actionIndex); removePointersFromTouchTargets(idBitsToRemove); } } if (! handled && mInputEventConsistencyVerifier ! = null) { mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1); } return handled; }Copy the code
The View:
/**
* Dispatches a key shortcut event.
*
* @param event The key event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchKeyShortcutEvent(KeyEvent event) {
return onKeyShortcut(event.getKeyCode(), event);
}
/**
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event. if (! isAccessibilityFocusedViewOrHost()) { return false; } // We have focus and got the event, then use normal event dispatch. event.setTargetAccessibilityFocus(false); } boolean result = false; if (mInputEventConsistencyVerifier ! = null) { mInputEventConsistencyVerifier.onTouchEvent(event, 0); } final int actionMasked = event.getActionMasked(); if (actionMasked == MotionEvent.ACTION_DOWN) { // Defensive cleanup for new gesture stopNestedScroll(); } if (onFilterTouchEventForSecurity(event)) { //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li ! = null && li.mOnTouchListener ! = null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } if (! result && onTouchEvent(event)) { result = true; } } if (! result && mInputEventConsistencyVerifier ! = null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } // Clean up after nested scrolls if this is the end of a gesture; // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if(actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL || (actionMasked == MotionEvent.ACTION_DOWN && ! result)) { stopNestedScroll(); }return result;
}
Copy the code
3.onInterceptTouchEvent
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
Copy the code
4.TouchEvent
/**
* Implement this method to handle touch screen motion events.
* <p>
* If this method is used to detect click actions, it is recommended that
* the actions be performed by implementing and calling
* {@link #performClick()}. This will ensure consistent system behavior,
* including:
* <ul>
* <li>obeying click sound preferences
* <li>dispatching OnClickListener calls
* <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
* accessibility features are enabled
* </ul>
*
* @param event The motion event.
* @return True if the event was handled, false otherwise.
*/
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if(action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) ! = 0) {setPressed(false);
}
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them. return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE); } if (mTouchDelegate ! = null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) { switch (action) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) ! = 0; if ((mPrivateFlags & PFLAG_PRESSED) ! = 0 || prepressed) { // take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if(isFocusable() && isFocusableInTouchMode() && ! isFocused()) { focusTaken = requestFocus(); }if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
if(! mHasPerformedLongPress && ! mIgnoreNextUpEvent) { // This is a tap, so remove the longpress check removeLongPressCallback(); // Only perform take click actionsif we were in the pressed state
if(! focusTaken) { // Use a Runnable and post this rather than calling // performClick directly. This lets other visual state // of the view update before click actions start.if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if(! post(mPerformClick)) { performClick(); }}}if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if(! post(mUnsetPressedState)) { // If the post failed, unpress right now mUnsetPressedState.run(); } removeTapCallback(); } mIgnoreNextUpEvent =false;
break;
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container. boolean isInScrollingContainer = isInScrollingContainer(); // For views inside a scrolling container, delay the pressed feedback for // a short period in case this is a scroll. if (isInScrollingContainer) { mPrivateFlags |= PFLAG_PREPRESSED; if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } mPendingCheckForTap.x = event.getX(); mPendingCheckForTap.y = event.getY(); postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } else { // Not inside a scrolling container, so show the feedback right away setPressed(true, x, y); checkForLongClick(0); } break; case MotionEvent.ACTION_CANCEL: setPressed(false); removeTapCallback(); removeLongPressCallback(); mInContextButtonPress = false; mHasPerformedLongPress = false; mIgnoreNextUpEvent = false; break; case MotionEvent.ACTION_MOVE: drawableHotspotChanged(x, y); // Be lenient about moving outside of buttons if (! pointInView(x, y, mTouchSlop)) { // Outside button removeTapCallback(); if ((mPrivateFlags & PFLAG_PRESSED) ! = 0) { // Remove any future long press/tap checks removeLongPressCallback(); setPressed(false); } } break; } return true; } return false; }Copy the code
about
Welcome to pay attention to my personal public number
Wechat search: Yizhaofusheng, or search the official ID: Life2Code
- Author: Huang Junbin
- Blog: junbin. Tech
- GitHub: junbin1011
- Zhihu: @ JunBin