public override bool dispatchTouchEvent(android.view.MotionEvent ev) { if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(ev, 1); } bool handled = false; if (onFilterTouchEventForSecurity(ev)) { int action = ev.getAction(); int actionMasked = action & android.view.MotionEvent.ACTION_MASK; // Handle an initial down. if (actionMasked == android.view.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); resetTouchState(); } // Check for interception. bool intercepted; if (actionMasked == android.view.MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { bool disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action); } else { // restore action in case it was changed 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; } // Check for cancelation. bool canceled = resetCancelNextUpFlag(this) || actionMasked == android.view.MotionEvent .ACTION_CANCEL; // Update list of touch targets for pointer down, if needed. bool split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; android.view.ViewGroup.TouchTarget newTouchTarget = null; bool alreadyDispatchedToNewTouchTarget = false; if (!canceled && !intercepted) { if (actionMasked == android.view.MotionEvent.ACTION_DOWN || (split && actionMasked == android.view.MotionEvent.ACTION_POINTER_DOWN) || actionMasked == android.view.MotionEvent .ACTION_HOVER_MOVE) { int actionIndex = ev.getActionIndex(); // always 0 for down int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) : android.view.ViewGroup .TouchTarget.ALL_POINTER_IDS; // Clean up earlier touch targets for this pointer id in case they // have become out of sync. removePointersFromTouchTargets(idBitsToAssign); int childrenCount = mChildrenCount; if (childrenCount != 0) { // Find a child that can receive the event. // Scan children from front to back. android.view.View[] children = mChildren; float x = ev.getX(actionIndex); float y = ev.getY(actionIndex); { for (int i = childrenCount - 1; i >= 0; i--) { android.view.View child = children[i]; if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child , null)) { continue; } 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. newTouchTarget.pointerIdBits |= idBitsToAssign; break; } resetCancelNextUpFlag(child); if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // Child wants to receive touch within its bounds. mLastTouchDownTime = ev.getDownTime(); mLastTouchDownIndex = i; mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } } } } 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, android.view.ViewGroup .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. android.view.ViewGroup.TouchTarget predecessor = null; android.view.ViewGroup.TouchTarget target = mFirstTouchTarget; while (target != null) { android.view.ViewGroup.TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { bool 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 == android.view.MotionEvent.ACTION_UP || actionMasked == android.view.MotionEvent.ACTION_HOVER_MOVE) { resetTouchState(); } else { if (split && actionMasked == android.view.MotionEvent.ACTION_POINTER_UP) { int actionIndex = ev.getActionIndex(); int idBitsToRemove = 1 << ev.getPointerId(actionIndex); removePointersFromTouchTargets(idBitsToRemove); } } } if (!handled && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1); } return handled; }
/// <summary> /// Transforms a motion event into the coordinate space of a particular child view, /// filters out irrelevant pointer ids, and overrides its action if necessary. /// </summary> /// <remarks> /// Transforms a motion event into the coordinate space of a particular child view, /// filters out irrelevant pointer ids, and overrides its action if necessary. /// If child is null, assumes the MotionEvent will be sent to this ViewGroup instead. /// </remarks> private bool dispatchTransformedTouchEvent(android.view.MotionEvent @event, bool cancel, android.view.View child, int desiredPointerIdBits) { bool handled; // Canceling motions is a special case. We don't need to perform any transformations // or filtering. The important part is the action, not the contents. int oldAction = @event.getAction(); if (cancel || oldAction == android.view.MotionEvent.ACTION_CANCEL) { @event.setAction(android.view.MotionEvent.ACTION_CANCEL); if (child == null) { handled = base.dispatchTouchEvent(@event); } else { handled = child.dispatchTouchEvent(@event); } @event.setAction(oldAction); return handled; } // Calculate the number of pointers to deliver. int oldPointerIdBits = @event.getPointerIdBits(); int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits; // If for some reason we ended up in an inconsistent state where it looks like we // might produce a motion event with no pointers in it, then drop the event. if (newPointerIdBits == 0) { return false; } // If the number of pointers is the same and we don't need to perform any fancy // irreversible transformations, then we can reuse the motion event for this // dispatch as long as we are careful to revert any changes we make. // Otherwise we need to make a copy. android.view.MotionEvent transformedEvent; if (newPointerIdBits == oldPointerIdBits) { if (child == null || child.hasIdentityMatrix()) { if (child == null) { handled = base.dispatchTouchEvent(@event); } else { float offsetX = mScrollX - child.mLeft; float offsetY = mScrollY - child.mTop; @event.offsetLocation(offsetX, offsetY); handled = child.dispatchTouchEvent(@event); @event.offsetLocation(-offsetX, -offsetY); } return handled; } transformedEvent = android.view.MotionEvent.obtain(@event); } else { transformedEvent = @event.split(newPointerIdBits); } // Perform any necessary transformations and dispatch. if (child == null) { handled = base.dispatchTouchEvent(transformedEvent); } else { float offsetX = mScrollX - child.mLeft; float offsetY = mScrollY - child.mTop; transformedEvent.offsetLocation(offsetX, offsetY); if (!child.hasIdentityMatrix()) { transformedEvent.transform(child.getInverseMatrix()); } handled = child.dispatchTouchEvent(transformedEvent); } // Done. transformedEvent.recycle(); return handled; }
protected internal override bool dispatchHoverEvent(android.view.MotionEvent @event ) { int action = @event.getAction(); // First check whether the view group wants to intercept the hover event. bool interceptHover = onInterceptHoverEvent(@event); @event.setAction(action); // restore action in case it was changed android.view.MotionEvent eventNoHistory = @event; bool handled = false; // Send events to the hovered children and build a new list of hover targets until // one is found that handles the event. android.view.ViewGroup.HoverTarget firstOldHoverTarget = mFirstHoverTarget; mFirstHoverTarget = null; if (!interceptHover && action != android.view.MotionEvent.ACTION_HOVER_EXIT) { float x = @event.getX(); float y = @event.getY(); int childrenCount = mChildrenCount; if (childrenCount != 0) { android.view.View[] children = mChildren; android.view.ViewGroup.HoverTarget lastHoverTarget = null; { for (int i = childrenCount - 1; i >= 0; i--) { android.view.View child = children[i]; if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child , null)) { continue; } // Obtain a hover target for this child. Dequeue it from the // old hover target list if the child was previously hovered. android.view.ViewGroup.HoverTarget hoverTarget = firstOldHoverTarget; bool wasHovered; { for (android.view.ViewGroup.HoverTarget predecessor = null; ; ) { if (hoverTarget == null) { hoverTarget = android.view.ViewGroup.HoverTarget.obtain(child); wasHovered = false; break; } if (hoverTarget.child == child) { if (predecessor != null) { predecessor.next = hoverTarget.next; } else { firstOldHoverTarget = hoverTarget.next; } hoverTarget.next = null; wasHovered = true; break; } predecessor = hoverTarget; hoverTarget = hoverTarget.next; } } // Enqueue the hover target onto the new hover target list. if (lastHoverTarget != null) { lastHoverTarget.next = hoverTarget; } else { lastHoverTarget = hoverTarget; mFirstHoverTarget = hoverTarget; } // Dispatch the event to the child. if (action == android.view.MotionEvent.ACTION_HOVER_ENTER) { if (!wasHovered) { // Send the enter as is. handled |= dispatchTransformedGenericPointerEvent(@event, child); } } else { // enter if (action == android.view.MotionEvent.ACTION_HOVER_MOVE) { if (!wasHovered) { // Synthesize an enter from a move. eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory); eventNoHistory.setAction(android.view.MotionEvent.ACTION_HOVER_ENTER); handled |= dispatchTransformedGenericPointerEvent(eventNoHistory, child); // enter eventNoHistory.setAction(action); handled |= dispatchTransformedGenericPointerEvent(eventNoHistory, child); } else { // move // Send the move as is. handled |= dispatchTransformedGenericPointerEvent(@event, child); } } } if (handled) { break; } } } } } // Send exit events to all previously hovered children that are no longer hovered. while (firstOldHoverTarget != null) { android.view.View child = firstOldHoverTarget.child; // Exit the old hovered child. if (action == android.view.MotionEvent.ACTION_HOVER_EXIT) { // Send the exit as is. handled |= dispatchTransformedGenericPointerEvent(@event, child); } else { // exit // Synthesize an exit from a move or enter. // Ignore the result because hover focus has moved to a different view. if (action == android.view.MotionEvent.ACTION_HOVER_MOVE) { dispatchTransformedGenericPointerEvent(@event, child); } // move eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory); eventNoHistory.setAction(android.view.MotionEvent.ACTION_HOVER_EXIT); dispatchTransformedGenericPointerEvent(eventNoHistory, child); // exit eventNoHistory.setAction(action); } android.view.ViewGroup.HoverTarget nextOldHoverTarget = firstOldHoverTarget.next; firstOldHoverTarget.recycle(); firstOldHoverTarget = nextOldHoverTarget; } // Send events to the view group itself if no children have handled it. bool newHoveredSelf = !handled; if (newHoveredSelf == mHoveredSelf) { if (newHoveredSelf) { // Send event to the view group as before. handled |= base.dispatchHoverEvent(@event); } } else { if (mHoveredSelf) { // Exit the view group. if (action == android.view.MotionEvent.ACTION_HOVER_EXIT) { // Send the exit as is. handled |= base.dispatchHoverEvent(@event); } else { // exit // Synthesize an exit from a move or enter. // Ignore the result because hover focus is moving to a different view. if (action == android.view.MotionEvent.ACTION_HOVER_MOVE) { base.dispatchHoverEvent(@event); } // move eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory); eventNoHistory.setAction(android.view.MotionEvent.ACTION_HOVER_EXIT); base.dispatchHoverEvent(eventNoHistory); // exit eventNoHistory.setAction(action); } mHoveredSelf = false; } if (newHoveredSelf) { // Enter the view group. if (action == android.view.MotionEvent.ACTION_HOVER_ENTER) { // Send the enter as is. handled |= base.dispatchHoverEvent(@event); // enter mHoveredSelf = true; } else { if (action == android.view.MotionEvent.ACTION_HOVER_MOVE) { // Synthesize an enter from a move. eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory); eventNoHistory.setAction(android.view.MotionEvent.ACTION_HOVER_ENTER); handled |= base.dispatchHoverEvent(eventNoHistory); // enter eventNoHistory.setAction(action); handled |= base.dispatchHoverEvent(eventNoHistory); // move mHoveredSelf = true; } } } } // Recycle the copy of the event that we made. if (eventNoHistory != @event) { eventNoHistory.recycle(); } // Done. return handled; }