您的位置:首页 > 其它

滑轮控件研究二、GestureDetector的深入研究

2014-07-23 19:31 344 查看
所谓手势,就是指用户的手指或者触摸笔在触摸屏上的连续触摸行为,比如在屏幕上从左至右划出的一个动作,就是手势。在比如在屏幕上画一个圆圈也是一个手势。手势的这种连续的触碰会形成某个方向上的移动趋势,也会形成一个不规则的几何图形。Android对两种手势行为都提供了支持:

对于第一种手势行为而言,Android提供了手势检测,并为手势检测提供了相应的监听器。

对于第二种手势行为,Android允许开发者添加自己的手势,并提供相应的API识别手势。

一、GestureDetector类

[java]
view plaincopy

public class GestureDetector {

// TODO: ViewConfiguration
private int mBiggerTouchSlopSquare = 20 * 20;//touch事件最大超时时间的平方

private int mTouchSlopSquare;//touch事件超时的时间平方
private int mDoubleTapSlopSquare;//双击事件超时时间的平方
private int mMinimumFlingVelocity;//最小滑动速率
private int mMaximumFlingVelocity;//最大滑动速率

private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();//长按超时
private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout();//单击超时
private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();//双击超时

// constants for Message.what used by GestureHandler below
private static final int SHOW_PRESS = 1;//短按标志
private static final int LONG_PRESS = 2;//长按标志
private static final int TAP = 3;//轻击标志

private final Handler mHandler;// Handler
private final OnGestureListener mListener;// 普通手势监听器
private OnDoubleTapListener mDoubleTapListener;// 双击或快速单击监听器

private boolean mStillDown;//是否按下就不动了
private boolean mInLongPress;//是否在长按过程中
private boolean mAlwaysInTapRegion;//是否一直点击同一个位置
private boolean mAlwaysInBiggerTapRegion;//是否在更大的范围内点击

private MotionEvent mCurrentDownEvent;// 这次手势按下的事件
private MotionEvent mPreviousUpEvent;// 上次手势抬起的事件

//如果用户仍然处于第二次点击的过程(按下,滑动,抬起),就为true。只能为真 如果有双击事件的监听器就只能为true
private boolean mIsDoubleTapping;

private float mLastMotionY;//最后一次动作的Y坐标
private float mLastMotionX;//最后一次动作的X坐标

private boolean mIsLongpressEnabled;//长按事件是否启用

/**
* 如果我们试用的API的版本级别>=Froyo,或者开发人员去显示的设置它,就为true。
* 如果为true,输入事件>1个触摸点,将会被忽略。
* 那么我们就能更好并排着的检测多点触控手势。
*/
private boolean mIgnoreMultitouch;//是否支持多点touch事件

/**
* 解决滑动持续时候的速度
*/
private VelocityTracker mVelocityTracker;// 追踪触摸事件的速率

/**
*处理某些指定的手势
*/
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:
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
if (mDoubleTapListener != null && !mStillDown) {
mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
}
break;

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

/**
* 在一个非UI线程中创建一个GestureDetector
* 已经过时了,用下面的构造方法代替
* public GestureDetector(Context context, OnGestureListener listener, Handler handler)
*/
@Deprecated
public GestureDetector(OnGestureListener listener, Handler handler) {
this(null, listener, handler);
}

/**
* 在一个非UI线程中创建一个GestureDetector
* 已经过时了,用下面的构造方法代替
* public GestureDetector(Context context, OnGestureListener listener, Handler handler)
*/
@Deprecated
public GestureDetector(OnGestureListener listener) {
this(null, listener, null);
}

/**
* 在UI线程中创建一个GestureDetector
*/
public GestureDetector(Context context, OnGestureListener listener) {
this(context, listener, null);
}

/**
* 在UI线程中创建一个GestureDetector
*/
public GestureDetector(Context context, OnGestureListener listener, Handler handler) {
this(context, listener, handler, context != null
&& context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.FROYO);
}

/**
* 在UI线程中创建一个GestureDetector
* 不管你用的是哪个方法产生实例,都会调用这个构造器
*/
public GestureDetector(Context context, OnGestureListener listener, Handler handler,
boolean ignoreMultitouch) {
if (handler != null) {
mHandler = new GestureHandler(handler);
} else {
mHandler = new GestureHandler();
}
mListener = listener;
//如果,这个listener只是实现了OnDoubleTapListener接口,就调用setOnDoubleTapListener方法
//,初始化mDoubleTapListener对象
if (listener instanceof OnDoubleTapListener) {
setOnDoubleTapListener((OnDoubleTapListener) listener);
}
init(context, ignoreMultitouch);
}

/**
* 初始化信息
*/
private void init(Context context, boolean ignoreMultitouch) {
//如果没有手势检测类,将抛出异常
if (mListener == null) {
throw new NullPointerException("OnGestureListener must not be null");
}
mIsLongpressEnabled = true;//默认长按事件开启
mIgnoreMultitouch = ignoreMultitouch;//对多点触摸的处理

// Fallback to support pre-donuts releases
int touchSlop, doubleTapSlop;//touch超时,双击超时
//对于一些超时操作需要变量的定义,通俗点说,就是不同事件的转变时间的问题
//比如 按住多长,变为longPress事件,双击事件之间的时间间隔
if (context == null) {
// noinspection deprecation
touchSlop = ViewConfiguration.getTouchSlop();
doubleTapSlop = ViewConfiguration.getDoubleTapSlop();
// noinspection deprecation
mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();
mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity();
} else {
final ViewConfiguration configuration = ViewConfiguration.get(context);
touchSlop = configuration.getScaledTouchSlop();
doubleTapSlop = configuration.getScaledDoubleTapSlop();
mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity();
}
mTouchSlopSquare = touchSlop * touchSlop;
mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop;
}

/**
* 设置回调双击事件和解释手势行为的监听器
*/
public void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener) {
mDoubleTapListener = onDoubleTapListener;
}

