现在的位置: 首页 > 综合 > 正文

手势检测GestureDetector的实现原理

2017年12月17日 ⁄ 综合 ⁄ 共 8793字 ⁄ 字号 评论关闭
文章目录

    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);
        }
        init(context);
    }

首先简单说明一下触摸屏事件的实现过程:触摸屏驱动检测到MotionEvent——>MotionEvent传递到Activity进程(这里可能还设计到Wms对事件的分发,然后才到Activity)——>Activity.dispatchTouchEvent(MotionEvent)(此处略去Fragment不谈)

Activity.dispatchTouchEvent()

    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;//如果该事件已经在Wms中被消化掉了,那么将不再往下分发
        }
        return onTouchEvent(ev);
    }

    public boolean onTouchEvent(MotionEvent event) {
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }
        
        return false;
    }

dispatchTouchEvent拦截事件,交给onTouchEvent消化处理,我们如果想在Activity的继承类中消化掉事件,只需要复写onTouchEvent()方法,并且return true即可。

——>若在上一步中,Activity并没有拦截MotionEvent,那么事件将交给View处理(此处先不讨论View和ViewGroup之间的事件分发)。这也就是为什么在View中定义了那么多可能的事件监听器监听处理各种可能的MotionEvent和组合的MotionEvent,因为最终事件往往是交给View进行处理。

再来说说MotionEvent这个类,查看这个类的源码发现它里面有许多native方法,用户在屏幕上的各种操作,都由驱动检测到,然后通过本地程序传递到MotionEvent。通过MotionEvent提供的各种变量和方法,我们可以得到用户操作的各种参数,包括在屏幕上的位置,有几个手指操作等。View里的各个监听器则定义了对一些简单操作比如单击事件(onClickListener)的处理。当然我们也可以自定义一些复杂事件,这需要组合多个MotionEvent,即同时检测到MotionEvent里定义的多个操作。而GestureDetector就是这么做的。

在MotionEvent中定义了一些常用事件:

主要的事件类型有:

 ACTION_DOWN: 表示用户开始触摸.

 ACTION_MOVE: 表示用户在移动(手指或者其他)

 ACTION_UP:表示用户抬起了手指 

ACTION_CANCEL:表示手势被取消了,一些关于这个事件类型的讨论见:http://stackoverflow.com/questions/11960861/what-causes-a-motionevent-action-cancel-in-android

还有一个不常见的:

ACTION_OUTSIDE: 表示用户触碰超出了正常的UI边界.

但是对于多点触控的支持,Android加入了以下一些事件类型.来处理,如另外有手指按下了,

有的手指抬起来了.等等:

ACTION_POINTER_DOWN:有一个非主要的手指按下了.

ACTION_POINTER_UP:一个非主要的手指抬起来了

 (2)事件发生的位置,x,y轴

   getX() 获得事件发生时,触摸的中间区域在屏幕的X轴.

   getY() 获得事件发生时,触摸的中间区域在屏幕的X轴.

 在多点触控中还可以通过:    

 getX(int pointerIndex) ,来获得对应手指事件的发生位置. 获得Y轴用getY(int pointerIndex)

 (3)其他属性

  getEdgeFlags():当事件类型是ActionDown时可以通过此方法获得,手指触控开始的边界. 如果是的话,有如下几种值:EDGE_LEFT,EDGE_TOP,EDGE_RIGHT,EDGE_BOTTOM

而这些事件类型的定义在本地代码中实现,通过getAction()我们可以获取到用户发生了哪些事件类型。

    public final int getAction() {
        return nativeGetAction(mNativePtr);
    }

查看GestureDetector.java源码:

    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);
        }
        init(context);
    }

GestureDetector是与Context相关的,即它可以在Activity的onTouchEvent中调用处理,因为View也是Context相关的,因此它也可以在View的onTouchEvent中处理。同理,它也使用了一个监听器来监听GestureDetector中拓展的一些事件。这里额外说一点,Java中的监听接口是一种回调机制,它实现了控制反转的功能。下面我们结合onTouchEvent中对事件的处理具体说说控制反转功能如何实现了我们想要的某种手势事件。

GestureDetector.onTouchEvent():

public boolean onTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 0);
        }

        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;

        // 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;
                    // Give a callback with the first tap of the double-tap
                    handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);//调用mDoubleTapListener.onDoubleTap()处理“双击的第一次单击事件”:首先我们谈谈为什么这里有了“双击的第一次单击事件”,这一切都由if条件判断决定,当第一个ACTION_DOWN事件传来时,不会走这个条件语句,因为
mCurrentDownEvent和mPreviousUpEvent的值还为空,从这两个变量事件赋值的地方来看,当他俩的值不为空的时候,已经经历了ACTION_DOWN和ACTION_UP两次事件。因此此时是满足“第一次单击事件”的,而再通过
hadTapMessage && isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)这个判断满足“双击事件”,因此就符合了"双击的第一次单击事件"这个条件。此时有个疑问,这个操作与View中定义的onClick单击事件处理起来会不会有冲突呢?这我们查看
isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)可以发现他是有一个时间上的判断的,即双击的间隔不大于多少就是“双击的第一次单击事件”。另外我们讨论“控制反转”这个功能是怎么通过回调监听实现的,首先,在GestureDetector框架里,有两个监听接口————onGestureListener和onDoubleTapListener,在框架里需要实现事件操作,比如说本处的“双击的第一次单击事件”操作时调用上述接口里的回调函数,在框架里,该函数里是没有实现任何操作的,但是程序员在调用这个框架的时候,需要实现这些接口,此时这里的
mDoubleTapListener是我们自己实现该接口后的对象,并且对这些接口函数进行了复写,有了实际的内容。即在框架中只定义接口和回调函数,调用的也是该接口对象,而真正赋值过来的却是该接口的实现类,回调函数也有了内容。






// 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);//mCurrentDownEvent第一次赋值的地方 mAlwaysInTapRegion = true; mAlwaysInBiggerTapRegion = true; mStillDown = true; mInLongPress = 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);//同理我们也可以分析出“单指弹起事件” } 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;//mPreviousUpEvent第一次赋值的地方
if (mVelocityTracker != null) { // This may have been cleared when we called out to the // application above. mVelocityTracker.recycle(); mVelocityTracker = null; } mIsDoubleTapping = 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; }


总的来说,MotionEvent中定义了最基本的屏幕事件,而Activity,View和GestureDetector在onTouchEvent中消化处理这些事件,并且通过组合这些基本事件,可以得到一些复杂事件,通过监听这些复杂事件,可以实现一些复杂操作。



抱歉!评论已关闭.