Rewrite OnTouchEvent() and write the touch algorithm inside the method
Return true, indicating that the event is consumed, all touch feedback is no longer effective, and event ownership is returned
if (event.actionMasked == MotionEvent.ACTION_UP){ performClick()//Lift the event to execute performClick to trigger the click }
override fun onTouchEvent(event: MotionEvent): Boolean { //event touch event }
//All touch events are a sequence, such as Down-Up, Down-Up-Cancel, Down-Move
//Example 1
View TouchEvnet ->ViewGroup TouchEvent
//Example 2
View1 TouchEvnet ->View2 TouchEvnet ->ViewGroup TouchEvent
The difference between event.action and ActionMarked
ActionMarked properties:
ACTION_MASK
ACTION_DOWN
ACTION_UP
ACTION_MOVE
ACTION_CANCEL
ACTION_OUTSIDE
ACTION_POINTER_DOWN
ACTION_POINTER_UP
ACTION_HOVER_MOVE
ACTION_SCROLL
ACTION_HOVER_ENTER
ACTION_HOVER_EXIT
ACTION_BUTTON_PRESS
ACTION_BUTTON_RELEASE
ACTION_POINTER_INDEX_MASK
ACTION_POINTER_INDEX_SHIFT
….
Compared with Action, ActionMarked is suitable for multi-touch //ACTION_POINTER_UP when the first finger is not lifted
The new version will be based on events and Pointers
event.Action will get multiple events. For example, down may be action-down – action-pointer-down, which will merge multiple events.
onTouchEvent
public boolean onTouchEvent(MotionEvent event) { final float x = event.getX(); //x coordinate final float y = event.getY(); //y coordinate final int viewFlags = mViewFlags; final int action = event.getAction(); //Action final boolean clickable = ((viewFlags & amp; CLICKABLE) == CLICKABLE || (viewFlags & amp; LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & amp; CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE; //Conform to click, long press context, all belong to click events if ((viewFlags & amp; ENABLED_MASK) == DISABLED) { if (action == MotionEvent.ACTION_UP & amp; & amp; (mPrivateFlags & amp; PFLAG_PRESSED) != 0) { setPressed(false); //Lift the mark to mark unclicked } mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return clickable; //If clickable but disabled, return clickable to mark consumption } if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; //mTouchDelegate adds click area } } if (clickable || (viewFlags & amp; TOOLTIP) == TOOLTIP) { //TOOLTIP API26 and above new features, explanation tools, auxiliary tools //Clickable or with auxiliary tools to explain the description switch (action) { case MotionEvent.ACTION_UP: mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; if ((viewFlags & amp; TOOLTIP) == TOOLTIP) { //Auxiliary prompt, release your finger and disappear for 1500ms handleTooltipUp(); } if (!clickable) { //Not clickable, cancel all events removeTapCallback(); removeLongPressCallback(); mInContextButtonPress = false; mHasPerformedLongPress = false; mIgnoreNextUpEvent = false; break; } boolean prepressed = (mPrivateFlags & amp; PFLAG_PREPRESSED) != 0; //Press or prepare to press if ((mPrivateFlags & amp; PFLAG_PRESSED) != 0 || prepressed) { boolean focusTaken = false; //If focus can be obtained and touch mode can obtain focus if (isFocusable() & amp; & amp; isFocusableInTouchMode() & amp; & amp; !isFocused()) { focusTaken = requestFocus(); } //Prepare to press 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); //The pressed state is true } if (!mHasPerformedLongPress & amp; & amp; !mIgnoreNextUpEvent) { // This is a tap, so remove the longpress check removeLongPressCallback(); //Remove long press operation // Only perform take click actions if we were in the pressed state if (!focusTaken) { //Not ready to press // 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)) { //Lift up to trigger click performClickInternal(); } } } if (mUnsetPressedState == null) { mUnsetPressedState = new UnsetPressedState(); } //Delay operation is left blank for 64ms and automatically raised 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: if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) { mPrivateFlags3 |= PFLAG3_FINGER_DOWN; //Whether the View is touched } mHasPerformedLongPress = false; if (!clickable) { //If it is not clickable, check the long press and there will be a delay in execution. checkForLongClick( ViewConfiguration.getLongPressTimeout(), x, y, TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS); break; } if (performButtonActionOnTouchDown(event)) { //right mouse click to display context menu break; } /* If the sliding control keeps recursing and returns the status public boolean isInScrollingContainer() { ViewParent p = getParent(); while (p != null & amp; & amp; p instanceof ViewGroup) { if (((ViewGroup) p).shouldDelayChildPressedState()) { //shouldDelayChildPressedState Whether to delay the child View delay state return true; } p = p.getParent(); } return false; } */ boolean isInScrollingContainer = isInScrollingContainer(); //Pre-press whether to slide or press for delay judgment if (isInScrollingContainer) { mPrivateFlags |= PFLAG_PREPRESSED; if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); /* private final class CheckForTap implements Runnable { public float x; public float y; @Override public void run() { mPrivateFlags & amp;= ~PFLAG_PREPRESSED; //Blank pre-click setPressed(true, x, y); final long delay = //Check long press ViewConfiguration.getLongPressTimeout() - ViewConfiguration.getTapTimeout(); checkForLongClick(delay, x, y, TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS); } } */ } mPendingCheckForTap.x = event.getX(); mPendingCheckForTap.y = event.getY(); postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout() ); //100ms } else { // Not inside a scrolling container, so show the feedback right away setPressed(true, x, y); //Set the sliding control to the pressed state checkForLongClick( //Check whether to long press and set the waiter ViewConfiguration.getLongPressTimeout(), //500ms x, y, TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS); //Pre-press needs to wait getLongPressTimeout 500 - getTapTimeout 100ms } break; case MotionEvent.ACTION_CANCEL: //Remove all events if (clickable) { setPressed(false); } removeTapCallback(); removeLongPressCallback(); mInContextButtonPress = false; mHasPerformedLongPress = false; mIgnoreNextUpEvent = false; mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; break; case MotionEvent.ACTION_MOVE: if (clickable) { //Clickable ripple effect after 5.0 drawableHotspotChanged(x, y); } final int motionClassification = event.getClassification(); final boolean ambiguousGesture = //Unspecified gesture, add long press event motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE; int touchSlop = mTouchSlop; if (ambiguousGesture & amp; & amp; hasPendingLongPressCallback()) { final float ambiguousMultiplier = ViewConfiguration.getAmbiguousGestureMultiplier(); if (!pointInView(x, y, touchSlop)) { //If the finger points to the boundary, touchSlop touches the boundary and increases the extended long press // The default action here is to cancel long press. But instead, we // just extend the timeout here, in case the classification // stays ambiguous. removeLongPressCallback(); // Double the ms of the long press event long delay = (long) (ViewConfiguration.getLongPressTimeout() * ambiguousMultiplier); // Subtract the time already spent delay -= event.getEventTime() - event.getDownTime(); //Check long press checkForLongClick( delay, x, y, TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS); } touchSlop *= ambiguousMultiplier; } // Be lenient about moving outside of buttons if (!pointInView(x, y, touchSlop)) { //cancel event removeTapCallback(); removeLongPressCallback(); if ((mPrivateFlags & amp; PFLAG_PRESSED) != 0) { setPressed(false); } mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; } //Android 10 press hard to trigger long press final boolean deepPress = motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS; if (deepPress & amp; & amp; hasPendingLongPressCallback()) { // process the long click action immediately removeLongPressCallback(); checkForLongClick( 0 /* send immediately */, x, y, TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS); } break; } }
If it is not a sliding Layout, you can override holdDelayChildPressedState false to turn off delayed click events.
ViewGroup:
onIntercerceptTouchEvent
onTouchEvent
onIntercerceptTouchEvent determines whether to intercept
1.
ViewGroup onIntercerceptTouchEvent false ->View -> onTouchEvent false ->… ->ViewGroup ->onTouchEvent
2.
ViewGroup onIntercerceptTouchEvent false -> ViewGroup onIntercerceptTouchEvent false -> View -> onTouchEvent false ->… ->ViewGroup ->onTouchEvent ->ViewGroup ->onTouchEvent
3.
ViewGroup onIntercerceptTouchEven true -> ViewGroup -> onTouchEvent true
ViewGroup onInterceptTouchEvent It is recommended to return false first to release, and then record the event/data to prepare
disparchTouchEvent manages ViewGroup’s onIntercerceptTouchEvent / onTouchEvent
disparchTouchEvent manages onTouchEvent with View
The child View dispatchTouchEvent returns true and the consumption event is no longer delivered.
ViewGroup dispatchTouchEvent calls super.dispatchTouchEvent
View dispatchTouchEvnet:
if (onFilterTouchEventForSecurity(event)) { if ((mViewFlags & amp; ENABLED_MASK) == ENABLED & amp; & amp; handleScrollBarDragging(event)) { result = true; } //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; //mOnTouchListener touch event monitoring does not call onTouchEvnet if (li != null & amp; & amp; li.mOnTouchListener != null & amp; & amp; (mViewFlags & amp; ENABLED_MASK) == ENABLED & amp; & amp; li.mOnTouchListener.onTouch(this, event)) { result = true; } if (!result & amp; & amp; onTouchEvent(event)) { result = true; } return TouchEvent }
ViewGroup.dispatchTouchEvent -> Core
if(intercptTouchEvent)
{onTouchEvent}
else{child View.dispatchTouchEvent}
@Override public boolean dispatchTouchEvent(MotionEvent ev){ if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(ev, 1); } //obstacle related // If the event targets the accessibility focused view and this is it, start // normal event dispatch. Maybe a descendant is what will handle the click. if (ev.isTargetAccessibilityFocus() & amp; & amp; isAccessibilityFocusedViewOrHost()) { ev.setTargetAccessibilityFocus(false); } boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { final int action = ev.getAction(); final int actionMasked = action & amp; MotionEvent.ACTION_MASK; // Handle an initial down. if (actionMasked == MotionEvent.ACTION_DOWN) { // Throw away all previous state when starting a new touch gesture. // The framework may have dropped the up or cancel event for the previous gesture // due to an app switch, ANR, or some other state change. cancelAndClearTouchTargets(ev); //Clear the state, make sure the view is not pressed, it is a brand new sequence down resetTouchState();//Intercept child View } // Check for interception. final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN // Press the event to determine whether the sub-View is intercepted || mFirstTouchTarget != null) { //mFirstTouchTarget has a sub-View pressed final boolean disallowIntercept = (mGroupFlags & amp; FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { //Do not intercept intercepted = false; } } else { // There are no touch targets and this action is not an initial down // so this view group continues to intercept touches. //interception intercepted = true; } // If intercepted, start normal event dispatch. Also if there is already // a view that is handling the gesture, do normal event dispatch. if (intercepted || mFirstTouchTarget != null) { ev.setTargetAccessibilityFocus(false); } // Check for cancellation. final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; // Update list of touch targets for pointer down, if needed. final boolean split = (mGroupFlags & amp; FLAG_SPLIT_MOTION_EVENTS) != 0; TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; //Non-cancellation and interception status if (!canceled & amp; & amp; !intercepted) { // If the event is targeting accessibility focus we give it to the // view that has accessibility focus and if it does not handle it // we clear the flag and dispatch the event to all children as usual. // We are looking up the accessibility focused host to avoid keeping // state since these events are very rare. View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus() ? findChildWithAccessibilityFocus() : null; //split multi-touch, if (actionMasked == MotionEvent.ACTION_DOWN || (split & amp; & amp; 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 case they // have become out of sync. removePointersFromTouchTargets(idBitsToAssign); final int childrenCount = mChildrenCount; if (newTouchTarget == null & amp; & amp; childrenCount != 0) { final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); // Find a child that can receive the event. // Scan children from front to back. final ArrayList<View> preorderedList = buildTouchDispatchChildList(); final boolean customOrder = preorderedList == null & amp; & amp; isChildrenDrawingOrderEnabled(); final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView( preorderedList, children, 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 (!child.canReceivePointerEvents() || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; } //Get whether there is a touching sub-View that can receive it newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { // Child is already receiving touch within its bounds. // Give it the new pointer in addition to the ones it is handling. //pointerIdBits added newTouchTarget.pointerIdBits |= idBitsToAssign; break; } resetCancelNextUpFlag(child); //No sub-View that is being touched can receive the event. Try to find a new sub-View to receive the event. if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // Child wants to receive touch within its bounds. mLastTouchDownTime = ev.getDownTime(); if (preorderedList != null) { // childIndex points into presorted list, find original index for (int j = 0; j < childrenCount; j + + ) { if (children[childIndex] == mChildren[j]) { mLastTouchDownIndex = j; break; } } } else { mLastTouchDownIndex = childIndex; } mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); //Add to 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 & amp; & amp; 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. //Sub View does not receive events if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. //Call your own TouchEvent 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; //The inner sub-View that was touched while (target != null) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget & amp; & amp; target == newTouchTarget) { handled = true; } else { final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; //Handle subView offset 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 & amp; & amp; actionMasked == MotionEvent.ACTION_POINTER_UP) { final int actionIndex = ev.getActionIndex(); final int idBitsToRemove = 1 << ev.getPointerId(actionIndex); removePointersFromTouchTargets(idBitsToRemove); } } if (!handled & amp; & amp; mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1); } return handled; } @Override public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { //After down and before up, called in the parent View ViewGroup, the same event is not intercepted if (disallowIntercept == ((mGroupFlags & amp; FLAG_DISALLOW_INTERCEPT) != 0)) { // We're already in this state, assume our ancestors are too return; } if (disallowIntercept) { mGroupFlags |= FLAG_DISALLOW_INTERCEPT; } else { mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; } // Pass it up to our parent if (mParent != null) { mParent.requestDisallowInterceptTouchEvent(disallowIntercept); } } dispatchTransformedTouchEvent () ViewGroup offset calculation, processing sub-View for conversion