天天看點

GestureDetector手勢檢測

GestureDetector

手勢檢測,用于輔助檢測使用者的單擊、滑動、長按、輕按兩下等行為。參考如下。

GestureDetector  gestureDetector=new GestureDetector(this, new GestureDetector.SimpleOnGestureListener(){

        @Override

        public boolean onDown(MotionEvent e) {

            //點了一下螢幕,ACTION_DOWN觸發

            return false;

        }

        @Override

        public void onShowPress(MotionEvent e) {

            //點選螢幕中,沒有松開和移動,ACTION_DOWN觸發

        }

        @Override

        public boolean onSingleTapUp(MotionEvent e) {

            //單擊事件,輕輕點選螢幕後松開,ACTION_DOWN觸發和ACTION_UP觸發

            return false;

        }

        @Override

        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {

            //拖動事件,點選螢幕後拖動,ACTION_DOWN觸發和多個ACTION_MOVE觸發

            return false;

        }

        @Override

        public void onLongPress(MotionEvent e) {

            //長按事件,點選螢幕中,沒有松開和移動,ACTION_DOWN觸發

        }

        @Override

        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {

            //滑動事件,點選螢幕快速滑動松開,ACTION_DOWN觸發和多個ACTION_MOVE、一個ACTION_UP觸發

            return false;

        }

        @Override

        public boolean onDoubleTap(MotionEvent e) {

            //輕按兩下事件,快速點選了兩下螢幕

            return super.onDoubleTap(e);

        }

        @Override

        public boolean onDoubleTapEvent(MotionEvent e) {

            //輕按兩下事件中,ACTION_DOWN、ACTION_UP和ACTION_MOVE都會觸發這個回調

            return super.onDoubleTapEvent(e);

        }

        @Override

        public boolean onSingleTapConfirmed(MotionEvent e) {

            //嚴格的單擊事件,不可能是輕按兩下中的一次

            return super.onSingleTapConfirmed(e);

        }

        @Override

        public boolean onContextClick(MotionEvent e) {

            //發生上下文單擊時候都會觸發

            return super.onContextClick(e);

        }

    });

    return super.onTouchEvent(event);

}

解決長按螢幕無法拖動問題

gestureDetector.setIsLongpressEnabled(false);

我們可以看看GestureDetector 的源碼,裡面其實是分開了三個接口OnGestureListener,OnDoubleTapListener,OnContextClickListener

OnGestureListener

public interface OnGestureListener {

    boolean onDown(MotionEvent e);

    void onShowPress(MotionEvent e);

    boolean onSingleTapUp(MotionEvent e);

    boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);

    void onLongPress(MotionEvent e);

    boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);

}

OnDoubleTapListener

public interface OnDoubleTapListener {

    boolean onSingleTapConfirmed(MotionEvent e);

    boolean onDoubleTap(MotionEvent e);

    boolean onDoubleTapEvent(MotionEvent e);

}

OnContextClickListener

public interface OnContextClickListener {

    boolean onContextClick(MotionEvent e);

}

SimpleOnGestureListener這是實作了三個接口,它是一個内部類,這樣你可以選擇自己需要的部分進行重寫,系統在設計方面還是比較詳細的

public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener,

        OnContextClickListener {

    public boolean onSingleTapUp(MotionEvent e) {

        return false;

    }

    public void onLongPress(MotionEvent e) {

    }

    public boolean onScroll(MotionEvent e1, MotionEvent e2,

            float distanceX, float distanceY) {

        return false;

    }

    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,

            float velocityY) {

        return false;

    }

    public void onShowPress(MotionEvent e) {

    }

    public boolean onDown(MotionEvent e) {

        return false;

    }

    public boolean onDoubleTap(MotionEvent e) {

        return false;

    }

    public boolean onDoubleTapEvent(MotionEvent e) {

        return false;

    }

    public boolean onSingleTapConfirmed(MotionEvent e) {

        return false;

    }

    public boolean onContextClick(MotionEvent e) {

        return false;

    }

}

其實這些功能我們也可以自己在onTounchEvent中實作,可以看看源碼也是這樣做的