/**
* 如果你设置true的话就是开启了长按键,当你长时间触屏不动就能得到 onLongPress 手势,
* 如果设置false 那么你长时间触屏不移动也得不到这个手势的支持
* 默认设置为true
*/
public void setIsLongpressEnabled(boolean isLongpressEnabled) {
mIsLongpressEnabled = isLongpressEnabled;
}

/**
* 返回长按事件传播的true或者false
*/
public boolean isLongpressEnabled() {
return mIsLongpressEnabled;
}

/**
* 分析给出的事件,如何适用的话,就会去触发我们所提供的OnGestureListener中的
* 回调方法
*/
public boolean onTouchEvent(MotionEvent ev) {
final int action = ev.getAction();//事件的类型
final float y = ev.getY();//事件的x坐标
final float x = ev.getX();//事件的y坐标

//初始化速率追踪者,并将事件添加进去
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);

boolean handled = false;//是否要发消失

switch (action & MotionEvent.ACTION_MASK) {//判断事件的类型
case MotionEvent.ACTION_POINTER_DOWN://非主触点按下的时候
if (mIgnoreMultitouch) {//如果忽略多点触摸,那么就调用cancel()方法
// Multitouch event - abort.
cancel();
}
break;

case MotionEvent.ACTION_POINTER_UP://非主触点抬起的时候
// Ending a multitouch gesture and going back to 1 finger
if (mIgnoreMultitouch && ev.getPointerCount() == 2) {
//获得触点的id
int index = (((action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT) == 0) ? 1
: 0;
mLastMotionX = ev.getX(index);//得到x,作为上一次的事件x坐标保存
mLastMotionY = ev.getY(index);//得到y,作为上一次的事件y坐标保存
mVelocityTracker.recycle();//回收mVelocityTracker
mVelocityTracker = VelocityTracker.obtain();//重现得到
}
break;

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

mLastMotionX = x;
mLastMotionY = y;
if (mCurrentDownEvent != null) {
mCurrentDownEvent.recycle();
}
mCurrentDownEvent = MotionEvent.obtain(ev);
mAlwaysInTapRegion = true;
mAlwaysInBiggerTapRegion = true;
mStillDown = true;
mInLongPress = 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);
break;

case MotionEvent.ACTION_MOVE:
if (mInLongPress || (mIgnoreMultitouch && ev.getPointerCount() > 1)) {
break;
}
final float scrollX = mLastMotionX - x;
final float scrollY = mLastMotionY - y;
if (mIsDoubleTapping) {
// Give the move events of the double-tap
handled |= mDoubleTapListener.onDoubleTapEvent(ev);
} else if (mAlwaysInTapRegion) {
final int deltaX = (int) (x - mCurrentDownEvent.getX());
final int deltaY = (int) (y - mCurrentDownEvent.getY());
int distance = (deltaX * deltaX) + (deltaY * deltaY);
if (distance > mTouchSlopSquare) {
handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
mLastMotionX = x;
mLastMotionY = y;
mAlwaysInTapRegion = fal6se;
mHandler.removeMessages(TAP);
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
}
if (distance > mBiggerTouchSlopSquare) {
mAlwaysInBiggerTapRegion = false;
}
} else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
mLastMotionX = x;
mLastMotionY = y;
}
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) {
handled = mListener.onSingleTapUp(ev);
} else {

// A fling must travel the minimum tap distance
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
final float velocityY = velocityTracker.getYVelocity();
final float velocityX = velocityTracker.getXVelocity();

if ((Math.abs(velocityY) > mMinimumFlingVelocity)
|| (Math.abs(velocityX) > mMinimumFlingVelocity)) {
handled = mListener.onFling(mCurrentDownEvent, ev, velocityX, velocityY);
}
}
if (mPreviousUpEvent != null) {
mPreviousUpEvent.recycle();
}
// Hold the event we obtained above - listeners may have changed the
// original.
mPreviousUpEvent = currentUpEvent;
mVelocityTracker.recycle();
mVelocityTracker = null;
mIsDoubleTapping = false;
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
break;
case MotionEvent.ACTION_CANCEL:
cancel();
}
return handled;
}

