public virtual bool onTouchEvent(android.view.MotionEvent @event) { if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(@event, 0); } int action = @event.getActionMasked(); if (action == android.view.MotionEvent.ACTION_DOWN) { reset(); } // Start fresh bool handled = true; if (mInvalidGesture) { handled = false; } else { if (!mGestureInProgress) { switch (action) { case android.view.MotionEvent.ACTION_DOWN: { mActiveId0 = @event.getPointerId(0); mActive0MostRecent = true; break; } case android.view.MotionEvent.ACTION_UP: { reset(); break; } case android.view.MotionEvent.ACTION_POINTER_DOWN: { // We have a new multi-finger gesture // as orientation can change, query the metrics in touch down android.util.DisplayMetrics metrics = mContext.getResources().getDisplayMetrics(); mRightSlopEdge = metrics.widthPixels - mEdgeSlop; mBottomSlopEdge = metrics.heightPixels - mEdgeSlop; if (mPrevEvent != null) { mPrevEvent.recycle(); } mPrevEvent = android.view.MotionEvent.obtain(@event); mTimeDelta = 0; int index1 = @event.getActionIndex(); int index0 = @event.findPointerIndex(mActiveId0); mActiveId1 = @event.getPointerId(index1); if (index0 < 0 || index0 == index1) { // Probably someone sending us a broken event stream. index0 = findNewActiveIndex(@event, index0 == index1 ? -1 : mActiveId1, index0); mActiveId0 = @event.getPointerId(index0); } mActive0MostRecent = false; setContext(@event); // Check if we have a sloppy gesture. If so, delay // the beginning of the gesture until we're sure that's // what the user wanted. Sloppy gestures can happen if the // edge of the user's hand is touching the screen, for example. float edgeSlop = mEdgeSlop; float rightSlop = mRightSlopEdge; float bottomSlop = mBottomSlopEdge; float x0 = getRawX(@event, index0); float y0 = getRawY(@event, index0); float x1 = getRawX(@event, index1); float y1 = getRawY(@event, index1); bool p0sloppy = x0 <edgeSlop || y0 <edgeSlop || x0> rightSlop || y0> bottomSlop; bool p1sloppy = x1 <edgeSlop || y1 <edgeSlop || x1> rightSlop || y1> bottomSlop; if (p0sloppy && p1sloppy) { mFocusX = -1; mFocusY = -1; mSloppyGesture = true; } else { if (p0sloppy) { mFocusX = @event.getX(index1); mFocusY = @event.getY(index1); mSloppyGesture = true; } else { if (p1sloppy) { mFocusX = @event.getX(index0); mFocusY = @event.getY(index0); mSloppyGesture = true; } else { mSloppyGesture = false; mGestureInProgress = mListener.onScaleBegin(this); } } } break; } case android.view.MotionEvent.ACTION_MOVE: { if (mSloppyGesture) { // Initiate sloppy gestures if we've moved outside of the slop area. float edgeSlop = mEdgeSlop; float rightSlop = mRightSlopEdge; float bottomSlop = mBottomSlopEdge; int index0 = @event.findPointerIndex(mActiveId0); int index1 = @event.findPointerIndex(mActiveId1); float x0 = getRawX(@event, index0); float y0 = getRawY(@event, index0); float x1 = getRawX(@event, index1); float y1 = getRawY(@event, index1); bool p0sloppy = x0 <edgeSlop || y0 <edgeSlop || x0> rightSlop || y0> bottomSlop; bool p1sloppy = x1 <edgeSlop || y1 <edgeSlop || x1> rightSlop || y1> bottomSlop; if (p0sloppy) { // Do we have a different pointer that isn't sloppy? int index = findNewActiveIndex(@event, mActiveId1, index0); if (index >= 0) { index0 = index; mActiveId0 = @event.getPointerId(index); x0 = getRawX(@event, index); y0 = getRawY(@event, index); p0sloppy = false; } } if (p1sloppy) { // Do we have a different pointer that isn't sloppy? int index = findNewActiveIndex(@event, mActiveId0, index1); if (index >= 0) { index1 = index; mActiveId1 = @event.getPointerId(index); x1 = getRawX(@event, index); y1 = getRawY(@event, index); p1sloppy = false; } } if (p0sloppy && p1sloppy) { mFocusX = -1; mFocusY = -1; } else { if (p0sloppy) { mFocusX = @event.getX(index1); mFocusY = @event.getY(index1); } else { if (p1sloppy) { mFocusX = @event.getX(index0); mFocusY = @event.getY(index0); } else { mSloppyGesture = false; mGestureInProgress = mListener.onScaleBegin(this); } } } } break; } case android.view.MotionEvent.ACTION_POINTER_UP: { if (mSloppyGesture) { int pointerCount = @event.getPointerCount(); int actionIndex = @event.getActionIndex(); int actionId = @event.getPointerId(actionIndex); if (pointerCount > 2) { if (actionId == mActiveId0) { int newIndex = findNewActiveIndex(@event, mActiveId1, actionIndex); if (newIndex >= 0) { mActiveId0 = @event.getPointerId(newIndex); } } else { if (actionId == mActiveId1) { int newIndex = findNewActiveIndex(@event, mActiveId0, actionIndex); if (newIndex >= 0) { mActiveId1 = @event.getPointerId(newIndex); } } } } else { // Set focus point to the remaining finger int index = @event.findPointerIndex(actionId == mActiveId0 ? mActiveId1 : mActiveId0 ); if (index < 0) { mInvalidGesture = true; android.util.Log.e(TAG, "Invalid MotionEvent stream detected.", new System.Exception ()); if (mGestureInProgress) { mListener.onScaleEnd(this); } return(false); } mActiveId0 = @event.getPointerId(index); mActive0MostRecent = true; mActiveId1 = -1; mFocusX = @event.getX(index); mFocusY = @event.getY(index); } } break; } } } else { switch (action) { case android.view.MotionEvent.ACTION_POINTER_DOWN: { // Transform gesture in progress - attempt to handle it // End the old gesture and begin a new one with the most recent two fingers. mListener.onScaleEnd(this); int oldActive0 = mActiveId0; int oldActive1 = mActiveId1; reset(); mPrevEvent = android.view.MotionEvent.obtain(@event); mActiveId0 = mActive0MostRecent ? oldActive0 : oldActive1; mActiveId1 = @event.getPointerId(@event.getActionIndex()); mActive0MostRecent = false; int index0 = @event.findPointerIndex(mActiveId0); if (index0 < 0 || mActiveId0 == mActiveId1) { // Probably someone sending us a broken event stream. android.util.Log.e(TAG, "Got " + android.view.MotionEvent.actionToString(action) + " with bad state while a gesture was in progress. " + "Did you forget to pass an event to " + "ScaleGestureDetector#onTouchEvent?"); index0 = findNewActiveIndex(@event, mActiveId0 == mActiveId1 ? -1 : mActiveId1, index0 ); mActiveId0 = @event.getPointerId(index0); } setContext(@event); mGestureInProgress = mListener.onScaleBegin(this); break; } case android.view.MotionEvent.ACTION_POINTER_UP: { int pointerCount = @event.getPointerCount(); int actionIndex = @event.getActionIndex(); int actionId = @event.getPointerId(actionIndex); bool gestureEnded = false; if (pointerCount > 2) { if (actionId == mActiveId0) { int newIndex = findNewActiveIndex(@event, mActiveId1, actionIndex); if (newIndex >= 0) { mListener.onScaleEnd(this); mActiveId0 = @event.getPointerId(newIndex); mActive0MostRecent = true; mPrevEvent = android.view.MotionEvent.obtain(@event); setContext(@event); mGestureInProgress = mListener.onScaleBegin(this); } else { gestureEnded = true; } } else { if (actionId == mActiveId1) { int newIndex = findNewActiveIndex(@event, mActiveId0, actionIndex); if (newIndex >= 0) { mListener.onScaleEnd(this); mActiveId1 = @event.getPointerId(newIndex); mActive0MostRecent = false; mPrevEvent = android.view.MotionEvent.obtain(@event); setContext(@event); mGestureInProgress = mListener.onScaleBegin(this); } else { gestureEnded = true; } } } mPrevEvent.recycle(); mPrevEvent = android.view.MotionEvent.obtain(@event); setContext(@event); } else { gestureEnded = true; } if (gestureEnded) { // Gesture ended setContext(@event); // Set focus point to the remaining finger int activeId = actionId == mActiveId0 ? mActiveId1 : mActiveId0; int index = @event.findPointerIndex(activeId); mFocusX = @event.getX(index); mFocusY = @event.getY(index); mListener.onScaleEnd(this); reset(); mActiveId0 = activeId; mActive0MostRecent = true; } break; } case android.view.MotionEvent.ACTION_CANCEL: { mListener.onScaleEnd(this); reset(); break; } case android.view.MotionEvent.ACTION_UP: { reset(); break; } case android.view.MotionEvent.ACTION_MOVE: { setContext(@event); // Only accept the event if our relative pressure is within // a certain limit - this can help filter shaky data as a // finger is lifted. if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) { bool updatePrevious = mListener.onScale(this); if (updatePrevious) { mPrevEvent.recycle(); mPrevEvent = android.view.MotionEvent.obtain(@event); } } break; } } } } if (!handled && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(@event, 0); } return(handled); }
/// <summary>Checks a touch event.</summary> /// <remarks>Checks a touch event.</remarks> /// <param name="event">The event.</param> /// <param name="nestingLevel"> /// The nesting level: 0 if called from the base class, /// or 1 from a subclass. If the event was already checked by this consistency verifier /// at a higher nesting level, it will not be checked again. Used to handle the situation /// where a subclass dispatching method delegates to its superclass's dispatching method /// and both dispatching methods call into the consistency verifier. /// </param> public void onTouchEvent(android.view.MotionEvent @event, int nestingLevel) { if (!startEvent(@event, nestingLevel, EVENT_TYPE_TOUCH)) { return; } int action = @event.getAction(); bool newStream = action == android.view.MotionEvent.ACTION_DOWN || action == android.view.MotionEvent .ACTION_CANCEL; if (newStream && (mTouchEventStreamIsTainted || mTouchEventStreamUnhandled)) { mTouchEventStreamIsTainted = false; mTouchEventStreamUnhandled = false; mTouchEventStreamPointers = 0; } if (mTouchEventStreamIsTainted) { @event.setTainted(true); } try { ensureMetaStateIsNormalized(@event.getMetaState()); int deviceId = @event.getDeviceId(); int source = @event.getSource(); if (!newStream && mTouchEventStreamDeviceId != -1 && (mTouchEventStreamDeviceId != deviceId || mTouchEventStreamSource != source)) { problem("Touch event stream contains events from multiple sources: " + "previous device id " + mTouchEventStreamDeviceId + ", previous source " + Sharpen.Util.IntToHexString (mTouchEventStreamSource) + ", new device id " + deviceId + ", new source " + Sharpen.Util.IntToHexString (source)); } mTouchEventStreamDeviceId = deviceId; mTouchEventStreamSource = source; int pointerCount = @event.getPointerCount(); if ((source & android.view.InputDevice.SOURCE_CLASS_POINTER) != 0) { switch (action) { case android.view.MotionEvent.ACTION_DOWN: { if (mTouchEventStreamPointers != 0) { problem("ACTION_DOWN but pointers are already down. " + "Probably missing ACTION_UP from previous gesture." ); } ensureHistorySizeIsZeroForThisAction(@event); ensurePointerCountIsOneForThisAction(@event); mTouchEventStreamPointers = 1 << @event.getPointerId(0); break; } case android.view.MotionEvent.ACTION_UP: { ensureHistorySizeIsZeroForThisAction(@event); ensurePointerCountIsOneForThisAction(@event); mTouchEventStreamPointers = 0; mTouchEventStreamIsTainted = false; break; } case android.view.MotionEvent.ACTION_MOVE: { int expectedPointerCount = Sharpen.Util.IntGetBitCount(mTouchEventStreamPointers); if (pointerCount != expectedPointerCount) { problem("ACTION_MOVE contained " + pointerCount + " pointers but there are currently " + expectedPointerCount + " pointers down."); mTouchEventStreamIsTainted = true; } break; } case android.view.MotionEvent.ACTION_CANCEL: { mTouchEventStreamPointers = 0; mTouchEventStreamIsTainted = false; break; } case android.view.MotionEvent.ACTION_OUTSIDE: { if (mTouchEventStreamPointers != 0) { problem("ACTION_OUTSIDE but pointers are still down."); } ensureHistorySizeIsZeroForThisAction(@event); ensurePointerCountIsOneForThisAction(@event); mTouchEventStreamIsTainted = false; break; } default: { int actionMasked = @event.getActionMasked(); int actionIndex = @event.getActionIndex(); if (actionMasked == android.view.MotionEvent.ACTION_POINTER_DOWN) { if (mTouchEventStreamPointers == 0) { problem("ACTION_POINTER_DOWN but no other pointers were down."); mTouchEventStreamIsTainted = true; } if (actionIndex < 0 || actionIndex >= pointerCount) { problem("ACTION_POINTER_DOWN index is " + actionIndex + " but the pointer count is " + pointerCount + "."); mTouchEventStreamIsTainted = true; } else { int id = @event.getPointerId(actionIndex); int idBit = 1 << id; if ((mTouchEventStreamPointers & idBit) != 0) { problem("ACTION_POINTER_DOWN specified pointer id " + id + " which is already down." ); mTouchEventStreamIsTainted = true; } else { mTouchEventStreamPointers |= idBit; } } ensureHistorySizeIsZeroForThisAction(@event); } else { if (actionMasked == android.view.MotionEvent.ACTION_POINTER_UP) { if (actionIndex < 0 || actionIndex >= pointerCount) { problem("ACTION_POINTER_UP index is " + actionIndex + " but the pointer count is " + pointerCount + "."); mTouchEventStreamIsTainted = true; } else { int id = @event.getPointerId(actionIndex); int idBit = 1 << id; if ((mTouchEventStreamPointers & idBit) == 0) { problem("ACTION_POINTER_UP specified pointer id " + id + " which is not currently down." ); mTouchEventStreamIsTainted = true; } else { mTouchEventStreamPointers &= ~idBit; } } ensureHistorySizeIsZeroForThisAction(@event); } else { problem("Invalid action " + android.view.MotionEvent.actionToString(action) + " for touch event." ); } } break; } } } else { problem("Source was not SOURCE_CLASS_POINTER."); } } finally { finishEvent(); } }