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