美文网首页源码解析
安卓源码GestureDetector类解读

安卓源码GestureDetector类解读

作者: 差得很远呢 | 来源:发表于2016-06-27 22:22 被阅读402次

    前言

    安卓源码给我们提供了一个GestureDetector类,来监听手势,点击,长按,双击,滚动,抛等。本文通过解读Gesture类来看看安卓源码是怎样判断各类手势的?搞清楚了这个,我们就可以自定义手势啦,比如一张图片,点击返回,长按保存,单个手指双击 图片放大到2倍或者由放大状态变回原来的大小,两个手指捏 缩小图片,两个手指张开 放大图片等等。

    下面我们就来看GestureDetector类的具体实现。涉及到fling抛的代码会放在后面讲,本文会略过。

    1. GestureDetector类中的接口

    GestureDetector类提供了两个接口OnGestureListenerOnDoubleTapListenerOnGestureListener用来监听单个手指事件,OnDoubleTapListener用来监听两个手指事件。具体方法如下:

    接口OnGestureListener

    public interface OnGestureListener {
            //手指按下就会触发(调用onTouch()方法的ACTION_DOWN事件时触发)
            boolean onDown(MotionEvent e);
            //一次点击up事件;在touch down后又没有滑动(onScroll),又没有长按(onLongPress),然后Touchup时触发
            boolean onSingleTapUp(MotionEvent e);
            /*
            down事件发生而move或则up还没发生前触发该
    事件;Touch了还没有滑动时触发(与onDown,onLongPress)比较onDown只要Touch down一定立刻触发。而Touchdown后过一会没有滑动先触发onShowPress再是onLongPress。所以Touchdown后一直不滑动
            onLongPress之前触发
            */
            void onShowPress(MotionEvent e);
            //长按,触摸屏按下后既不抬起也不移动,过一段时间后触发
            void onLongPress(MotionEvent e);
            //滚动,触摸屏按下后移动(执行onTouch()方法的ACTION_MOVE事件时会触发)
            boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
            //滑动,触摸屏按下后快速移动并抬起,会先触发滚动手势,跟着触发一个滑动手势
            //在ACTION_UP时才会触发
            /*参数:
                e1:第1个ACTION_DOWN MotionEvent 并且只有一个;
                e2:最后一个ACTION_MOVE MotionEvent ;
                velocityX:X轴上的移动速度,像素/秒 ;
                velocityY:Y轴上的移动速度,像素/秒.
              触发条件:X轴的坐标位移大于FLING_MIN_DISTANCE,且移动速度大于FLING_MIN_VELOCITY个像素/秒
             */       
            boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
        }
    

    接口OnDoubleTapListener

    public interface OnDoubleTapListener {
            /* 
            1. 单击确认,用来判定该次点击是SingleTap而不是DoubleTap,如果连续点击两次就是DoubleTap手势,如果只点击一次,系统等待一段时间后没有收到第二次点击则判定该次点击为SingleTap而不是DoubleTap,然后触发SingleTapConfirmed事件。
            2. 不同于onSingleTapUp,他是在GestureDetector确信用户在第一次触摸屏幕后,没有紧跟着第二次触摸屏幕,也就是不是“双击”的时候触发
            */
            boolean onSingleTapConfirmed(MotionEvent e);
            //在双击的第二下,Touch down时触发 。
            boolean onDoubleTap(MotionEvent e);
            //双击的第二下Touch down和up都会触发,可用e.getAction()区分。
            boolean onDoubleTapEvent(MotionEvent e);
        }
    

    各种手势调用方法如下:

    • 快速点击(没有滑动):onDown() -> onSingleTapUp() -> onSingleTapConfirmed()
    • 手指一直按下,即长按(没有滑动,没有抬起):onDown() -> onShowPress() -> onLongPress()
    • 滚动:onDown() -> onScroll() -> onScroll() ... ...
    • 抛:onDown() -> onScroll() -> ... ... -> onFling()

    (onTouch()::ACTION_DOWN) -> onDown() -> (onTouch()::ACTION_MOVE) -> onScroll() -> ... ...(onTouch()::ACTION_MOVE) ->onScroll() -> (onTouch()::ACTION_UP) -> onFling()

    2. GestureDetector类中的内部类SimpleOnGestureListener

    内部类SimpleOnGestureListener,实现了上面2个接口:

    public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener {
            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;
            }
        }
    

    3. 消息类GestureHandler

    GestureDetector类中的消息类型有3种:TAP,SHOW_PRESS,LONG_PRESS,

    • SHOW_PRESS:回调onShowPress()接口

    • LONG_PRESS:回调onLongPress()接口,即长按事件。

    • TAP:如果当前手指不是down状态,回调onSingleTapConfirmed()接口,即单击事件。

    具体实现如下:

    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) {
                            mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
                        } else {
                            mDeferConfirmSingleTap = true;
                        }
                    }
                    break;
                default:
                    throw new RuntimeException("Unknown message " + msg); //never
                }
            }
        }
        //处理长按事件
        private void dispatchLongPress() {
            mHandler.removeMessages(TAP);
            mDeferConfirmSingleTap = false;
            mInLongPress = true;
            mListener.onLongPress(mCurrentDownEvent);
        }
    

    4. 单击,长按,双击,移动等这些事件的判断条件

    源码是怎么判断单击,长按,双击,移动等这些事件的呢?当手指处于按下状态,即down事件,会发送一些消息TAP,LONG_PRESS,SHOW_PRESS(延时发送或在指定的时间点发送),然后在接下来的事件中分别去判断,如果是移动事件,移除TAP,LONG_PRESS和SHOW_PRESS消息;如果是长按事件,就移除TAP消息。这样,如果没有移除TAP消息,那么handler中会回调相应接口。一般双击的判断在down事件,移动的判断发生在move事件,而双击的消费发生在up事件,回调onShowPress(),长按回调onLongPress(),单击回调onSingleTapConfirmed()都发生在handleMessage中。

    4.1 单击

    点击时间在 DOUBLE_TAP_TIMEOUT 内未进行第二次单击事件。

    单击的回调接口为onSingleTapConfirmed()onSingleTapConfirmed()的调用有两个地方:

    1. handlerMessage中
    2. up事件中

    handlerMessage中消息类型为TAP并且只要当前不是按下状态就会调用,up事件中,首先判断双击和长按是否正在进行,如果都不是,判断如果移动区域在mTouchSlopSquare之内并且mDeferConfirmSingleTap为true,会调用。

    4.2 长按

    1. 手指处于按下状态
    2. 按下时间未超过LONGPRESS_TIMEOUT。通过ViewConfiguration.getLongPressTimeout()来获取
    3. 移动未超过mTouchSlopSquare的距离。

    4.3 双击

    1. 是否有上一次的单击事件(上一次down事件和up事件不为空)

    2. mAlwaysInBiggerTapRegion为true。即每次单击的移动距离在 mDoubleTapTouchSlopSquare 之内。

    3. 第二次单击的down事件和第一次单击的up事件的时间间隔在 DOUBLE_TAP_MIN_TIME 和 DOUBLE_TAP_TIMEOUT之间。

    4. 第一次按下的位置和第二次按下的位置之间的距离小于mDoubleTapSlopSquare。

    源码如下:

    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;
                        }
    
    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);
        }
    

    4.4 移动

    mAlwaysInTapRegion为false 或者 mAlwaysInTapRegion为true并且 移动区域在 mTouchSlopSquare 和 mDoubleTapTouchSlopSquare 之间。

    5. 各个事件的具体实现和分析

    看源码之前,我们先来看一些变量。

       private boolean mStillDown;//是否按下。ACTION_DOWN为true,ACTION_UP 和 cancle 为false
       
       //是否延迟确定点击。确定点击即消费点击,其实就是点击事件。在handleMessage中消息类型为TAP时,如果手指为非按下状态,则回调onSingleTapConfirmed接口,否则,mDeferConfirmSingleTap置为true,其他情况全部置为false:ACTION_DOWN,ACTION_UP结尾,处理长按事件,cancle。
       private boolean mDeferConfirmSingleTap;
       
       private boolean mInLongPress;//是否正在处理长按事件
       
       //是否允许双击事件发生,默认为true,除非手动去设置。只有为true,双击事件才能进行
       private boolean mIsLongpressEnabled;
       private boolean mIsDoubleTapping;//当前事件是否是双击事件
       
       //是否当前手指仅在小范围内移动,当手指仅在小范围内移动时,视为手指未曾移动过,不会触发onScroll手势。用来判断点击事件和移动事件。移动距离大于mTouchSlopSquare时,为false。
       private boolean mAlwaysInTapRegion;
       
       //是否当前手指在较大范围内移动,判断双击手势。此值为true时,双击手势成立。移动距离大于mDoubleTapTouchSlopSquare时,为false。
       private boolean mAlwaysInBiggerTapRegion;
       
    

    关于点击区域的变量:

    // 判断是否是单击,是否能构成onScroll事件。移动距离小于mTouchSlopSquare,则是单击,大于,则是onScroll
    private int mTouchSlopSquare;
    
    //判断是否构成双击,单次单击移动距离小于mDoubleTapTouchSlopSquare,mAlwaysInBiggerTapRegion为true。
    private int mDoubleTapTouchSlopSquare;
    
    //判断是否构成双击,第二次按下的位置和第一次抬起的位置的距离小于mDoubleTapSlopSquare,则构成双击
    private int mDoubleTapSlopSquare;
    

    关于时间的变量:

        //发送SHOW_PRESS消息的时间,发生在ACTION_MOVE和ACTION_UP之前。如果在ACTION_MOVE中判断为onScroll事件,则    //取消SHOW_PRESS消息。
        private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
        
        private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
        
        //判断双击
        private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
        private static final int DOUBLE_TAP_MIN_TIME = ViewConfiguration.getDoubleTapMinTime();
    
    • 发送长按LONG_PRESS消息的延时为:

    mCurrentDownEvent.getDownTime() + TAP_TIMEOUT + LONGPRESS_TIMEOUT

    • 发送SHOW_PRESS消息的时间:

    mCurrentDownEvent.getDownTime() + TAP_TIMEOUT

    • 发送TAP消息的延时:

    DOUBLE_TAP_TIMEOUT

        //上一次焦点的x坐标
        private float mLastFocusX;
        private float mLastFocusY;
        //当前焦点的x坐标
        private float mDownFocusX;
        private float mDownFocusY;
        
        private MotionEvent mCurrentDownEvent;//当前down事件
        private MotionEvent mPreviousUpEvent;//上一次up事件
    

    5.1 MotionEvent.ACTION_POINTER_DOWN

    case MotionEvent.ACTION_POINTER_DOWN:
                mDownFocusX = mLastFocusX = focusX;
                mDownFocusY = mLastFocusY = focusY;
                // Cancel long press and taps
                cancelTaps();
                break;
                
    private void cancelTaps() {
            mHandler.removeMessages(SHOW_PRESS);
            mHandler.removeMessages(LONG_PRESS);
            mHandler.removeMessages(TAP);
            mIsDoubleTapping = false;
            mAlwaysInTapRegion = false;
            mAlwaysInBiggerTapRegion = false;
            mDeferConfirmSingleTap = false;
            if (mInLongPress) {
                mInLongPress = false;
            }
        }
    

    5.2 MotionEvent.ACTION_POINTER_UP

    case MotionEvent.ACTION_POINTER_UP:
                mDownFocusX = mLastFocusX = focusX;
                mDownFocusY = mLastFocusY = focusY;
                break;
    

    5.3 MotionEvent.ACTION_DOWN

    1. 如果当前handler有TAP消息,移除所有的TAP消息

    2. 判断是否是双击,如果是,设置回调onDoubleTap和onDoubleTapEvent。如果不是,发送一个延时消息TAP,延时为DOUBLE_TAP_TIMEOUT。

    3. 如果允许长安事件发生,在 mCurrentDownEvent.getDownTime()+TAP_TIMEOUT+LONGPRESS_TIMEOUT 时间发送LONG_PRESS消息。

    4. 发送SHOW_PRESS消息,在mCurrentDownEvent.getDownTime() + TAP_TIMEOUT 时间。

    源码如下:

            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;
    

    5.4 MotionEvent.ACTION_MOVE

    判断是否正在进行长按事件,是,break,否,向下执行。

    判断是否是双击事件,是,回调onDoubleTapEvent()接口,否,向下执行。

    判断是否是scroll事件,是,移除所有消息,并且向下执行。

    移动距离只否满足双击的条件

           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;
    

    5.5 MotionEvent.ACTION_UP

    1. 如果是双击事件,回调onDoubleTapEvent接口

    2. 如果正在执行长按事件,移除TAP消息,mInLongPress置为false。mInLongPress为boolean类型的变量,判断是否正在执行长按事件。

    3. 如果是移动事件,回调onSingleTapUp接口;

    4. 移除SHOW_PRESS和LONG_PRESS消息。

    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 {
                    // 这里是判断fling事件的代码,这里暂不讨论
                }
                if (mPreviousUpEvent != null) {
                    mPreviousUpEvent.recycle();
                }
                // Hold the event we obtained above - listeners may have changed the original.
                mPreviousUpEvent = currentUpEvent;
                
                mIsDoubleTapping = false;
                mDeferConfirmSingleTap = false;
                mHandler.removeMessages(SHOW_PRESS);
                mHandler.removeMessages(LONG_PRESS);
                break;
    

    5.6 MotionEvent.ACTION_CANCEL

    case MotionEvent.ACTION_CANCEL:
                cancel();
                break;
            }
            
    private void cancel() {
            mHandler.removeMessages(SHOW_PRESS);
            mHandler.removeMessages(LONG_PRESS);
            mHandler.removeMessages(TAP);
            mVelocityTracker.recycle();
            mVelocityTracker = null;
            mIsDoubleTapping = false;
            mStillDown = false;
            mAlwaysInTapRegion = false;
            mAlwaysInBiggerTapRegion = false;
            mDeferConfirmSingleTap = false;
            if (mInLongPress) {
                mInLongPress = false;
            }
        }
    

    相关文章

      网友评论

        本文标题:安卓源码GestureDetector类解读

        本文链接:https://www.haomeiwen.com/subject/uettjttx.html