Android VelocityTracker与GestureDetector使用简介

时间:2022-11-24 19:06:40

VelocityTracker使用简介

当你需要跟踪触摸屏事件的速度的时候,使用obtain()方法来获得VelocityTracker类的一个实例对象
onTouchEvent回调函数中,使用addMovement(MotionEvent)函数将当前的移动事件传递给VelocityTracker对象
使用computeCurrentVelocity (int units)函数来计算当前的速度,使用getXVelocity ()、 getYVelocity ()函数来获得当前的速度
代码如下:


public class MainActivity extends Activity {
    private TextView mInfo;

    private VelocityTracker mVelocityTracker;
    private int mMaxVelocity;

    private int mPointerId;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mInfo = new TextView(this);
        mInfo.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
        mInfo.setGravity(Gravity.CENTER);
        mInfo.setTextColor(getResources().getColor(android.R.color.holo_red_dark));

        setContentView(mInfo);

        //Maximum velocity to initiate a fling, as measured in pixels per second
        mMaxVelocity = ViewConfiguration.get(this).getScaledMaximumFlingVelocity();  //注意这里返回的都是每秒的像素单位

        ViewConfiguration.get(this).getScaledTouchSlop();//获得能够视为是手势滑动的最短距离
        ViewConfiguration.get(this).getScaledMinimumFlingVelocity();//获得允许执行一个fling手势动作的最小速度值
        ViewConfiguration.get(this).getScaledMaximumFlingVelocity();//获得允许执行一个fling手势动作的最大速度值
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        final int action = event.getAction();
        //当你需要跟踪触摸屏事件的速度的时候,使用obtain()方法来获得VelocityTracker类的一个实例对象
        //在onTouchEvent回调函数中,使用addMovement(MotionEvent)函数将当前的移动事件传递给VelocityTracker对象
        acquireVelocityTracker(event);
        //final VelocityTracker verTracker = mVelocityTracker;
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                //求第一个触点的id, 此时可能有多个触点,但至少一个
                mPointerId = event.getPointerId(0);
                break;

            case MotionEvent.ACTION_MOVE:
                //求伪瞬时速度  如果速度小于mMaxVelocity,正常显示。如果大于mMaxVelocity,则显示mMaxVelocity
                mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
                //1000表示像素/秒,1表示像素/毫秒。因为mMaxVelocity是用像素/秒做单位,所以此时用1000

                final float velocityX = mVelocityTracker.getXVelocity(mPointerId);
                final float velocityY = mVelocityTracker.getYVelocity(mPointerId);
                //getXVelocity getYVelocity之前必须先调用computeCurrentVelocity
                recodeInfo(velocityX, velocityY);
                break;

            case MotionEvent.ACTION_UP:
                releaseVelocityTracker();
                break;

            case MotionEvent.ACTION_CANCEL:
                releaseVelocityTracker();
                break;

            default:
                break;
        }
        return super.onTouchEvent(event);
    }
	//获取VelocityTracker同时把event增加进去
    private void acquireVelocityTracker(final MotionEvent event) {
        if (null == mVelocityTracker) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(event);
    }
	//捕获到UP事件的时候,记得release
    private void releaseVelocityTracker() {
        if (null != mVelocityTracker) {
            mVelocityTracker.clear();
            mVelocityTracker.recycle();
            mVelocityTracker = null;
        }
    }

    private static final String sFormatStr = "velocityX=%f\nvelocityY=%f";

    /**
     * 记录当前速度
     *
     * @param velocityX x轴速度
     * @param velocityY y轴速度
     */
    private void recodeInfo(final float velocityX, final float velocityY) {
        final String info = String.format(sFormatStr, velocityX, velocityY);
        Log.d("LiaBin", "info:" + info);
        mInfo.setText(info);
    }
}

GestureDetector使用详解

首先参考:http://blog.csdn.net/harvic880925/article/details/39520901 中介绍

功能介绍

