public class GestureDetector {
public interface OnGestureListener {
//单纯的手指按下时触发
boolean onDown(MotionEvent e);
//按下一段时间TAP_TIMEOUT后触发,用来给出提示,不影响之后单击的判定
void onShowPress(MotionEvent e);
//必须是点击过程的抬起事件,移动后再抬起就不会被触发,但后面也有可能会跟着双击事件
boolean onSingleTapUp(MotionEvent e);
//按下后滑动时触发
boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
//按下一段比onShowPress更长的时间TAP_TIMEOUT+LONGPRESS_TIMEOUT后触发,取消之后单击的判定
void onLongPress(MotionEvent e);
//快速滑动,抬手后时触发
boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
}
//确认了单击和双击时的回调
public interface OnDoubleTapListener {
/**
* Notified when a single-tap occurs.
* <p>
* Unlike {@link OnGestureListener#onSingleTapUp(MotionEvent)}, this
* will only be called after the detector is confident that the user's
* first tap is not followed by a second tap leading to a double-tap
* gesture.
*
* @param e The down motion event of the single-tap.
* @return true if the event is consumed, else false
*/
//单纯的单击,后面没有跟随双击,此处的e可能是down也可能是up事件
boolean onSingleTapConfirmed(MotionEvent e);
//在双击过程中,第二次点击时触发,但返回的是第一次点击时的数据
boolean onDoubleTap(MotionEvent e);
//在双击过程中,第二次点击时的按下、滑动和抬起事件
boolean onDoubleTapEvent(MotionEvent e);
}
/**
* The listener that is used to notify when a context click occurs. When listening for a
* context click ensure that you call {@link #onGenericMotionEvent(MotionEvent)} in
* {@link View#onGenericMotionEvent(MotionEvent)}.
*/
public interface OnContextClickListener {
/**
* Notified when a context click occurs.
*
* @param e The motion event that occurred during the context click.
* @return true if the event is consumed, else false
*/
boolean onContextClick(MotionEvent e);
}
/**
* A convenience class to extend when you only want to listen for a subset
* of all the gestures. This implements all methods in the
* {@link OnGestureListener}, {@link OnDoubleTapListener}, and {@link OnContextClickListener}
* but does nothing and return {@code false} for all applicable methods.
*/
public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener,
OnContextClickListener {
......
}
private int mTouchSlopSquare;
private int mDoubleTapTouchSlopSquare;
private int mDoubleTapSlopSquare;
private int mMinimumFlingVelocity;
private int mMaximumFlingVelocity;
//按下TAP_TIMEOUT时间内没有滑动,则触发SHOW_PRESS回调,它和判断是否单击无关
private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
//在DOUBLE_TAP_TIMEOUT内没有产生双击,且按下时间没有形成长按,则判定为单击
private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
private static final int DOUBLE_TAP_MIN_TIME = ViewConfiguration.getDoubleTapMinTime();
//长按的时间为TAP_TIMEOUT+LONGPRESS_TIMEOUT
private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
// 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;
private final OnGestureListener mListener;
private OnDoubleTapListener mDoubleTapListener;
private OnContextClickListener mContextClickListener;
private boolean mStillDown;
private boolean mDeferConfirmSingleTap;
private boolean mInLongPress;
private boolean mInContextClick;
private boolean mAlwaysInTapRegion;
private boolean mAlwaysInBiggerTapRegion;
private boolean mIgnoreNextUpEvent;
private MotionEvent mCurrentDownEvent;
private MotionEvent mPreviousUpEvent;
//判定为双击事件的标记,在第二次按下时设置
private boolean mIsDoubleTapping;
private float mLastFocusX;
private float mLastFocusY;
private float mDownFocusX;
private float mDownFocusY;
private boolean mIsLongpressEnabled;
/**
* Determines speed during touch scrolling
*/
private VelocityTracker mVelocityTracker;
/**
* Consistency verifier for debugging purposes.
*/
private final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
InputEventConsistencyVerifier.isInstrumentationEnabled() ?
new InputEventConsistencyVerifier(this, 0) : null;
//内部接收事件的handler
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) {
if (!mStillDown) { //按下后立刻抬起,此时在TAP延时后,才判定为单击事件,
mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
} else { //还未抬起,但是也没有达到长按的事件判定,此时如果产生up事件,则认为是单击
mDeferConfirmSingleTap = true;
}
}
break;
default:
throw new RuntimeException("Unknown message " + msg); //never
}
}
}
//借用传入的handler的looper来构造自己的handler
public GestureDetector(Context context, OnGestureListener listener, Handler handler) {
if (handler != null) {
mHandler = new GestureHandler(handler);
} else {
mHandler = new GestureHandler();
}
mListener = listener;
//这地方怎么能这么写?三个类型的接口并不兼容啊
if (listener instanceof OnDoubleTapListener) {
setOnDoubleTapListener((OnDoubleTapListener) listener);
}
if (listener instanceof OnContextClickListener) {
setContextClickListener((OnContextClickListener) listener);
}
init(context);
}
public GestureDetector(Context context, OnGestureListener listener, Handler handler,
boolean unused) {
this(context, listener, handler);
}
private void init(Context context) {
if (mListener == null) {
throw new NullPointerException("OnGestureListener must not be null");
}
mIsLongpressEnabled = true; //默认允许长按
// Fallback to support pre-donuts releases
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;
}
public void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener) {
mDoubleTapListener = onDoubleTapListener;
}
public void setContextClickListener(OnContextClickListener onContextClickListener) {
mContextClickListener = onContextClickListener;
}
public void setIsLongpressEnabled(boolean isLongpressEnabled) {
mIsLongpressEnabled = isLongpressEnabled;
}
public boolean isLongpressEnabled() {
return mIsLongpressEnabled;
}
public boolean onTouchEvent(MotionEvent ev) {
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;
//求出多点的平均值,排除刚刚抬起的点
float sumX = 0, sumY = 0;
final int count = ev.getPointerCount();
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:
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);
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;
case MotionEvent.ACTION_DOWN:
if (mDoubleTapListener != null) {
boolean hadTapMessage = mHandler.hasMessages(TAP);
if (hadTapMessage) mHandler.removeMessages(TAP);
//存在之前记录过按下和抬起的事件,还存在TAP的定时器,说明还没有被判定为单击,
//而且第二次点击的时间还在判定双击的范围内
if ((mCurrentDownEvent != null) &&
(mPreviousUpEvent != null) &&
hadTapMessage &&
isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev))
{
//标记此后的滑动和抬起都是在一个双击事件中
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 {
//第一个按下事件,开启定时,以判断之后是否为单击或者双击
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; //是否仍然按下,down设为true,up设为false
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);
break;
case MotionEvent.ACTION_MOVE:
//忽略长按过程中的移动事件
if (mInLongPress || mInContextClick) {
break;
}
//计算移动的距离
final float scrollX = mLastFocusX - focusX;
final float scrollY = mLastFocusY - focusY;
if (mIsDoubleTapping) { //如果是双击事件中的移动,调用onDoubleTapEvent
handled |= mDoubleTapListener.onDoubleTapEvent(ev);
} else if (mAlwaysInTapRegion) { //不是双击事件,检查移动范围是否在Tap的限定范围内
final int deltaX = (int) (focusX - mDownFocusX);
final int deltaY = (int) (focusY - mDownFocusY);
int distance = (deltaX * deltaX) + (deltaY * deltaY);
if (distance > mTouchSlopSquare) { //移动距离超出mTouchSlopSquare的限制,判定为非TAP
handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); //回调onScroll事件
mLastFocusX = focusX;
mLastFocusY = focusY;
mAlwaysInTapRegion = false; //设置为非TAP
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)) {
//移动范围超过mTouchSlop的限制后,位移大于1才回调onScroll,提高效率,降低回调频率
handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
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
//如果是双击事件中的抬起,则调用onDoubleTapEvent
handled |= mDoubleTapListener.onDoubleTapEvent(ev);
} else if (mInLongPress) { //取消长按状态
mHandler.removeMessages(TAP); //达到长按状态,应该不可能还有TAP消息了吧
mInLongPress = false;
} else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) {
//一直在单击允许的区域内,则判定为一次点击抬起事件,回调onSingleTapUp
handled = mListener.onSingleTapUp(ev);
//如果从按下到抬起的时间超过了双击所需要的时间,但是没有超过长按所需要的时间,
//则确认为单击,并回调onSingleTapConfirmed
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);
//计算第一个点的滑动速度,在规定范围内则判定为fling,回调onFling
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;
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;
}
......
return handled;
}
/**
* Analyzes the given generic motion event and if applicable triggers the
* appropriate callbacks on the {@link OnGestureListener} supplied.
*
* @param ev The current motion event.
* @return true if the {@link OnGestureListener} consumed the event,
* else false.
*/
//作用不明,需要再研究
//非Touch Event才会触发onGenericMotionEvent
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)) {
mInContextClick = false;
mIgnoreNextUpEvent = true;
}
break;
}
return false;
}
private void cancel() {
mVelocityTracker.recycle();
mVelocityTracker = null;
mStillDown = false;
//以下即为cancelTaps的逻辑
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
mHandler.removeMessages(TAP);
mIsDoubleTapping = false;
//down事件设置为true,move大于一定程度设置为false,up中进行判定,表示是否为单纯的点击事件
mAlwaysInTapRegion = false;
mAlwaysInBiggerTapRegion = false;
mDeferConfirmSingleTap = false;
mInLongPress = false;
mInContextClick = false;
mIgnoreNextUpEvent = false;
}
private void cancelTaps() {
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
mHandler.removeMessages(TAP);
mIsDoubleTapping = false;
mAlwaysInTapRegion = false;
mAlwaysInBiggerTapRegion = false;
mDeferConfirmSingleTap = false;
mInLongPress = false;
mInContextClick = false;
mIgnoreNextUpEvent = false;
}
//第二次点击时调用,太快的双击和太慢的双击,以及两次点击之间的距离过大,都认为不是双击
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();
return (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare);
}
private void dispatchLongPress() {
mHandler.removeMessages(TAP); //产生长按时,不可能有还有TAP消息存在吧?
mDeferConfirmSingleTap = false; //取消单击的判定
mInLongPress = true;
mListener.onLongPress(mCurrentDownEvent); //回调长按
}
}
GestureDetector分析
最新推荐文章于 2021-08-18 09:48:00 发布