GestureDetector与ScaleGestureDetector入门

来源:互联网 发布:阿里云服务器中文乱码 编辑:程序博客网 时间:2024/05/17 04:22

GestureDetector

概述

          该类主要是用于识别一些特定的手势,我们只需要调用GestureDetector.onTouchEvent(),并把MotionEvent传递进去即可。对于各种手势的回调,可以通过GestureDetector中的接口OnGestureListener来完成。

        只需要在View#onTouchEvent()中调用GestureDetector#onTouchEvent()即可。

OnGestureListener

onDown

/**         * Notified when a tap occurs with the down {@link MotionEvent}         * that triggered it. This will be triggered immediately for         * every down event. All other events should be preceded by this.         *         * @param e The down motion event.         */        boolean onDown(MotionEvent e);

        在GestureDetector.onTouchEvent()中,当action_down发生时,该方法肯定会立即被调用。源码为:

    case MotionEvent.ACTION_DOWN:        ……        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;
        从中可以看出action_down事件发生时,一定会触发onDown()事件。而隔了TAG_TIMEOUT后才会触发onShowPress();隔TAP_TIMEOUT+LONGPRESS_TIMEOUT后才会触发onLongPress()。

        GestureDetector.onTouchEvent()的返回值就是最后一句中的handled,而在每一次调用该方法时handled都会被初始化成false。因此,该方法最终会影响onTouchEvent()的返回值,如果onDown()返回true,那么onTouchEvent()一定会返回true;反之不一定返回false(因为省略的部分对handled进行了修改)。

onShowPress 

/**         * The user has performed a down {@link MotionEvent} and not performed         * a move or up yet. This event is commonly used to provide visual         * feedback to the user to let them know that their action has been         * recognized i.e. highlight an element.         *         * @param e The down motion event         */        void onShowPress(MotionEvent e);
        在action_down时,会通过handler发送一个消息(SHOW_PRESS),代码为:
mHandler.sendEmptyMessageAtTime(SHOW_PRESS, mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);
        从这行代码可以看出,在down事件发生时,并不会立即执行onShowPress(),会等待一段时间后才执行。并且如果在该时间段内,发生了action_move,action_up或者ACTION_CANCEL,还会取消执行该方法。因此,该方法就是在用户真正进行按下事件时才执行,并不是ACTION_DOWN时。因为ACTION_DOWN后,用户可能进行move也可能进行press。

        onDown()会在action_down事件时就执行,无论该次触摸会发展成press还是scroll。而onShowPress只有在事件是press(按下足够长的时间,并且没有大范围移动)时才会执行,在该方法中一般用来提示用户你已按下,而不能用来单击事件的操作,因为有可能会发现成onLongPress

onLongPress

        与onShowPress()类似,只不过按的时候长时执行onLongPress,时间短时执行onShowPress()。从onDown中的源码可以看出,当mIsLongpressEnabled=true时,才有可能执行onLongPress()。

        在new GestureDetector时,该值默认设置为true。可以通过setIsLongpressEnabled()进行设置。

onSingleTapUp

/**         * Notified when a tap occurs with the up {@link MotionEvent}         * that triggered it.         *         * @param e The up motion event that completed the first tap         * @return true if the event is consumed, else false         */        boolean onSingleTapUp(MotionEvent e);
      在GestureDetector.onTouchEvent()中,只有在action_up时,才会有可能执行该方法。但是它并不等价于action_up事件。源码如下:
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);

        从中可以看出,只有当mIsDoubleTapping=false,mInLongPress=false且mAlwaysInTapRegion=true时才会执行。

        在源码中可以看到,当移动的距离过大(移动距离超出以ACTION-DOWN时为圆心,以ViewConfiguration.get(context).getScaledTouchSlop();为半径的圆时,移动距离就过大)时mAlwaysInTapRegion = false,或者当点击的时间太长时mInLongPress = true,当为双击事件时,mIsDoubleTapping为true,此时都不会执行onSingleTapUp()。因此,该方法在短单击事件抬起时执行,而且它的MotionEvent中只有Action_UP事件

        注:ViewConfiguration.get(context).getScaledTouchSlop()的返回值经常用来判断是移动还是点击。用户按下后,肯定会不停的抖动,抖动的过程中会不断地触发ACTION_MOVE。如果直接将两次ev.getX(),ev.getY()不一样就认为是滑动的话,显然是不合理的。合理的做法是:当两次ev.getX(),ev.getY()的距离够大才认为是滑动,而ViewConfiguration.get(context).getScaledTouchSlop()就是对这个距离的判断标准。

