欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android源码阅读1————GestureDetectory

程序员文章站 2022-05-14 15:47:45
...

#Android源码阅读1————GestureDetectory

一.前言

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

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

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

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

二. GestureDetectory的作用

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

三.接口

在GestureDetector有4个接口
Android源码阅读1————GestureDetectory
接口中每个方法的作用在上一篇博客中也有讲到过。

四.初始化处理

构造方法

//构造方法共有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的了解更加深入.

相关标签: Android 源码