google 提供的API中,有个类,大家都很熟悉,GestureDetector。使用它,我们可以识别用户通常会用的手势。但是,这个类不支持多点触摸(可能 google认为没有人会在几个手指都在屏幕上的时候,使用手势吧~),不过,最近和朋友们一起做的一个App,的确用到了多点手势(主要是 onScroll和onFling两个手势),所以,我就把这个类拓展了一下,来实现让多个控件各自跟着一跟手指实现拖动和滑动的效果。  
  顺便说一下,大家应该都知道,在Android3.0以后,Android的触摸事件的分配机制和以前的版本是有区别的。从3.0开始,用户在不同控件上操作产生的touch消息不会相互干扰,touch消息会被分派到不同控件上的touchListener中处理。而  
在以前的版本中,所有的touch消息,都会被分排到第一个碰到屏幕的手指所操作的控件的touchListener中处理,也就是说,会出现这样一个矛盾的现象:  
  在界面上有A,B,C三个控件,然后,当你先用食指按住A,跟着又用中指和无名指(嘛,别的手指也行,不用在意中指还是无名指)按住B,C。当中指和无名指移动的时候,B和C都无法接收到这个ACTION_MOVE消息,而接收到消息的却是A。而在3.0以上版本中,并不存在这个问题。

使用以下的这个类,可以实现从2.2到3.2平台上手势识别的兼容。