onScroll

 /**         * Notified when a scroll occurs with the initial on down {@link MotionEvent} and the         * current move {@link MotionEvent}. The distance in x and y is also supplied for         * convenience.         *         * @param e1 The first down motion event that started the scrolling.         * @param e2 The move motion event that triggered the current onScroll.         * @param distanceX The distance along the X axis that has been scrolled since the last         *              call to onScroll. This is NOT the distance between {@code e1}         *              and {@code e2}.         * @param distanceY The distance along the Y axis that has been scrolled since the last         *              call to onScroll. This is NOT the distance between {@code e1}         *              and {@code e2}.         * @return true if the event is consumed, else false         */        boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
      当有滚动时,执行该方法。对该方法的调用都出现在action_move中,代码为:
else if (mAlwaysInTapRegion) {                final int deltaX = (int) (x - mCurrentDownEvent.getX());                final int deltaY = (int) (y - mCurrentDownEvent.getY());                int distance = (deltaX * deltaX) + (deltaY * deltaY);                if (distance > mTouchSlopSquare) {//移动距离过大                    handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);                    mLastMotionX = x;                    mLastMotionY = y;                    mAlwaysInTapRegion = false;                    mHandler.removeMessages(TAP);                    mHandler.removeMessages(SHOW_PRESS);//移除对onShowPress的回调                    mHandler.removeMessages(LONG_PRESS);//移除longpress                }                if (distance > mBiggerTouchSlopSquare) {                    mAlwaysInBiggerTapRegion = false;                }            } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {                handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);                mLastMotionX = x;                mLastMotionY = y;            }
       对于mAlwaysInTapRegion,当action_down时,它会被设置为true。以后只要移动距离不过大,mAlwaysInTapRegion就会一直为true。这也是一般处理点击和滑动的方式:只要移动范围过大了,就不会再认为是点击。只要移动范围不过大,都认为是点击中,不论调用几次action_move

onFling

        手指离开屏幕,且滑动速度足够快时调用且仅调用一次该方法。注意:这个方法并不是当屏幕滚动时调用的。

final VelocityTracker velocityTracker = mVelocityTracker;                velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);                final float velocityY = velocityTracker.getYVelocity();                final float velocityX = velocityTracker.getXVelocity();                if ((Math.abs(velocityY) > mMinimumFlingVelocity)                        || (Math.abs(velocityX) > mMinimumFlingVelocity)){                    handled = mListener.onFling(mCurrentDownEvent, ev, velocityX, velocityY);                }
        从中可以看到只有当x轴速度或y轴速度大于相应的值时,才会调用onFling()。

OnDoubleTapListener

          它是GestureDetector中的另一个接口,主要用来处理双击事件。可以通过调用setOnDoubleTapListener()进行设置,也可以在new GestureDetector()时传入SimpleOnGestureListener。

onSingleTapConfirmed

/**         * 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         */        boolean onSingleTapConfirmed(MotionEvent e);
          当确定该事件为短单击事件时执行该方法。它与OnGestureListener.onSingleTapUp类似,但是又有不同。onSingleTapUp当一次短点击事件执行到action_up时就会执行;而onSingleTapConfirmed却是在确定该次短点击事件是一次单击事件时才会执行,如果是双击事件就不会执行。

        当我们双击时,只会执行onSingleTapUp,不会执行onSingleTapConfirmed;当我们单击时,两者都会执行。

onDoubleTap

        /**         * Notified when a double-tap occurs.         *         * @param e The down motion event of the first tap of the double-tap.         * @return true if the event is consumed, else false         */        boolean onDoubleTap(MotionEvent e);
       双击时,第二次touch事件的down发生时就会执行。因此该MotionEvent中只有action_down。因为该方法只有在action_down中被调用。

onDoubleTapEvent   

/**         * Notified when an event within a double-tap gesture occurs, including         * the down, move, and up events.         *         * @param e The motion event that occurred during the double-tap gesture.         * @return true if the event is consumed, else false         */        boolean onDoubleTapEvent(MotionEvent e);
        该方法与onDoubleTap类似,只不过它的参数中含有action_up,action_down,action_move事件。它的源码是:

action_down时:

// 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);
        从这里可以看出:先调用onDoubleTap再调用onDoubleTapEvent。

action_move时:

 if (mIsDoubleTapping) {                // Give the move events of the double-tap                handled |= mDoubleTapListener.onDoubleTapEvent(ev);            } else if (mAlwaysInTapRegion) {
       从中可以看出:只有当mIsDoubleTapping = true时,才会执行该方法。

       而mIsDoubleTapping = true的唯一一个地方就是上面action_down中的代码。而action_up也类似于action_move。

           因此,可以看出:onDoubleTapEvent的执行后于onDoubleTap,并且它的MotionEvent中的down,move与up都是在双击事件中的第二击中的。

总结

        单击:选择onSingleTapUp。

        长按:选择onLongPress。

        展示按下效果:选择onShowPress()——该方法中有show,主要就是用来展示效果的。不能在该方法中处理单击事件,因为有可能会发展成LongPress。

        拖动:选择onScroll。

        快速滑动:选择onFling。

        双击:选择onDoubleTap。这种选择也不好,但几个方法中最好的。

ScaleGestureDetector

概述

        用于处理缩放的工具类,用法与GestureDetector类似,都是通过onTouchEvent()关联相应的MotionEvent的。使用该类时,用户需要传入一个完整的连续不断地motion事件(包含ACTION_DOWN,ACTION_MOVE和ACTION_UP事件)。

OnScaleGestureListener

        ScaleGestureDetector中的回调接口。主要有三个方法。

onScale

        缩放时。返回值代表本次缩放事件是否已被处理。如果已被处理,那么detector就会重置缩放事件;如果未被处理,detector会继续进行计算,修改getScaleFactor()的返回值,直到被处理为止。因此,它常用在判断只有缩放值达到一定数值时才进行缩放。例如:

public boolean onScale(ScaleGestureDetector detector) {System.out.println(detector.getScaleFactor());if(detector.getScaleFactor()< 2){return false;}mMatrix.preScale(detector.getScaleFactor(), detector.getScaleFactor());setImageMatrix(mMatrix);return true;}
        当进行放大时它的输出结果为:

        从中可以看出getScaleFactor()返回的值由小变大,直到大于2后又从1开始变大。这是因为,当缩放因子(getScaleFactor()的返回值)小于2时,onScale()返回的是false,此时detector认为本次缩放尚未结束,所以再次计算缩放因子时仍旧以上次结束时为基准,这样就导致了手指外移时缩放参数越来越大;当达到2后,本次缩放结束,再次获得缩放因子时的基准就变成刚刚结束的位置了,因此,获取的缩放因子会猛然变小(因为手指外移,执行的是放大操作,所以缩放因子会大于1)。

onScaleBegin

        缩放开始。该detector是否处理后继的缩放事件。返回false时,不会执行onScale()。

onScaleEnd

        缩放结束时。

常用方法

        参考:http://www.haodaima.net/art/1742556

        onTouchEvent():关联MotionEvent。返回true代表该detector想继续接收后继的motion事件;否则反之。默认时该方法返回true。

        getScaleFactor():获取本次缩放事件的缩放因子(缩放事件以onScale()返回值为基准,一旦该方法返回true,代表本次事件结束,要开启下次事件)。它的返回值是指本次事件中的缩放值,并不是相对于最开始的值。如一张图片开始放大2倍,后来又放大1.1倍。那么第二次放大时,该方法返回的就是1.1,而不是总放大倍数:2*1.1。

        getCurrentSpan(): 返回手势过程中,组成该手势的两个触点的当前距离。返回值以像素为单位的触点距离。

        getCurrentSpanX(),getCurrentSpanY():跟getCurrentSpan()类似,只不过一个是返回的是x轴上的距离,一个是y轴上的距离。注意:返回值有可能为负数。这两个方法的返回值和getCurrentSpan()的返回值满足勾股定理。

        getFocusY(),getFocusX():返回组成该手势的两个触点的中点在组件上的y,x轴坐标,单位为像素。

        getPreviousSpan():返回缩放过程中,组成当前缩放手势的两个触点的前一次距离。假设有a,b,c三个手指,某一次a,b组成缩放手势,两者的距离是300;随后一直是b,c组成缩放手势,当c抬起时,b,c的距离时100。此时,ab会组成缩放手势,该值返回的就是300,而不是b,c的100。

        getPreviousSpanX(),getPreviousSpanY():同getPreviousSpan()类似。

        getEventTime():获取当前motion事件的时间。源码如下:

    public boolean onTouchEvent(MotionEvent event) {        if (mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onTouchEvent(event, 0);        }        mCurrTime = event.getEventTime();//该方法返回的就是mCurrTime的值
        getTimeDelta():返回上次缩放事件结束时到当前的时间间隔。见下图:


        开始是1586,下一次立刻变成了10。这是因为在1586时,onScale返回了true,detector认为本次缩放事件已结束,再获取deltatime时就重新开始计算,所以值陡然变减少变成10。

        isInProgress():如果缩放手势正处在进行中,返回true;否则返回false。







1 0
原创粉丝点击