当用户触摸屏幕的时候,会产生许多手势,例如down,up,scroll,filing等等。
一般情况下,我们知道View类有个View.OnTouchListener内部接口,通过重写他的onTouch(View v, MotionEvent event)方法,我们可以处理一些touch事件,但是这个方法太过简单,如果需要处理一些复杂的手势,用这个接口就会很麻烦(因为我们要自己根据用户触摸的轨迹去判断是什么手势)。
Android sdk给我们提供了GestureDetector(Gesture:手势Detector:识别)类,通过这个类我们可以识别很多的手势,主要是通过他的onTouchEvent(event)方法完成了不同手势的识别。虽然他能识别手势,但是不同的手势要怎么处理,应该是提供给程序员实现的。


使用介绍

1.  创建OnGestureListener监听对象
方式1:
实现接口:OnGestureListener 必须实现如下方法
onDown  onShowPress onSingleTapUp  onScroll  onLongPress  onFling  
OnDown(MotionEvent e):用户按下屏幕就会触发
onScroll(MotionEvent e1, MotionEvent e2,float distanceX, float distanceY):在屏幕上拖动事件。无论是用手拖动view,或者是以抛的动作滚动,都会多次触发,这个方法       在ACTION_MOVE动作发生时就会触发
    滑屏:手指触动屏幕后,稍微滑动后立即松开
    onDown-----》onScroll----》onScroll----》onScroll----》………----->onFling
    拖动
    onDown------》onScroll----》onScroll------》onFiling
    可见,无论是滑屏,还是拖动,影响的只是中间OnScroll触发的数量多少而已,最终都会触发onFling事件!
注意此时e1始终是ACTION_DOWN,就是按下那个点的事件,而e2是ACTION_MOVE的事件。distanceX其实是两次ACTION_MOVE事件在X坐标方向的偏移值,同理distanceY

onFling(MotionEvent e1, MotionEvent e2, float velocityX,float velocityY) :滑屏,用户按下触摸屏、快速移动后松开,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE, 1个ACTION_UP触发   
     参数解释:
    e1:第1个ACTION_DOWN MotionEvent
    e2:ACTION_UP MotionEvent
    velocityX:X轴上的移动速度,像素/秒
    velocityY:Y轴上的移动速度,像素/秒
注意,必须是快速的松开,才会触发onFling,如果是慢吞吞的就不会触发了。同时onFling在手指松开的时候调用,e1是ACTION_DOWN事件,e2是=ACTION_UP事件。

方式2:
实现类:SimpleOnGestureListener  空实现了OnGestureListener接口,用这个就不必实现接口所有的方法了

2. 创建GestureDetector实例mGestureDetector
   mGestureDetector = new GestureDetector(this, new gestureListener());//使用上面介绍的OnGestureListener监听对象
3. onTouch(View v, MotionEvent event)中拦截
    public boolean onTouch(View v, MotionEvent event) {
        return mGestureDetector.onTouchEvent(event);
    }
4. 控件绑定
tv.setOnTouchListener(this);


代码使用示例

public class MainActivity extends Activity implements View.OnTouchListener {

    private GestureDetector mGestureDetector;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mGestureDetector = new GestureDetector(this, new gestureListener()); //使用派生自OnGestureListener

