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.youkuaiyun.com/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());