Android源码阅读1————GestureDetectory

Android源码阅读1————GestureDetectory

一.前言

目前接触Android也差不多一年了,一直处于知其然不知其所以然,所以也是希望从源码的角度出发,理解更多关于Android的知识。

这个系列我希望可以一直写下去,增强我对Android的理解。

开头的第一篇我选择了一个不是很长的源码,GestureDetector手势控制器来开始阅读。

参考资料:Android手势检测——GestureDetector全面分析

二. GestureDetectory的作用

关于GestureDetectory的作用及使用,我的上一篇博客中有讲到
Android艺术开发探索学习笔记2————初识View
这里我就不再赘述了。

三.接口

在GestureDetector有4个接口
这里写图片描述
接口中每个方法的作用在上一篇博客中也有讲到过。

四.初始化处理

构造方法

//构造方法共有4个,但底层调用都是下面那一个
    public GestureDetector(Context context, OnGestureListener listener,Handler handler) {
    //设置handler
        if (handler != null) {
            mHandler = new GestureHandler(handler);
        } else {
            mHandler = new GestureHandler();
        }

    //设置listener
        mListener = listener;
        if (listener instanceof OnDoubleTapListener) {
            setOnDoubleTapListener((OnDoubleTapListener) listener);
        }
        if (listener instanceof OnContextClickListener) {
            setContextClickListener((OnContextClickListener) listener);
        }
        init(context);
    }

GestureHandler 方法

 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:
               //手指轻触屏幕的一瞬间,尚未松开,调用接口中的onShowPress
                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
                //这里控制SingleTapConfirmed的回调,
                if (mDoubleTapListener != null) {
                    if (!mStillDown) {
                     //回调onSingleTapConfirmed
                         mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
                    } else {
                    //如果处理Message的时候还没松开,就设置mDeferConfirmSingleTap为true,在UP事件的时候调用SingleTapConfirme
                        mDeferConfirmSingleTap = true;
                    }
                }
                break;

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


//长按处理
    private void dispatchLongPress() {
        mHandler.removeMessages(TAP);
        mDeferConfirmSingleTap = false;
        mInLongPress = true;
        mListener.onLongPress(mCurrentDownEvent);
    }

setOnDoubleTapListener&setContextClickListener方法

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


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

init方法

    private void init(Context context) {
        if (mListener == null) {
            throw new NullPointerException("OnGestureListener must not be null");
        }
        mIsLongpressEnabled = true;

        // 获取设置,边界距离,用于判断是否属于滑动,是否属于双击。
        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;
    }

五. 输入处理

初始化完之后,就是看它如何处理输入。
1.onTouchEvent

 public boolean onTouchEvent(MotionEvent ev) {
 //检查事件输入的一致性,log出来一致性的信息,如:有事件只有up没有down
        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;
        final boolean isGeneratedGesture =
                (ev.getFlags() & MotionEvent.FLAG_IS_GENERATED_GESTURE) != 0;

        // Determine focal point
        //是非主要指针抬起动作会跳过
        float sumX = 0, sumY = 0;
        final int count = ev.getPointerCount();
        //把所有还在触摸顶点手指的位置x,y加起来,求平均值。
        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:
            //...多点触摸动作
            break;

        case MotionEvent.ACTION_POINTER_UP:
            //...多点离开动作
            break;

        case MotionEvent.ACTION_DOWN:
            //...单点触摸动作
            break;

        case MotionEvent.ACTION_MOVE:
            //...触摸点移动动作
            break;

        case MotionEvent.ACTION_UP:
            //...单点触摸离开动作
            break;

        case MotionEvent.ACTION_CANCEL://触摸动作取消
            cancel();
            break;
        }
        //对未被处理的事件进行一次一致性检测
        if (!handled && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 0);
        }
        return handled;
    }

onTouchEvent()的主要思路就是先对输入事件做出统一处理,提取一些共有的信息,如多个点同时触摸时候的中心焦点和滑动速度等,然后根据事件的分类做出相应的处理。

2.DOWN事件处理

             case MotionEvent.ACTION_DOWN:
                if (mDoubleTapListener != null) {
                    //处理双击
                    //取消TAP事件
                    boolean hadTapMessage = mHandler.hasMessages(TAP);
                    if (hadTapMessage) mHandler.removeMessages(TAP);
                    //判断是否是双击事件,isConsideredDoubleTap函数在下
                    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
                        //延时发出单击事件,如果到了时间(300ms)还没有取消的话就确认是TAP事件了
                        mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);
                    }
                }

                mDownFocusX = mLastFocusX = focusX;
                mDownFocusY = mLastFocusY = focusY;
                //重置CurrentDownEvent
                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() + LONGPRESS_TIMEOUT);
                }
                //延时发送showPress事件
                mHandler.sendEmptyMessageAtTime(SHOW_PRESS,
                        mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);
                handled |= mListener.onDown(ev);
                break;



    //判断第二次点击是否是有效点击
    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();

        //MotionEvent.FLAG_IS_GENERATED_GESTURE标识表示GestureDector不使用任何触摸
        final boolean isGeneratedGesture =
                (firstDown.getFlags() & MotionEvent.FLAG_IS_GENERATED_GESTURE) != 0;
        int slopSquare = isGeneratedGesture ? 0 : mDoubleTapSlopSquare;

        //判断第二次点击是否在附近,在附近才被认为是双击
        return (deltaX * deltaX + deltaY * deltaY < slopSquare);
    }

