Android的GestureDetector类

最近在做一个小需求的时候,会用到手势的判断,在对应不同的事件。用户在触摸手机屏幕时,会产生很多不同的手势,比如点击、双击、长按、拖拽、抛。

OnTouchListener

刚开始我使用的方式是让类继承OnTouchListener内部接口,然后重写onTouch(View v,MotionEvent event)方法,然后处理一些touch事件,虽然看起来貌似解决了目前的问题,但是这种方式太简单了。如果要处理负责的手势,用这个接口去判断手势会非常麻烦。
我从触屏的时间和触屏的滑动长度来判别手势,这种自定义的方式增加了代码的复杂度而且不准确。不过,我相信GestureDetector类的包装类中应该封装了以上思想的实现,可能它的数据是建立在实验的基础上。
代码如下:

public boolean onTouch(View v, MotionEvent event) {

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                pressStartTime = System.currentTimeMillis();
                pressedX = event.getX();
                pressedY = event.getY();
                break;
            case MotionEvent.ACTION_UP:
                long pressDuration = System.currentTimeMillis() - pressStartTime;
                /**
                 * 判断touch是点击还是滑动,点击,触发以下事件
                 */
                if (pressDuration < MAX_CLICK_DURATION && distance(pressedX, pressedY, event.getX(), event.getY(), getActivity()) < MAX_CLICK_DISTANCE) {
                 ..........
                }
            default:
                break;
        }
        return false;
    }

GestureDetector类

OnGestureListener有下面的几个动作:

  • 按下(onDown): 刚刚手指接触到触摸屏的那一刹那,就是触的那一下。
  • 抛掷(onFling): 手指在触摸屏上迅速移动,并松开的动作。
  • 长按(onLongPress): 手指按在持续一段时间,并且没有松开。
  • 滚动(onScroll): 手指在触摸屏上滑动。
  • 按住(onShowPress): 手指按在触摸屏上,它的时间范围在按下起效,在长按之前。
  • 抬起(onSingleTapUp):手指离开触摸屏的那一刹那。

GestureDetector.OnGestureListener接口:用来通知普通的手势事件,该接口有如下六个回调函数:
1. onDown(MotionEvent e):按下事件;
2. onSingleTapUp(MotionEvent e):单击时,在按下后既没有滑动(onScroll),又没有长按(onLongPress),然后抬起时触发。
点击一下非常快的(不滑动)Touchup:onDown->onSingleTapUp->onSingleTapConfirmed
点击一下稍微慢点的(不滑动)Touchup:onDown->onShowPress->onSingleTapUp->onSingleTapConfirmed
3. onShowPress(MotionEvent e):down事件发生而move或up还没发生前,触发该事件;Touch了但还没有滑动时触发。
与onDown,onLongPress比较:onDown只要按下一定立刻触发。而按下后过一会没有滑动先触发onShowPress再是onLongPress。如,按下后一直不滑动,触发顺序onDown–>onShowPress–>onLongPress。
4. onLongPress(MotionEvent e):长按事件;Touch了不移动一直Touch down时触发。
5. onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY):滑动手势事件;Touch了滑动一点距离后,在抬起时才会触发。
参数:e1,第1个ACTION_DOWN MotionEvent 并且只有一个;e2,最后一个ACTION_MOVE MotionEvent ;velocityX,X轴上的移动速度(像素/秒);velocityY,Y轴上的移动速度(像素/秒)。
触发条件:X轴的坐标位移大于FLING_MIN_DISTANCE,且移动速度大于FLING_MIN_VELOCITY个像素/秒时。
6. onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY):在屏幕上拖动事件。在ACTION_MOVE动作发生时触发,会多次触发。onDown–>onScroll–>onScroll–>onFiling
SimpleOnGestureListener类是GestureDetector提供给我们的一个更方便的响应不同手势的类,这个类实现了上述两个接口(但是所有的方法体都是空的),该类是static class,也就是说它实际上是一个外部类。可以在外部继承这个类,重写里面的手势处理方法。——以上部分摘抄自链接中的博客

读源码

在GestureDetector类中果然也有个onTouchEvent方法,我们来读一下源码,看看别人是怎么实现的。
看源码之前,我们再了解一些概念:

  • ACTION_MASK在Android中应用于多点触摸操作。字面的意思是动作掩码
  • ACTION_DOWN和ACTION_UP是单点触摸屏幕,按下去和放开的操作;
  • ACTION_POINTER_DOWN和ACTION_POINTER_UP是多点触摸屏幕,当有一只手指按下去的时候,另一只手指按下和放开的动作捕获。
  • ACTION_MOVE是手指在屏幕上移动的操作。