public boolean onTouchEvent(MotionEvent ev) {

    if (mInputEventConsistencyVerifier != null) {

        mInputEventConsistencyVerifier.onTouchEvent(ev, 0);

    }

    final int action = ev.getAction();

    if (mCurrentMotionEvent != null) {

        mCurrentMotionEvent.recycle();

    }

    mCurrentMotionEvent = MotionEvent.obtain(ev);

    if (mVelocityTracker == null) {

        mVelocityTracker = VelocityTracker.obtain();

    }

    mVelocityTracker.addMovement(ev);

    final boolean pointerUp =

            (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP;

    final int skipIndex = pointerUp ? ev.getActionIndex() : -1;

    final boolean isGeneratedGesture =

            (ev.getFlags() & MotionEvent.FLAG_IS_GENERATED_GESTURE) != 0;

    // Determine focal point

    float sumX = 0, sumY = 0;

    final int count = ev.getPointerCount();

    for (int i = 0; i < count; i++) {

        if (skipIndex == i) continue;

        sumX += ev.getX(i);

        sumY += ev.getY(i);

    }

    final int div = pointerUp ? count - 1 : count;

    final float focusX = sumX / div;

    final float focusY = sumY / div;

    boolean handled = false;

    switch (action & MotionEvent.ACTION_MASK) {

        case MotionEvent.ACTION_POINTER_DOWN:

            mDownFocusX = mLastFocusX = focusX;

            mDownFocusY = mLastFocusY = focusY;

            // Cancel long press and taps

            cancelTaps();

            break;

        case MotionEvent.ACTION_POINTER_UP:

            mDownFocusX = mLastFocusX = focusX;

            mDownFocusY = mLastFocusY = focusY;

            // Check the dot product of current velocities.

            // If the pointer that left was opposing another velocity vector, clear.

            mVelocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);

            final int upIndex = ev.getActionIndex();

            final int id1 = ev.getPointerId(upIndex);

            final float x1 = mVelocityTracker.getXVelocity(id1);

            final float y1 = mVelocityTracker.getYVelocity(id1);

            for (int i = 0; i < count; i++) {

                if (i == upIndex) continue;

                final int id2 = ev.getPointerId(i);

                final float x = x1 * mVelocityTracker.getXVelocity(id2);

                final float y = y1 * mVelocityTracker.getYVelocity(id2);

                final float dot = x + y;

                if (dot < 0) {

                    mVelocityTracker.clear();

                    break;

                }

            }

            break;

        case MotionEvent.ACTION_DOWN:

            if (mDoubleTapListener != null) {

                boolean hadTapMessage = mHandler.hasMessages(TAP);

                if (hadTapMessage) mHandler.removeMessages(TAP);

                if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null)

                        && hadTapMessage

                        && isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) {

                    // This is a second tap

                    mIsDoubleTapping = true;

                    recordGestureClassification(

                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP);

                    // Give a callback with the first tap of the double-tap

                    handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);

                    // Give a callback with down event of the double-tap

                    handled |= mDoubleTapListener.onDoubleTapEvent(ev);

                } else {

                    // This is a first tap

                    mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);

                }

            }

            mDownFocusX = mLastFocusX = focusX;

            mDownFocusY = mLastFocusY = focusY;

            if (mCurrentDownEvent != null) {

                mCurrentDownEvent.recycle();

            }

            mCurrentDownEvent = MotionEvent.obtain(ev);

            mAlwaysInTapRegion = true;

            mAlwaysInBiggerTapRegion = true;

            mStillDown = true;

            mInLongPress = false;

            mDeferConfirmSingleTap = false;

            mHasRecordedClassification = false;

            if (mIsLongpressEnabled) {

                mHandler.removeMessages(LONG_PRESS);

                mHandler.sendMessageAtTime(

                        mHandler.obtainMessage(

                                LONG_PRESS,

                                TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS,

                                0 ),

                        mCurrentDownEvent.getDownTime()

                                + ViewConfiguration.getLongPressTimeout());

            }

            mHandler.sendEmptyMessageAtTime(SHOW_PRESS,

                    mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);

            handled |= mListener.onDown(ev);

            break;

        case MotionEvent.ACTION_MOVE:

            if (mInLongPress || mInContextClick) {

                break;

            }

            final int motionClassification = ev.getClassification();

            final boolean hasPendingLongPress = mHandler.hasMessages(LONG_PRESS);

            final float scrollX = mLastFocusX - focusX;

            final float scrollY = mLastFocusY - focusY;

            if (mIsDoubleTapping) {

                // Give the move events of the double-tap

                recordGestureClassification(

                        TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP);

                handled |= mDoubleTapListener.onDoubleTapEvent(ev);

            } else if (mAlwaysInTapRegion) {

                final int deltaX = (int) (focusX - mDownFocusX);

                final int deltaY = (int) (focusY - mDownFocusY);

                int distance = (deltaX * deltaX) + (deltaY * deltaY);

                int slopSquare = isGeneratedGesture ? 0 : mTouchSlopSquare;

                final boolean ambiguousGesture =

                        motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;

                final boolean shouldInhibitDefaultAction =

                        hasPendingLongPress && ambiguousGesture;

                if (shouldInhibitDefaultAction) {

                    // Inhibit default long press

                    if (distance > slopSquare) {

                        // The default action here is to remove long press. But if the touch

                        // slop below gets increased, and we never exceed the modified touch

                        // slop while still receiving AMBIGUOUS_GESTURE, we risk that *nothing*

                        // will happen in response to user input. To prevent this,

                        // reschedule long press with a modified timeout.

                        mHandler.removeMessages(LONG_PRESS);

                        final long longPressTimeout = ViewConfiguration.getLongPressTimeout();

                        mHandler.sendMessageAtTime(

                                mHandler.obtainMessage(

                                        LONG_PRESS,

                                        TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS,

                                        0 ),

                                ev.getDownTime()

                                    + (long) (longPressTimeout * mAmbiguousGestureMultiplier));

                    }

                    // Inhibit default scroll. If a gesture is ambiguous, we prevent scroll

                    // until the gesture is resolved.

                    // However, for safety, simply increase the touch slop in case the

                    // classification is erroneous. Since the value is squared, multiply twice.

                    slopSquare *= mAmbiguousGestureMultiplier * mAmbiguousGestureMultiplier;

                }

                if (distance > slopSquare) {

                    recordGestureClassification(

                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL);

                    handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);

                    mLastFocusX = focusX;

                    mLastFocusY = focusY;

                    mAlwaysInTapRegion = false;

                    mHandler.removeMessages(TAP);

                    mHandler.removeMessages(SHOW_PRESS);

                    mHandler.removeMessages(LONG_PRESS);

                }

                int doubleTapSlopSquare = isGeneratedGesture ? 0 : mDoubleTapTouchSlopSquare;

                if (distance > doubleTapSlopSquare) {

                    mAlwaysInBiggerTapRegion = false;

                }

            } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {

                recordGestureClassification(TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL);

                handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);

                mLastFocusX = focusX;

                mLastFocusY = focusY;

            }

            final boolean deepPress =

                    motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;

            if (deepPress && hasPendingLongPress) {

                mHandler.removeMessages(LONG_PRESS);

                mHandler.sendMessage(

                        mHandler.obtainMessage(

                              LONG_PRESS,

                              TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS,

                              0 ));

            }

            break;

        case MotionEvent.ACTION_UP:

            mStillDown = false;

            MotionEvent currentUpEvent = MotionEvent.obtain(ev);

            if (mIsDoubleTapping) {

                // Finally, give the up event of the double-tap

                recordGestureClassification(

                        TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP);

                handled |= mDoubleTapListener.onDoubleTapEvent(ev);

            } else if (mInLongPress) {

                mHandler.removeMessages(TAP);

                mInLongPress = false;

            } else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) {

                recordGestureClassification(

                        TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP);

                handled = mListener.onSingleTapUp(ev);

                if (mDeferConfirmSingleTap && mDoubleTapListener != null) {

                    mDoubleTapListener.onSingleTapConfirmed(ev);

                }

            } else if (!mIgnoreNextUpEvent) {

                // A fling must travel the minimum tap distance

                final VelocityTracker velocityTracker = mVelocityTracker;

                final int pointerId = ev.getPointerId(0);

                velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);

                final float velocityY = velocityTracker.getYVelocity(pointerId);

                final float velocityX = velocityTracker.getXVelocity(pointerId);

                if ((Math.abs(velocityY) > mMinimumFlingVelocity)

                        || (Math.abs(velocityX) > mMinimumFlingVelocity)) {

                    handled = mListener.onFling(mCurrentDownEvent, ev, velocityX, velocityY);

                }

            }

            if (mPreviousUpEvent != null) {

                mPreviousUpEvent.recycle();

            }

            // Hold the event we obtained above - listeners may have changed the original.

            mPreviousUpEvent = currentUpEvent;

            if (mVelocityTracker != null) {

                // This may have been cleared when we called out to the

                // application above.

                mVelocityTracker.recycle();

                mVelocityTracker = null;

            }

            mIsDoubleTapping = false;

            mDeferConfirmSingleTap = false;

            mIgnoreNextUpEvent = false;

            mHandler.removeMessages(SHOW_PRESS);

            mHandler.removeMessages(LONG_PRESS);

            break;

        case MotionEvent.ACTION_CANCEL:

            cancel();

            break;

    }

    if (!handled && mInputEventConsistencyVerifier != null) {

        mInputEventConsistencyVerifier.onUnhandledEvent(ev, 0);

    }

    return handled;

}