在DOWN事件中涉及到

  • 处理单击事件,如果收到一次Down事件,并且前端时间也没有DOWN事件的话,会发送一个延时的TAP信息,而一段时间(300ms)之后没有取消的话,就执行GestureHandler里面的TAP单击确认操作
  • 处理双击事件,如果前面有一次DOWN事件,而且也符合isConsideredDoubleTap()的条件(第一次点击后没有移动超出范围,第二次点击也在附近),就可以确认双击,执行onDoubleTap()和onDoubleTapEvent()的回调。
  • 处理长按事件:先看用户是否允许检查长按,然后发送一个延时LONG_PRESS信息,如果到时候还没取消,就回调长按方法。
  • 处理showPress判断:这个和长按差不多,只是短一些。

3. MOVE事件处理

            case MotionEvent.ACTION_MOVE:
                //如果是正在长按和点击了鼠标右键
                if (mInLongPress || mInContextClick) {
                    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);
                    int slopSquare = isGeneratedGesture ? 0 : mTouchSlopSquare;
                    //mTouchSlopSquare是一个距离的平方,表示滑动的时候,手的移动要大于这个距离才认为是Scroll事件
                    if (distance > slopSquare) {
                        //进入滑动模式
                        handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
                        mLastFocusX = focusX;
                        mLastFocusY = focusY;
                        mAlwaysInTapRegion = false;
                        mHandler.removeMessages(TAP);
                        mHandler.removeMessages(SHOW_PRESS);
                        mHandler.removeMessages(LONG_PRESS);
                    }
                    int doubleTapSlopSquare = isGeneratedGesture ? 0 : mDoubleTapTouchSlopSquare;
                    //如果移动距离超过允许范围,则不再认为移动事件是双击的
                    if (distance > doubleTapSlopSquare) {
                        mAlwaysInBiggerTapRegion = false;
                    }
                } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
                    //后续的Scroll移动,前面的是进入Scroll移动
                    handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
                    mLastFocusX = focusX;
                    mLastFocusY = focusY;
                }
                break;

MOVE事件涉及到

  • onDoubleTapEvent() 回调:只要确认是双击之后,mIsDoubleTapping为true,除了长按,后面的MOVE事件都会只回调onDoubleTapEvent().
  • onScroll回调:当MOVE不是长按时,不是DoubleTapEvent之后,当移动距离大于移动距离之后,就会进入Scroll模式,然后两个MOVE事件的位移距离scrollX或者scroolY大于1px,就会调用onScroll。

4.UP事件的处理

            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);
                    mInLongPress = false;
                } else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) {
                    handled = mListener.onSingleTapUp(ev);
                    //处理单击确认,具体逻辑在GestureHandler如何处理TAP事件
                    if (mDeferConfirmSingleTap && mDoubleTapListener != null) {
                        mDoubleTapListener.onSingleTapConfirmed(ev);
                    }
                } else if (!mIgnoreNextUpEvent) {
                    //处理Fling,如果速度符合要求,就回调fling
                    // 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);
                    }
                }

                //重置mPreviousUpEvent
                if (mPreviousUpEvent != null) {
                    mPreviousUpEvent.recycle();
                }
                // Hold the event we obtained above - listeners may have changed the original.
                mPreviousUpEvent = currentUpEvent;

                //回收mVelocityTracker
                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;

UP事件涉及到:

  • onDoubleTapEvent()回调:只要确认是双击之后,mIsDoubleTapping为true,除了长按,后面的MOVE事件都会只回调onDoubleTapEvent()。
  • onSingleTapUp()回调:DOWN事件之后没有MOVE,或者MOVE的距离没有超出范围,mAlwaysInTapRegion才不会变成false,回调onSingleTapUp()。
  • onSingleTapConfirmed()回调

5.多点事件的处理

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

                //如果剩下的手指速度方向和抬起那根手指速度相反方向,就说明不是fling,清空速度监听
                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;

6. ContextClick的处理

//ContextClick处理事件
    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)) {
                    //无视下一个UP事件,因为它是由鼠标右键或者触控笔键带起的
                    mInContextClick = false;
                    mIgnoreNextUpEvent = true;
                }
                break;
        }
        return false;
    }

六.后记

第一次分析源码,还是有很多的不足,在这其中也是百度了很多,也对其他的博客参考了很多。发现自己阅读代码的能力还是有些薄弱,希望自己可以把阅读源码这个习惯坚持下去,让自己的阅读代码的能卡里更近一层,对Android的了解更加深入.

猜你喜欢

转载自blog.csdn.net/qq_38499859/article/details/80490418