package com.finger.utils;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
public class MultiTouchGestureDetector {@SuppressWarnings("unused")private static final String MYTAG = "Ray";public static final String CLASS_NAME = "MultiTouchGestureDetector";/*** 事件信息类 <br/>* 用来记录一个手势*/private class EventInfo {private MultiMotionEvent mCurrentDownEvent;    //当前的down事件private MultiMotionEvent mPreviousUpEvent;    //上一次up事件private boolean mStillDown;                    //当前手指是否还在屏幕上private boolean mInLongPress;                //当前事件是否属于长按手势private boolean mAlwaysInTapRegion;            //是否当前手指仅在小范围内移动,当手指仅在小范围内移动时,视为手指未曾移动过,不会触发onScroll手势private boolean mAlwaysInBiggerTapRegion;    //是否当前手指在较大范围内移动,仅当此值为true时,双击手势才能成立private boolean mIsDoubleTapping;            //当前手势,是否为双击手势private float mLastMotionY;                    //最后一次事件的X坐标private float mLastMotionX;                    //最后一次事件的Y坐标private EventInfo(MotionEvent e) {this(new MultiMotionEvent(e));}private EventInfo(MultiMotionEvent me) {mCurrentDownEvent = me;mStillDown = true;mInLongPress = false;mAlwaysInTapRegion = true;mAlwaysInBiggerTapRegion = true;mIsDoubleTapping = false;}//释放MotionEven对象,使系统能够继续使用它们public void recycle() {if (mCurrentDownEvent != null) {mCurrentDownEvent.recycle();mCurrentDownEvent = null;}if (mPreviousUpEvent != null) {mPreviousUpEvent.recycle();mPreviousUpEvent = null;}}@Overridepublic void finalize() {this.recycle();}}/*** 多点事件类 <br/>* 将一个多点事件拆分为多个单点事件,并方便获得事件的绝对坐标* <br/> 绝对坐标用以在界面中找到触点所在的控件* @author ray-ni*/public class MultiMotionEvent {private MotionEvent mEvent;private int mIndex;private MultiMotionEvent(MotionEvent e) {mEvent = e;mIndex = (e.getAction() & MotionEvent.ACTION_POINTER_ID_MASK) >> MotionEvent.ACTION_POINTER_ID_SHIFT;  //等效于 mEvent.getActionIndex();}private MultiMotionEvent(MotionEvent e, int idx) {mEvent = e;mIndex = idx;}// 行为public int getAction() {int action = mEvent.getAction() & MotionEvent.ACTION_MASK;    //等效于 mEvent.getActionMasked();switch (action) {case MotionEvent.ACTION_POINTER_DOWN:action = MotionEvent.ACTION_DOWN;break;case MotionEvent.ACTION_POINTER_UP:action = MotionEvent.ACTION_UP;break;}return action;}// 返回X的绝对坐标public float getX() {return mEvent.getX(mIndex) + mEvent.getRawX() - mEvent.getX();}// 返回Y的绝对坐标public float getY() {return mEvent.getY(mIndex) + mEvent.getRawY() - mEvent.getY();}// 事件发生的时间public long getEventTime() {return mEvent.getEventTime();}// 事件序号public int getIndex() {return mIndex;}// 事件IDpublic int getId() {return mEvent.getPointerId(mIndex);}// 释放事件对象,使系统能够继续使用public void recycle() {if (mEvent != null) {mEvent.recycle();mEvent = null;}}}// 多点手势监听器public interface MultiTouchGestureListener {// 手指触碰到屏幕,由一个 ACTION_DOWN触发boolean onDown(MultiMotionEvent e);// 确定一个press事件,强调手指按下的一段时间(TAP_TIMEOUT)内,手指未曾移动或抬起void onShowPress(MultiMotionEvent e);// 手指点击屏幕后离开,由 ACTION_UP引发,可以简单的理解为单击事件,即手指点击时间不长(未构成长按事件),也不曾移动过boolean onSingleTapUp(MultiMotionEvent e);// 长按,手指点下后一段时间(DOUBLE_TAP_TIMEOUT)内,不曾抬起或移动void onLongPress(MultiMotionEvent e);// 拖动,由ACTION_MOVE触发,手指地按下后,在屏幕上移动boolean onScroll(MultiMotionEvent e1, MultiMotionEvent e2, float distanceX, float distanceY);// 滑动,由ACTION_UP触发,手指按下并移动一段距离后,抬起时触发boolean onFling(MultiMotionEvent e1, MultiMotionEvent e2, float velocityX, float velocityY);}// 多点双击监听器public interface MultiTouchDoubleTapListener {// 单击事件确认,强调第一个单击事件发生后,一段时间内,未发生第二次单击事件,即确定不会触发双击事件boolean onSingleTapConfirmed(MultiMotionEvent e);// 双击事件, 由ACTION_DOWN触发,从第一次单击事件的DOWN事件开始的一段时间(DOUBLE_TAP_TIMEOUT)内结束(即手指),// 并且在第一次单击事件的UP时间开始后的一段时间内(DOUBLE_TAP_TIMEOUT)发生第二次单击事件,// 除此之外两者坐标间距小于定值(DOUBLE_TAP_SLAP)时,则触发双击事件boolean onDoubleTap(MultiMotionEvent e);// 双击事件,与onDoubleTap事件不同之处在于,构成双击的第二次点击的ACTION_DOWN,ACTION_MOVE和ACTION_UP都会触发该事件boolean onDoubleTapEvent(MultiMotionEvent e);}// 事件信息队列,队列的下标与MotionEvent的pointId对应private static List<EventInfo> sEventInfos = new ArrayList<EventInfo>(10);// 双击判断队列,这个队列中的元素等待双击超时的判断结果private static List<EventInfo> sEventForDoubleTap = new ArrayList<EventInfo>(5);// 指定大点击区域的大小(这个比较拗口),这个值主要用于帮助判断双击是否成立private int mBiggerTouchSlopSquare = 20 * 20;// 判断是否构成onScroll手势,当手指在这个范围内移动时,不触发onScroll手势private int mTouchSlopSquare;// 判断是否构成双击,只有两次点击的距离小于该值,才能构成双击手势private int mDoubleTapSlopSquare;// 最小滑动速度private int mMinimumFlingVelocity;// 最大滑动速度private int mMaximumFlingVelocity;// 长按阀值,当手指按下后,在该阀值的时间内,未移动超过mTouchSlopSquare的距离并未抬起,则长按手势触发private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();// showPress手势的触发阀值,当手指按下后,在该阀值的时间内,未移动超过mTouchSlopSquare的距离并未抬起,则showPress手势触发private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout();// 双击超时阀值,仅在两次双击事件的间隔(第一次单击的UP事件和第二次单击的DOWN事件)小于此阀值,双击事件才能成立private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();// 双击区域阀值,仅在两次双击事件的距离小于此阀值,双击事件才能成立private static final int DOUBLE_TAP_SLAP = 64;// GestureHandler所处理的Message的what属性可能为以下 常量:// showPress手势private static final int SHOW_PRESS = 1;// 长按手势private static final int LONG_PRESS = 2;// SingleTapConfirmed手势private static final int TAP_SINGLE = 3;// 双击手势private static final int TAP_DOUBLE = 4;// 手势处理器private final GestureHandler mHandler;// 手势监听器private final MultiTouchGestureListener mListener;// 双击监听器private MultiTouchDoubleTapListener mDoubleTapListener;// 长按允许阀值private boolean mIsLongpressEnabled;// 速度追踪器private VelocityTracker mVelocityTracker;private class GestureHandler extends Handler {GestureHandler() {super();}GestureHandler(Handler handler) {super(handler.getLooper());}@Overridepublic void handleMessage(Message msg) {int idx = (Integer) msg.obj;switch (msg.what) {case SHOW_PRESS: {if (idx >= sEventInfos.size()) {
//                    Log.w(MYTAG, CLASS_NAME + ":handleMessage, msg.what = SHOW_PRESS, idx=" + idx + ", while sEventInfos.size()="
//                            + sEventInfos.size());break;}EventInfo info = sEventInfos.get(idx);if (info == null) {
//                    Log.e(MYTAG, CLASS_NAME + ":handleMessage, msg.what = SHOW_PRESS, idx=" + idx + ", Info = null");break;}// 触发手势监听器的onShowPress事件mListener.onShowPress(info.mCurrentDownEvent);break;}case LONG_PRESS: {// Log.d(MYTAG, CLASS_NAME + ":trigger LONG_PRESS");if (idx >= sEventInfos.size()) {
//                    Log.w(MYTAG, CLASS_NAME + ":handleMessage, msg.what = LONG_PRESS, idx=" + idx + ", while sEventInfos.size()="
//                            + sEventInfos.size());break;}EventInfo info = sEventInfos.get(idx);if (info == null) {
//                    Log.e(MYTAG, CLASS_NAME + ":handleMessage, msg.what = LONG_PRESS, idx=" + idx + ", Info = null");break;}dispatchLongPress(info, idx);break;}case TAP_SINGLE: {// Log.d(MYTAG, CLASS_NAME + ":trriger TAP_SINGLE");// If the user's finger is still down, do not count it as a tapif (idx >= sEventInfos.size()) {
//                    Log.e(MYTAG, CLASS_NAME + ":handleMessage, msg.what = TAP_SINGLE, idx=" + idx + ", while sEventInfos.size()="
//                            + sEventInfos.size());break;}EventInfo info = sEventInfos.get(idx);if (info == null) {
//                    Log.e(MYTAG, CLASS_NAME + ":handleMessage, msg.what = TAP_SINGLE, idx=" + idx + ", Info = null");break;}if (mDoubleTapListener != null && !info.mStillDown) { //手指在双击超时的阀值内未离开屏幕进行第二次单击事件,则确定单击事件成立(不再触发双击事件)mDoubleTapListener.onSingleTapConfirmed(info.mCurrentDownEvent);}break;}case TAP_DOUBLE: {if (idx >= sEventForDoubleTap.size()) {
//                    Log.w(MYTAG, CLASS_NAME + ":handleMessage, msg.what = TAP_DOUBLE, idx=" + idx + ", while sEventForDoubleTap.size()="
//                            + sEventForDoubleTap.size());break;}EventInfo info = sEventForDoubleTap.get(idx);if (info == null) {
//                    Log.w(MYTAG, CLASS_NAME + ":handleMessage, msg.what = TAP_DOUBLE, idx=" + idx + ", Info = null");break;}sEventForDoubleTap.set(idx, null);// 这个没什么好做的,就是把队列中对应的元素清除而已break;}default:throw new RuntimeException("Unknown message " + msg); // never}}}/*** 触发长按事件* @param info* @param idx*/private void dispatchLongPress(EventInfo info, int idx) {mHandler.removeMessages(TAP_SINGLE, idx);//移除单击事件确认info.mInLongPress = true;mListener.onLongPress(info.mCurrentDownEvent);}/*** 构造器1* @param context* @param listener*/public MultiTouchGestureDetector(Context context, MultiTouchGestureListener listener) {this(context, listener, null);}/*** 构造器2* @param context* @param listener* @param handler*/public MultiTouchGestureDetector(Context context, MultiTouchGestureListener listener, Handler handler) {if (handler != null) {mHandler = new GestureHandler(handler);} else {mHandler = new GestureHandler();}mListener = listener;if (listener instanceof MultiTouchDoubleTapListener) {setOnDoubleTapListener((MultiTouchDoubleTapListener) listener);}init(context);}/*** 初始化识别器* @param context*/private void init(Context context) {if (mListener == null) {throw new NullPointerException("OnGestureListener must not be null");}mIsLongpressEnabled = true;int touchSlop, doubleTapSlop;if (context == null) {touchSlop = ViewConfiguration.getTouchSlop();doubleTapSlop = DOUBLE_TAP_SLAP;mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity();} else {//允许识别器在App中,使用偏好的设定final ViewConfiguration configuration = ViewConfiguration.get(context);touchSlop = configuration.getScaledTouchSlop();doubleTapSlop = configuration.getScaledDoubleTapSlop();mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity();}mTouchSlopSquare = touchSlop * touchSlop / 16;mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop;}/*** 设置双击监听器* @param onDoubleTapListener*/public void setOnDoubleTapListener(MultiTouchDoubleTapListener onDoubleTapListener) {mDoubleTapListener = onDoubleTapListener;}/*** 设置是否允许长按* @param isLongpressEnabled*/public void setIsLongpressEnabled(boolean isLongpressEnabled) {mIsLongpressEnabled = isLongpressEnabled;}/*** 判断是否允许长按* @return*/public boolean isLongpressEnabled() {return mIsLongpressEnabled;}/*** 判断当前事件是否为双击事件* <br/> 通过遍历sEventForDoubleTap来匹配是否存在能够构成双击事件的单击事件* @param e* @return*/private EventInfo checkForDoubleTap(MultiMotionEvent e) {if (sEventForDoubleTap.isEmpty()) {
//            Log.e(MYTAG, CLASS_NAME + ":checkForDoubleTap(), sEventForDoubleTap is empty !");return null;}for (int i = 0; i < sEventForDoubleTap.size(); i++) {EventInfo info = sEventForDoubleTap.get(i);if (info != null && isConsideredDoubleTap(info, e)) {sEventForDoubleTap.set(i, null);// 这个单击事件已经被消耗了,所以置为nullmHandler.removeMessages(TAP_DOUBLE, i);// 移除Handler内的为处理消息return info;}}return null;}/*** 判断当前按下事件是否能和指定的单击事件构成双击事件* * @param info* @param secondDown* @return*/private boolean isConsideredDoubleTap(EventInfo info, MultiMotionEvent secondDown) {if (!info.mAlwaysInBiggerTapRegion) { //如多第一次单击事件有过较大距离的移动,则无法构成双击事件return false;}if (secondDown.getEventTime() - info.mPreviousUpEvent.getEventTime() > DOUBLE_TAP_TIMEOUT) {//如果第一次单击的UP时间和第二次单击的down时间时间间隔大于DOUBLE_TAP_TIMEOUT,也无法构成双击事件return false;}int deltaX = (int) info.mCurrentDownEvent.getX() - (int) secondDown.getX();int deltaY = (int) info.mCurrentDownEvent.getY() - (int) secondDown.getY();return (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare);//最后判断两次单击事件的距离}/*** 将事件信息放入双击判断队列,并返回序号* * @param info* @return*/private int addIntoTheMinIndex(EventInfo info) {for (int i = 0; i < sEventForDoubleTap.size(); i++) {if (sEventForDoubleTap.get(i) == null) {sEventForDoubleTap.set(i, info);return i;}}sEventForDoubleTap.add(info);return sEventForDoubleTap.size() - 1;}/*** 从事件信息队列中移除指定序号的事件* * @param idx*/private void removeEventFromList(int id) {if (id > sEventInfos.size() || id < 0) {
//            Log.e(MYTAG, CLASS_NAME + ".removeEventFromList(), id=" + id + ", while sEventInfos.size() =" + sEventInfos.size());return;}sEventInfos.set(id, null);}/*** 向事件队列中添加新信息* * @param e*/private void addEventIntoList(EventInfo info) {int id = info.mCurrentDownEvent.getId();if (id < sEventInfos.size()) {
//            if (sEventInfos.get(id) != null)
//                Log.e(MYTAG, CLASS_NAME + ".addEventIntoList, info(" + id + ") has not set to null !");sEventInfos.set(info.mCurrentDownEvent.getId(), info);} else if (id == sEventInfos.size()) {sEventInfos.add(info);} else {
//            Log.e(MYTAG, CLASS_NAME + ".addEventIntoList, invalidata id !");}}public boolean onTouchEvent(MotionEvent ev) {if (mVelocityTracker == null) {mVelocityTracker = VelocityTracker.obtain();}mVelocityTracker.addMovement(ev);//把所有事件都添加到速度追踪器,为计算速度做准备boolean handled = false;final int action = ev.getAction(); //获取Action
//        int idx = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;//获取触摸事件的序号int idx = ev.getPointerId(ev.getActionIndex());//获取触摸事件的idswitch (action & MotionEvent.ACTION_MASK) {case MotionEvent.ACTION_DOWN:case MotionEvent.ACTION_POINTER_DOWN: {EventInfo info = new EventInfo(MotionEvent.obtain(ev));this.addEventIntoList(info);//将手势信息保存到队列中if (mDoubleTapListener != null) {//如果双击监听器不为nullif (mHandler.hasMessages(TAP_DOUBLE)) {MultiMotionEvent e = new MultiMotionEvent(ev);EventInfo origInfo = checkForDoubleTap(e);//检查是否构成双击事件if (origInfo != null) {info.mIsDoubleTapping = true;handled |= mDoubleTapListener.onDoubleTap(origInfo.mCurrentDownEvent);handled |= mDoubleTapListener.onDoubleTapEvent(e);}}if (!info.mIsDoubleTapping) {//当前事件不构成双击事件,那么发送延迟消息以判断onSingleTapConfirmed事件mHandler.sendMessageDelayed(mHandler.obtainMessage(TAP_SINGLE, idx), DOUBLE_TAP_TIMEOUT);// Log.d(MYTAG, CLASS_NAME + ": add TAP_SINGLE");}}// 记录X坐标和Y坐标info.mLastMotionX = info.mCurrentDownEvent.getX();info.mLastMotionY = info.mCurrentDownEvent.getY();if (mIsLongpressEnabled) {//允许长按mHandler.removeMessages(LONG_PRESS, idx);mHandler.sendMessageAtTime(mHandler.obtainMessage(LONG_PRESS, idx), info.mCurrentDownEvent.getEventTime() + TAP_TIMEOUT+ LONGPRESS_TIMEOUT);//延时消息以触发长按手势// Log.d(MYTAG, CLASS_NAME +// ":add LONG_PRESS to handler  for idx " + idx);}mHandler.sendMessageAtTime(mHandler.obtainMessage(SHOW_PRESS, idx), info.mCurrentDownEvent.getEventTime() + TAP_TIMEOUT);// 延时消息,触发showPress手势handled |= mListener.onDown(info.mCurrentDownEvent);//触发onDown()break;}case MotionEvent.ACTION_UP:case MotionEvent.ACTION_POINTER_UP: {MultiMotionEvent currentUpEvent = new MultiMotionEvent(ev);if (idx >= sEventInfos.size()) {
//                Log.e(MYTAG, CLASS_NAME + ":ACTION_POINTER_UP, idx=" + idx + ", while sEventInfos.size()=" + sEventInfos.size());break;}EventInfo info = sEventInfos.get(currentUpEvent.getId());if (info == null) {
//                Log.e(MYTAG, CLASS_NAME + ":ACTION_POINTER_UP, idx=" + idx + ", Info = null");break;}info.mStillDown = false;if (info.mIsDoubleTapping) { //处于双击状态,则触发onDoubleTapEvent事件handled |= mDoubleTapListener.onDoubleTapEvent(currentUpEvent);} else if (info.mInLongPress) {//处于长按状态mHandler.removeMessages(TAP_SINGLE, idx);//可以无视这行代码info.mInLongPress = false;} else if (info.mAlwaysInTapRegion) {//尚未移动过if (mHandler.hasMessages(TAP_SINGLE, idx)) {//还在双击的时间阀值内,所以要为双击判断做额外处理mHandler.removeMessages(TAP_SINGLE, idx);info.mPreviousUpEvent = new MultiMotionEvent(MotionEvent.obtain(ev));int index = this.addIntoTheMinIndex(info);// 把当前事件放入队列,等待双击的判断mHandler.sendMessageAtTime(mHandler.obtainMessage(TAP_DOUBLE, index), info.mCurrentDownEvent.getEventTime()+ DOUBLE_TAP_TIMEOUT); // 将双击超时判断添加到Handler// Log.d(MYTAG, CLASS_NAME + ": add TAP_DOUBLE");}handled = mListener.onSingleTapUp(currentUpEvent);//触发onSingleTapUp事件} else {// A fling must travel the minimum tap distancefinal VelocityTracker velocityTracker = mVelocityTracker;velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);//计算1秒钟内的滑动速度//获取X和Y方向的速度final float velocityX = velocityTracker.getXVelocity(idx);final float velocityY = velocityTracker.getYVelocity(idx);// Log.i(MYTAG, CLASS_NAME + ":ACTION_POINTER_UP, idx=" + idx +// ", vx=" + velocityX + ", vy=" + velocityY);// 触发滑动事件if ((Math.abs(velocityY) > mMinimumFlingVelocity) || (Math.abs(velocityX) > mMinimumFlingVelocity)) {handled = mListener.onFling(info.mCurrentDownEvent, currentUpEvent, velocityX, velocityY);}}// Hold the event we obtained above - listeners may have changed the// original.if (action == MotionEvent.ACTION_UP) {    //释放速度追踪器mVelocityTracker.recycle();mVelocityTracker = null;// Log.w(MYTAG, CLASS_NAME +// ":ACTION_POINTER_UP, mVelocityTracker.recycle()");}info.mIsDoubleTapping = false;// Log.d(MYTAG, CLASS_NAME + "remove LONG_PRESS");// 移除showPress和长按消息mHandler.removeMessages(SHOW_PRESS, idx);mHandler.removeMessages(LONG_PRESS, idx);removeEventFromList(currentUpEvent.getId());//手指离开,则从队列中删除手势信息break;}case MotionEvent.ACTION_MOVE:for (int rIdx = 0; rIdx < ev.getPointerCount(); rIdx++) {//因为无法确定当前发生移动的是哪个手指,所以遍历处理所有手指MultiMotionEvent e = new MultiMotionEvent(ev, rIdx);if (e.getId() >= sEventInfos.size()) {
//                    Log.e(MYTAG, CLASS_NAME + ":ACTION_MOVE, idx=" + rIdx + ", while sEventInfos.size()=" + sEventInfos.size());break;}EventInfo info = sEventInfos.get(e.getId());if (info == null) {
//                    Log.e(MYTAG, CLASS_NAME + ":ACTION_MOVE, idx=" + rIdx + ", Info = null");break;}if (info.mInLongPress) {    //长按,则不处理move事件break;}//当前坐标float x = e.getX();float y = e.getY();//距离上次事件移动的位置final float scrollX = x - info.mLastMotionX;final float scrollY = y - info.mLastMotionY;if (info.mIsDoubleTapping) {//双击事件handled |= mDoubleTapListener.onDoubleTapEvent(e);} else if (info.mAlwaysInTapRegion) {//该手势尚未移动过(移动的距离小于mTouchSlopSquare,视为未移动过)// 计算从落下到当前事件,移动的距离final int deltaX = (int) (x - info.mCurrentDownEvent.getX());final int deltaY = (int) (y - info.mCurrentDownEvent.getY());// Log.d(MYTAG, CLASS_NAME + "deltaX="+deltaX+";deltaY=" +// deltaX +"mTouchSlopSquare=" + mTouchSlopSquare);int distance = (deltaX * deltaX) + (deltaY * deltaY);if (distance > mTouchSlopSquare) {     // 移动距离超过mTouchSlopSquarehandled = mListener.onScroll(info.mCurrentDownEvent, e, scrollX, scrollY);info.mLastMotionX = e.getX();info.mLastMotionY = e.getY();info.mAlwaysInTapRegion = false;// Log.d(MYTAG, CLASS_NAME +// ":remove LONG_PRESS for idx" + rIdx +// ",mTouchSlopSquare("+mTouchSlopSquare+"), distance("+distance+")");// 清除onSingleTapConform,showPress,longPress三种消息int id = e.getId();mHandler.removeMessages(TAP_SINGLE, id);mHandler.removeMessages(SHOW_PRESS, id);mHandler.removeMessages(LONG_PRESS, id);}if (distance > mBiggerTouchSlopSquare) {//移动距离大于mBiggerTouchSlopSquare,则无法构成双击事件info.mAlwaysInBiggerTapRegion = false;}} else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {//之前已经移动过了handled = mListener.onScroll(info.mCurrentDownEvent, e, scrollX, scrollY);info.mLastMotionX = x;info.mLastMotionY = y;}}break;case MotionEvent.ACTION_CANCEL:cancel();//清理}return handled;}// 清理所有队列private void cancel() {mHandler.removeMessages(SHOW_PRESS);mHandler.removeMessages(LONG_PRESS);mHandler.removeMessages(TAP_SINGLE);mVelocityTracker.recycle();mVelocityTracker = null;sEventInfos.clear();sEventForDoubleTap.clear();}}

来自:源码天堂整理

转载于:https://www.cnblogs.com/suncoolcat/p/3331386.html

实现在Android 多点手势识别相关推荐