/**
* 私有方法,取消的方法,移动消息队列中的消息,释放内存
*/
private void cancel() {
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
mHandler.removeMessages(TAP);
mVelocityTracker.recycle();
mVelocityTracker = null;
mIsDoubleTapping = false;
mStillDown = false;
if (mInLongPress) {
mInLongPress = false;
}
}

/**
* 判断双击事件中,两次点击的位置关系。
* 如果间隔很远,就不触发双击的事件
*/
private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp,
MotionEvent secondDown) {
if (!mAlwaysInBiggerTapRegion) {
return false;
}

if (secondDown.getEventTime() - firstUp.getEventTime() > DOUBLE_TAP_TIMEOUT) {
return false;
}

int deltaX = (int) firstDown.getX() - (int) secondDown.getX();
int deltaY = (int) firstDown.getY() - (int) secondDown.getY();
return (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare);
}

private void dispatchLongPress() {
mHandler.removeMessages(TAP);
mInLongPress = true;
mListener.onLongPress(mCurrentDownEvent);
}
}

源码中也没啥详细的注释,看了一段时间,实在是看的头大,就放弃了。可以去看下GestureDetector类中文API:/article/4602666.html
如果对上面的事件发生的时间不太清楚的话,可以自己试验下,可以看下篇的小例子:滑轮控件研究三、GestureDetector的中手势事件的测试

对于上面的VelocityTracker,如何追踪touch事件的速率不明白的话,可看下下面的分析:滑轮控件研究四、VelocityTracker的简单研究

对于上面的ViewConfiguration某些变量不明白的,看以看下下面的分析:滑轮控件研究五、ViewConfiguration的简单研究

如果,想直接看下GestureDetector的实际用法,可以看下面的:滑轮控件研究六、GestureDetector的简单应用
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: