GestureDetector分析

public class GestureDetector {
    public interface OnGestureListener {
        //单纯的手指按下时触发
        boolean onDown(MotionEvent e);

        //按下一段时间TAP_TIMEOUT后触发,用来给出提示,不影响之后单击的判定
        void onShowPress(MotionEvent e);

        //必须是点击过程的抬起事件,移动后再抬起就不会被触发,但后面也有可能会跟着双击事件
        boolean onSingleTapUp(MotionEvent e);

        //按下后滑动时触发
        boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);

        //按下一段比onShowPress更长的时间TAP_TIMEOUT+LONGPRESS_TIMEOUT后触发,取消之后单击的判定
        void onLongPress(MotionEvent e);

        //快速滑动,抬手后时触发
        boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
    }

    //确认了单击和双击时的回调
    public interface OnDoubleTapListener {
        /**
         * Notified when a single-tap occurs.
         * <p>
         * Unlike {@link OnGestureListener#onSingleTapUp(MotionEvent)}, this
         * will only be called after the detector is confident that the user's
         * first tap is not followed by a second tap leading to a double-tap
         * gesture.
         *
         * @param e The down motion event of the single-tap.
         * @return true if the event is consumed, else false
         */

        //单纯的单击,后面没有跟随双击,此处的e可能是down也可能是up事件
        boolean onSingleTapConfirmed(MotionEvent e);
 
        //在双击过程中,第二次点击时触发,但返回的是第一次点击时的数据
        boolean onDoubleTap(MotionEvent e);

        //在双击过程中,第二次点击时的按下、滑动和抬起事件
        boolean onDoubleTapEvent(MotionEvent e);
    }

    /**
     * The listener that is used to notify when a context click occurs. When listening for a
     * context click ensure that you call {@link #onGenericMotionEvent(MotionEvent)} in
     * {@link View#onGenericMotionEvent(MotionEvent)}.
     */
    public interface OnContextClickListener {
        /**
         * Notified when a context click occurs.
         *
         * @param e The motion event that occurred during the context click.
         * @return true if the event is consumed, else false
         */
        boolean onContextClick(MotionEvent e);
    }

    /**
     * A convenience class to extend when you only want to listen for a subset
     * of all the gestures. This implements all methods in the
     * {@link OnGestureListener}, {@link OnDoubleTapListener}, and {@link OnContextClickListener}
     * but does nothing and return {@code false} for all applicable methods.
     */
    public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener,
            OnContextClickListener {
                ......
    }

    private int mTouchSlopSquare;
    private int mDoubleTapTouchSlopSquare;
    private int mDoubleTapSlopSquare;
    private int mMinimumFlingVelocity;
    private int mMaximumFlingVelocity;

    //按下TAP_TIMEOUT时间内没有滑动,则触发SHOW_PRESS回调,它和判断是否单击无关
    private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout();

    //在DOUBLE_TAP_TIMEOUT内没有产生双击,且按下时间没有形成长按,则判定为单击
    private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
    private static final int DOUBLE_TAP_MIN_TIME = ViewConfiguration.getDoubleTapMinTime();

    //长按的时间为TAP_TIMEOUT+LONGPRESS_TIMEOUT
    private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();

    // constants for Message.what used by GestureHandler below
    private static final int SHOW_PRESS = 1;
    private static final int LONG_PRESS = 2;
    private static final int TAP = 3;   //用来判定单击

    private final Handler mHandler;
    private final OnGestureListener mListener;
    private OnDoubleTapListener mDoubleTapListener;
    private OnContextClickListener mContextClickListener;

    private boolean mStillDown;
    private boolean mDeferConfirmSingleTap;
    private boolean mInLongPress;
    private boolean mInContextClick;
    private boolean mAlwaysInTapRegion;
    private boolean mAlwaysInBiggerTapRegion;
    private boolean mIgnoreNextUpEvent;

    private MotionEvent mCurrentDownEvent;
    private MotionEvent mPreviousUpEvent;

    //判定为双击事件的标记,在第二次按下时设置
    private boolean mIsDoubleTapping;

    private float mLastFocusX;
    private float mLastFocusY;
    private float mDownFocusX;
    private float mDownFocusY;

    private boolean mIsLongpressEnabled;

    /**
     * Determines speed during touch scrolling
     */
    private VelocityTracker mVelocityTracker;

    /**
     * Consistency verifier for debugging purposes.
     */
    private final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
            InputEventConsistencyVerifier.isInstrumentationEnabled() ?
                    new InputEventConsistencyVerifier(this, 0) : null;

    //内部接收事件的handler
    private class GestureHandler extends Handler {
        GestureHandler() {
            super();
        }

        GestureHandler(Handler handler) {
            super(handler.getLooper());
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case SHOW_PRESS:    //短时间按住
                mListener.onShowPress(mCurrentDownEvent);
                break;
                
            case LONG_PRESS:    //长时间按住
                dispatchLongPress();
                break;
                
            case TAP:
                //按下后在一定时间内没有形成双击
                // If the user's finger is still down, do not count it as a tap
                if (mDoubleTapListener != null) {
                    if (!mStillDown) {  //按下后立刻抬起,此时在TAP延时后,才判定为单击事件,
                        mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
                    } else {    //还未抬起,但是也没有达到长按的事件判定,此时如果产生up事件,则认为是单击
                        mDeferConfirmSingleTap = true;
                    }
                }
                break;

            default:
                throw new RuntimeException("Unknown message " + msg); //never
            }
        }
    }

    //借用传入的handler的looper来构造自己的handler
    public GestureDetector(Context context, OnGestureListener listener, Handler handler) {
        if (handler != null) {
            mHandler = new GestureHandler(handler);
        } else {
            mHandler = new GestureHandler();
        }
        mListener = listener;

        //这地方怎么能这么写?三个类型的接口并不兼容啊
        if (listener instanceof OnDoubleTapListener) {
            setOnDoubleTapListener((OnDoubleTapListener) listener);
        }
        if (listener instanceof OnContextClickListener) {
            setContextClickListener((OnContextClickListener) listener);
        }
        init(context);
    }
    
    public GestureDetector(Context context, OnGestureListener listener, Handler handler,
            boolean unused) {
        this(context, listener, handler);
    }

    private void init(Context context) {
        if (mListener == null) {
            throw new NullPointerException("OnGestureListener must not be null");
        }
        mIsLongpressEnabled = true; //默认允许长按

        // Fallback to support pre-donuts releases
        int touchSlop, doubleTapSlop, doubleTapTouchSlop;
        if (context == null) {
            //noinspection deprecation
            touchSlop = ViewConfiguration.getTouchSlop();
            doubleTapTouchSlop = touchSlop; // Hack rather than adding a hiden method for this
            doubleTapSlop = ViewConfiguration.getDoubleTapSlop();
            //noinspection deprecation
            mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();
            mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity();
        } else {
            //这里应该是新版本的代码,
            final ViewConfiguration configuration = ViewConfiguration.get(context);
            touchSlop = configuration.getScaledTouchSlop(); //滑动判定的最小距离
            doubleTapTouchSlop = configuration.getScaledDoubleTapTouchSlop();
            doubleTapSlop = configuration.getScaledDoubleTapSlop();
            //快速滑动的最小和最大速度
            mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
            mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity();
        }
        mTouchSlopSquare = touchSlop * touchSlop;
        mDoubleTapTouchSlopSquare = doubleTapTouchSlop * doubleTapTouchSlop;
        mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop;
    }

    public void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener) {
        mDoubleTapListener = onDoubleTapListener;
    }

    public void setContextClickListener(OnContextClickListener onContextClickListener) {
        mContextClickListener = onContextClickListener;
    }

    public void setIsLongpressEnabled(boolean isLongpressEnabled) {
        mIsLongpressEnabled = isLongpressEnabled;
    }

    public boolean isLongpressEnabled() {
        return mIsLongpressEnabled;
    }

    public boolean onTouchEvent(MotionEvent ev) {
        final int action = ev.getAction();

        //计算滑动速度
        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;

        //求出多点的平均值,排除刚刚抬起的点
        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);
                //存在之前记录过按下和抬起的事件,还存在TAP的定时器,说明还没有被判定为单击,
                //而且第二次点击的时间还在判定双击的范围内
                if ((mCurrentDownEvent != null) && 
                    (mPreviousUpEvent != null) && 
                    hadTapMessage &&
                    isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) 
                {
                    //标记此后的滑动和抬起都是在一个双击事件中
                    mIsDoubleTapping = true;
                    // 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 {
                    //第一个按下事件,开启定时,以判断之后是否为单击或者双击
                    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;                  //是否仍然按下,down设为true,up设为false
            mInLongPress = false;               //是否为长按
            mDeferConfirmSingleTap = false;     //是否推迟确认是单击

            //如果允许长按,则延时一段时间后确认长按事件
            if (mIsLongpressEnabled) {  
                mHandler.removeMessages(LONG_PRESS);
                mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime()
                        + TAP_TIMEOUT + LONGPRESS_TIMEOUT);
            }
            //开启短时间按住的定时器
            mHandler.sendEmptyMessageAtTime(SHOW_PRESS, mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);
            //回调单纯的按下事件
            handled |= mListener.onDown(ev);
            break;

        case MotionEvent.ACTION_MOVE:
            //忽略长按过程中的移动事件
            if (mInLongPress || mInContextClick) {
                break;
            }
            //计算移动的距离
            final float scrollX = mLastFocusX - focusX;
            final float scrollY = mLastFocusY - focusY;

            if (mIsDoubleTapping) {     //如果是双击事件中的移动,调用onDoubleTapEvent
                handled |= mDoubleTapListener.onDoubleTapEvent(ev);
            } else if (mAlwaysInTapRegion) {    //不是双击事件,检查移动范围是否在Tap的限定范围内
                final int deltaX = (int) (focusX - mDownFocusX);
                final int deltaY = (int) (focusY - mDownFocusY);
                int distance = (deltaX * deltaX) + (deltaY * deltaY);
                if (distance > mTouchSlopSquare) {  //移动距离超出mTouchSlopSquare的限制,判定为非TAP
                    handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);  //回调onScroll事件
                    mLastFocusX = focusX;
                    mLastFocusY = focusY;
                    mAlwaysInTapRegion = false;     //设置为非TAP
                    mHandler.removeMessages(TAP);
                    mHandler.removeMessages(SHOW_PRESS);
                    mHandler.removeMessages(LONG_PRESS);
                }
                if (distance > mDoubleTapTouchSlopSquare) {     //如果移动范围超过了双击判定的允许范围
                    mAlwaysInBiggerTapRegion = false;
                }
            } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
                //移动范围超过mTouchSlop的限制后,位移大于1才回调onScroll,提高效率,降低回调频率
                handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
                mLastFocusX = focusX;
                mLastFocusY = focusY;
            }
            break;

        case MotionEvent.ACTION_UP:
            mStillDown = false;     //单纯标记了是否为按下状态
            //备份当前的抬起事件
            MotionEvent currentUpEvent = MotionEvent.obtain(ev);
            if (mIsDoubleTapping) {
                // Finally, give the up event of the double-tap
                //如果是双击事件中的抬起,则调用onDoubleTapEvent
                handled |= mDoubleTapListener.onDoubleTapEvent(ev);
            } else if (mInLongPress) {  //取消长按状态
                mHandler.removeMessages(TAP);   //达到长按状态,应该不可能还有TAP消息了吧
                mInLongPress = false;
            } else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) {
                //一直在单击允许的区域内,则判定为一次点击抬起事件,回调onSingleTapUp
                handled = mListener.onSingleTapUp(ev);
                //如果从按下到抬起的时间超过了双击所需要的时间,但是没有超过长按所需要的时间,
                //则确认为单击,并回调onSingleTapConfirmed
                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);

                //计算第一个点的滑动速度,在规定范围内则判定为fling,回调onFling
                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;
        }
        ......
        return handled;
    }

    /**
     * Analyzes the given generic motion event and if applicable triggers the
     * appropriate callbacks on the {@link OnGestureListener} supplied.
     *
     * @param ev The current motion event.
     * @return true if the {@link OnGestureListener} consumed the event,
     *              else false.
     */
    //作用不明,需要再研究
    //非Touch Event才会触发onGenericMotionEvent
    public boolean onGenericMotionEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onGenericMotionEvent(ev, 0);
        }

        final int actionButton = ev.getActionButton();
        switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_BUTTON_PRESS:
                if (mContextClickListener != null && !mInContextClick && !mInLongPress
                        && (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
                        || actionButton == MotionEvent.BUTTON_SECONDARY)) {
                    if (mContextClickListener.onContextClick(ev)) {
                        mInContextClick = true;
                        mHandler.removeMessages(LONG_PRESS);
                        mHandler.removeMessages(TAP);
                        return true;
                    }
                }
                break;

            case MotionEvent.ACTION_BUTTON_RELEASE:
                if (mInContextClick && (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY
                        || actionButton == MotionEvent.BUTTON_SECONDARY)) {
                    mInContextClick = false;
                    mIgnoreNextUpEvent = true;
                }
                break;
        }
        return false;
    }

    private void cancel() {
        mVelocityTracker.recycle();
        mVelocityTracker = null;
        mStillDown = false;

        //以下即为cancelTaps的逻辑
        mHandler.removeMessages(SHOW_PRESS);
        mHandler.removeMessages(LONG_PRESS);
        mHandler.removeMessages(TAP);

        mIsDoubleTapping = false;

        //down事件设置为true,move大于一定程度设置为false,up中进行判定,表示是否为单纯的点击事件
        mAlwaysInTapRegion = false;     


        mAlwaysInBiggerTapRegion = false;
        mDeferConfirmSingleTap = false;
        mInLongPress = false;
        mInContextClick = false;
        mIgnoreNextUpEvent = false;
    }

    private void cancelTaps() {
        mHandler.removeMessages(SHOW_PRESS);
        mHandler.removeMessages(LONG_PRESS);
        mHandler.removeMessages(TAP);

        mIsDoubleTapping = false;
        mAlwaysInTapRegion = false;
        mAlwaysInBiggerTapRegion = false;
        mDeferConfirmSingleTap = false;
        mInLongPress = false;
        mInContextClick = false;
        mIgnoreNextUpEvent = false;
    }

    //第二次点击时调用,太快的双击和太慢的双击,以及两次点击之间的距离过大,都认为不是双击
    private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp,
            MotionEvent secondDown) {
        //如果在第一次点击过程中,移动的范围过大,则直接跳出
        if (!mAlwaysInBiggerTapRegion) {
            return false;
        }

        final long deltaTime = secondDown.getEventTime() - firstUp.getEventTime();
        if (deltaTime > DOUBLE_TAP_TIMEOUT || deltaTime < DOUBLE_TAP_MIN_TIME) {
            return false;
        }

        int deltaX = (int) firstDown.getX() - (int) secondDown.getX();
        int deltaY = (int) firstDown.getY() - (int) secondDown.getY();
        return (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare);
    }

    private void dispatchLongPress() {
        mHandler.removeMessages(TAP);   //产生长按时,不可能有还有TAP消息存在吧?
        mDeferConfirmSingleTap = false; //取消单击的判定
        mInLongPress = true;
        mListener.onLongPress(mCurrentDownEvent);   //回调长按
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值