  1. 简述Android触摸屏手势识别

    很多时候,利用触摸屏的Fling.Scroll等Gesture(手势)操作来操作会使得应用程序的用户体验大大提升,比如用Scroll手势在 浏览器中滚屏,用Fling在阅读器中翻页等.在Android ...

  2. Android Gesture 手势识别使用实例 - Android - mobile - ITeye论坛

    Android Gesture 手势识别使用实例 - Android - mobile - ITeye论坛 主题:Android Gesture 手势识别使用实例 精华帖 (0) :: 良好帖 (0) ...

  3. MTK 驱动(65)---Android 多点触摸协议(Multi-touch Protocol)

    Android 多点触摸协议(Multi-touch Protocol) Android 多点触摸协议Multi-touch Protocol A协议 B协议 代码编写 Protocol A Prot ...

  4. Android多点触控详解

    本文转载自GcsSloop的 安卓自定义View进阶-多点触控详解 的文章 Android 多点触控详解,在前面的几篇文章中我们大致了解了 Android 中的事件处理流程和一些简单的处理方案,本次带 ...

  5. 使用RootTools实现对Android系统文件的读写

    使用RootTools实现对Android系统文件的读写  作者:          蒋东国  时间:     2016年11月2日 星期三  应用来源:     hqt APP(测试机型:华为荣耀4 ...

  6. 模拟Android多点触控

    Android多点触控 Android多点触控 多点触控实现思路 第一种adb shell input方式 第二种adb shell sendevent方式 多点触控实现思路   经过资料的查询,要在 ...

  7. Android多点触控揭秘

    本文原创,转载请注明:http://blog.csdn.net/cloudzfy1/article/details/6582707 Google 暑期大学生博客分享大赛 - 2011 Android ...

  8. Android多点触控技术

    1 简介 Android多点触控在本质上需要LCD驱动和程序本身设计上支持,目前市面上HTC.Motorola和Samsung等知名厂商只要使用电容屏触控原理的手机均可以支持多点触控Multitouc ...

  9. Android 多点触控消息捕获与处理

    1 简介 Android多点触控在本质上需要LCD驱动和程序本身设计上支持,目前市面上HTC.Motorola和Samsung等知名厂商只要使用电容屏触控原理的手机均可以支持多点触控Multitouc ...

最新文章

  1. AWS Artifact如何处理企业的法规遵从?
  2. 每日一皮:程序猿的读书历程,最后一本​必备​!
  3. 怎么转换html文件为mp3,如何把音频转换成mp3_音频文件怎么转mp3格式-系统城
  4. 干点大事!“覆盖25万人的AI资源对接平台”发布,找人、找技术不再难!
  5. idea 启动选择profiles_玩转SpringBoot 2 之项目启动篇
  6. 计算机错误英语,计算机启动提示:引导时解释英语错误消息的含义
  7. linux6 安装oracle11g
  8. SpringBoot整合WebSocket实现前后端互推消息
  9. android BaseAdapter优化
  10. Security+ 学习笔记40 网络安全设备
  11. 大一c语言期末课程总结,大一下学期期末自我总结
  12. vue token过期如何处理_超市货架上摆放有过期食品如何定性处理?总局这样答复...
  13. sublime text3怎么运行python代码_Sublime Text3配置在可交互环境下运行python快捷键
  14. 在树莓派3上使用红外遥控器控制libreELEC和Raspbian系统
  15. echarts如何在柱状图上显示百分比
  16. 互联网运营之道读书笔记
  17. python中关系运算符惰性求值_python对象属性惰性取值
  18. live555 官方网站源码下载地址
  19. 神州战神电脑关闭触摸板
  20. Python BeautifulSoup简介

热门文章

  1. jquery获取iframe里的js事件
  2. python语言分数等级转化_如何把分数转化成等级
  3. c语言头文件_C语言学习之头文件的原理和使用方法
  4. 如何在Linux上部署Jenkins
  5. MapReduce之OutputFormat理解
  6. (09)FPGA设计流程
  7. (66)FPGA面试题-为parallel encoder编写Verilog代码,实现MUX4_1
  8. (16)FPGA面试题MOORE 与 MEELEY状态机
  9. (7)FPGA面试题Latch和Register区别
  10. html5语言在tr下下拉列表,5-HTML列表, table表格标签及其属性-Go语言中文社区