        TextView tv = (TextView) findViewById(R.id.tv);
        tv.setOnTouchListener(this);
        tv.setFocusable(true);
        tv.setClickable(true);
        tv.setLongClickable(true);
    }


    /*
     * 在onTouch()方法中,我们调用GestureDetector的onTouchEvent()方法,将捕捉到的MotionEvent交给GestureDetector
     * 来分析是否有合适的callback函数来处理用户的手势
     */
    public boolean onTouch(View v, MotionEvent event) {
        return mGestureDetector.onTouchEvent(event);
    }

    private class gestureListener implements GestureDetector.OnGestureListener {

        // 用户轻触触摸屏,由1个MotionEvent ACTION_DOWN触发
        public boolean onDown(MotionEvent e) {
            Log.i("LiaBin", "onDown");
            Toast.makeText(MainActivity.this, "onDown", Toast.LENGTH_SHORT).show();
            return true;
        }

        /*
         * 用户轻触触摸屏,尚未松开或拖动,由一个1个MotionEvent ACTION_DOWN触发
         * 注意和onDown()的区别,强调的是没有松开或者拖动的状态
         *
         * 而onDown也是由一个MotionEventACTION_DOWN触发的,但是他没有任何限制,
         * 也就是说当用户点击的时候,首先MotionEventACTION_DOWN,onDown就会执行,
         * 如果在按下的瞬间没有松开或者是拖动的时候onShowPress就会执行,如果是按下的时间超过瞬间
         * (这块我也不太清楚瞬间的时间差是多少,一般情况下都会执行onShowPress),拖动了,就不执行onShowPress。
         */
        public void onShowPress(MotionEvent e) {
            Log.i("LiaBin", "onShowPress");
            Toast.makeText(MainActivity.this, "onShowPress", Toast.LENGTH_SHORT).show();
        }

        // 用户(轻触触摸屏后)松开,由一个1个MotionEvent ACTION_UP触发
        ///轻击一下屏幕,立刻抬起来,才会有这个触发
        //从名子也可以看出,一次单独的轻击抬起操作,当然,如果除了Down以外还有其它操作,那就不再算是Single操作了,所以这个事件 就不再响应
        public boolean onSingleTapUp(MotionEvent e) {
            Log.i("LiaBin", "onSingleTapUp");
            Toast.makeText(MainActivity.this, "onSingleTapUp", Toast.LENGTH_SHORT).show();
            return true;
        }

        // 用户按下触摸屏,并拖动,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE触发,记住e1使用是ACTION_DOWN事件,distanceX是两次ACTION_MOVE事件的偏移量
        public boolean onScroll(MotionEvent e1, MotionEvent e2,
                                float distanceX, float distanceY) {
            Log.i("LiaBin", "onScroll: " + "e1.getX:" + e1.getX() + " e2.getX:" + e2.getX() + " distanceX:" + distanceX);
            Toast.makeText(MainActivity.this, "onScroll", Toast.LENGTH_LONG).show();

            return true;
        }

        // 用户长按触摸屏,由多个MotionEvent ACTION_DOWN触发
        public void onLongPress(MotionEvent e) {
            Log.i("LiaBin", "onLongPress");
            Toast.makeText(MainActivity.this, "onLongPress", Toast.LENGTH_LONG).show();
        }

        // 用户按下触摸屏、快速移动后松开,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE, 1个ACTION_UP触发。记住松开只调用一次,e1记录的是ACTION_DOWN,
        //e2记录的是ACTION_UP事件    
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
                               float velocityY) {
            Log.i("LiaBin", "onFling: " + "e1.getX:" + e1.getX() + " e2.getX:" + e2.getX() +
                    " velocityX:" + velocityX + " velocityY:" + velocityY);
            Toast.makeText(MainActivity.this, "onFling", Toast.LENGTH_LONG).show();
            return true;
        }
    }
}

GestureDetector源码解析

可以看到onDown  onSingleTapUp  onScroll  onFling 有返回值,其它几个没有返回值。

所以注意这几个方法的返回值,如果返回为true,那么事件就被消耗掉了,该view的onTouchEvent就执行不到了。

    public boolean onTouch(View v, MotionEvent event) {
        return mGestureDetector.onTouchEvent(event);
    }


所以直接看GestureDetector的源码,可以看到onDown在ACTION_DOWN中,onScroll 在ACTION_MOVE中,onSingleTapUp和onFling在ACTION_UP中。

同时可以看到onSingleTapUp和onFling因为if-else关系只会执行一个

         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);//调用onDown
            break;

        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);
                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);//调用onScroll
                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 && !mIgnoreNextUpEvent) {
                handled = mListener.onSingleTapUp(ev);//调用onSingleTapUp
                if (mDeferConfirmSingleTap && mDoubleTapListener != null) {
                    mDoubleTapListener.onSingleTapConfirmed(ev);
                }
            } else if (!mIgnoreNextUpEvent) {

                // 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);//调用onFling
                }
            }
            if (mPreviousUpEvent != null) {
                mPreviousUpEvent.recycle();
            }
            // Hold the event we obtained above - listeners may have changed the original.
            mPreviousUpEvent = currentUpEvent;
            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;

        case MotionEvent.ACTION_CANCEL:
            cancel();
            break;
        }
mListener就是上文通过构造函数传递进来的
mGestureDetector = new GestureDetector(this, new gestureListener());