使用event.getAction()&MotionEvent.ACTION_MASK就可以处理多点触摸的ACTION_POINTER_DOWN和ACTION_POINTER_UP事件。
感觉明白了那么一点点,我们再做一个实验。

intACTION_MASK=0xff;
int ACTION_DOWN= 0;
int ACTION_UP= 1;
int ACTION_MOVE= 2;
int ACTION_POINTER_DOWN= 5;
int ACTION_POINTER_UP= 6;
主要是来搞清楚ACTION_POINTER_DOWN和ACTION_POINTER_UP的具体动作。
我们定义一个Activity,在里面重写一下OnTouchEvent方法,然后触发不同的触摸事件,输出相应的结果。

@Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        int result=action & MotionEvent.ACTION_MASK;
        switch (result) {
            case MotionEvent.ACTION_DOWN:
                showMsg("ACTION_DOWN"+"------" + action+"------" + result);
                break;
            case MotionEvent.ACTION_UP:
                showMsg("ACTION_UP" + "------" + action+"------" + result);
                break;
            case MotionEvent.ACTION_POINTER_UP:
                showMsg("ACTION_POINTER_UP"+ "------" + action+"------" + result);
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                showMsg("ACTION_POINTER_DOWN"+ "------" + action+"------" + result);
                break;
        }
        return super.onTouchEvent(event);
    }

    private void showMsg(String str)
    {
       Log.d("Action",str);
    }

运行结果如下所示
这里写图片描述
屏幕上触发的事件是:
单指按下—》单指拿起
单指按下—》第二根手指按下—》第二根手指拿起—》第一根手指拿起
单指按下—》第二根手指按下—》第三根手指按下—》第三根手指拿起—》第二根手指拿起—》第一根手指拿起
双指按下—》双指拿起
综上所述:只有但多个(大于1)手指放下拿起才会触发ACTION_POINTER_DOWN和ACTION_POINTER_UP事件
velocityTracker简介参考链接:http://blog.csdn.net/hudashi/article/details/7352157
再看源码:

 public boolean onTouchEvent(MotionEvent ev) {//传入的是当前的手势事件
        if (mInputEventConsistencyVerifier != null) { //用于调试目的的一致性校验,这个我们不用管
            mInputEventConsistencyVerifier.onTouchEvent(ev, 0);
        }
        final int action = ev.getAction();   //获得手势事件

        if (mVelocityTracker == null) {//用于跟踪触摸事件的速度
            mVelocityTracker = VelocityTracker.obtain();
        }
 //判断是否是多点触摸,多于1个触点中是否有手指拿起的                    //ACTION_POINTER_UP的值为6,我们可以看到上图gif中哪些动作的result是6
        mVelocityTracker.addMovement(ev);
        final boolean pointerUp =
                (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP;
                //表示触点的Index,第一个手指按下是0,第二个按下是1,即屏幕上剩余触点
        final int skipIndex = pointerUp ? ev.getActionIndex() : -1;

        // Determine focal point
        float sumX = 0, sumY = 0;
        触摸点的个数 
        final int count = ev.getPointerCount();
        for (int i = 0; i < count; i++) {
            if (skipIndex == i) continue;//单点就跳过,多点就计算触摸点x,y的和
            sumX += ev.getX(i);
            sumY += ev.getY(i);
        }
        final int div = pointerUp ? count - 1 : count;
        final float focusX = sumX / div;//计算多个触点x,y的平均值,即为焦点值
        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;
                    // 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;

            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) {
                break;
            }
            final float scrollX = mLastFocusX - focusX;
            final float scrollY = mLastFocusY - focusY;
            if (mIsDoubleTapping) {
                // Give the move events of the 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);
                if (distance > mTouchSlopSquare) {
                    handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
                    mLastFocusX = focusX;
                    mLastFocusY = focusY;
                    mAlwaysInTapRegion = false;
                    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)) {
                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
                handled |= mDoubleTapListener.onDoubleTapEvent(ev);
            } else if (mInLongPress) {
                mHandler.removeMessages(TAP);
                mInLongPress = false;
            } else if (mAlwaysInTapRegion) {
                handled = mListener.onSingleTapUp(ev);
                if (mDeferConfirmSingleTap && mDoubleTapListener != null) {
                    mDoubleTapListener.onSingleTapConfirmed(ev);
                }
            } else {

                // 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;
            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;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值