天天看點

Android 多點手勢識别詳解

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平台上手勢識别的相容。

代碼區:

packagecom.finger.utils;

importjava.util.ArrayList;

importjava.util.List;

importandroid.content.Context;

importandroid.os.Handler;

importandroid.os.Message;

importandroid.view.MotionEvent;

importandroid.view.VelocityTracker;

importandroid.view.ViewConfiguration;

publicclassMultiTouchGestureDetector {

    @SuppressWarnings("unused")

    privatestaticfinalString MYTAG ="Ray";

    publicstaticfinalString CLASS_NAME ="MultiTouchGestureDetector";

    /**

     * 事件資訊類 <br/>

     * 用來記錄一個手勢

     */

    privateclassEventInfo {

        privateMultiMotionEvent mCurrentDownEvent;   //目前的down事件

        privateMultiMotionEvent mPreviousUpEvent;   //上一次up事件

        privatebooleanmStillDown;                   //目前手指是否還在螢幕上

        privatebooleanmInLongPress;               //目前事件是否屬于長按手勢

        privatebooleanmAlwaysInTapRegion;           //是否目前手指僅在小範圍内移動,當手指僅在小範圍内移動時,視為手指未曾移動過,不會觸發onScroll手勢

        privatebooleanmAlwaysInBiggerTapRegion;   //是否目前手指在較大範圍内移動,僅當此值為true時,輕按兩下手勢才能成立

        privatebooleanmIsDoubleTapping;           //目前手勢,是否為輕按兩下手勢

        privatefloatmLastMotionY;                   //最後一次事件的X坐标

        privatefloatmLastMotionX;                   //最後一次事件的Y坐标

        privateEventInfo(MotionEvent e) {

            this(newMultiMotionEvent(e));

        }

        privateEventInfo(MultiMotionEvent me) {

            mCurrentDownEvent = me;

            mStillDown =true;

            mInLongPress =false;

            mAlwaysInTapRegion =true;

            mAlwaysInBiggerTapRegion =true;

            mIsDoubleTapping =false;

        //釋放MotionEven對象,使系統能夠繼續使用它們

        publicvoidrecycle() {

            if(mCurrentDownEvent !=null) {

                mCurrentDownEvent.recycle();

                mCurrentDownEvent =null;

            }

            if(mPreviousUpEvent !=null) {

                mPreviousUpEvent.recycle();

                mPreviousUpEvent =null;

        @Override

        publicvoidfinalize() {

            this.recycle();

    }

     * 多點事件類 <br/>

     * 将一個多點事件拆分為多個單點事件,并友善獲得事件的絕對坐标

     * <br/> 絕對坐标用以在界面中找到觸點所在的控件

     * @author ray-ni

    publicclassMultiMotionEvent {

        privateMotionEvent mEvent;

        privateintmIndex;

        privateMultiMotionEvent(MotionEvent e) {

            mEvent = e;

            mIndex = (e.getAction() &

MotionEvent.ACTION_POINTER_ID_MASK) >>

MotionEvent.ACTION_POINTER_ID_SHIFT; //等效于 mEvent.getActionIndex();

        privateMultiMotionEvent(MotionEvent e,intidx) {

            mIndex = idx;

        // 行為

        publicintgetAction() {

            intaction = mEvent.getAction() & MotionEvent.ACTION_MASK;   //等效于 mEvent.getActionMasked();

            switch(action) {

            caseMotionEvent.ACTION_POINTER_DOWN:

                action = MotionEvent.ACTION_DOWN;

                break;

            caseMotionEvent.ACTION_POINTER_UP:

                action = MotionEvent.ACTION_UP;

            returnaction;

        // 傳回X的絕對坐标

        publicfloatgetX() {

            returnmEvent.getX(mIndex) + mEvent.getRawX() - mEvent.getX();

        // 傳回Y的絕對坐标

        publicfloatgetY() {

            returnmEvent.getY(mIndex) + mEvent.getRawY() - mEvent.getY();

        // 事件發生的時間

        publiclonggetEventTime() {

            returnmEvent.getEventTime();

        // 事件序号

        publicintgetIndex() {

            returnmIndex;

        // 事件ID

        publicintgetId() {

            returnmEvent.getPointerId(mIndex);

        // 釋放事件對象,使系統能夠繼續使用

            if(mEvent !=null) {

                mEvent.recycle();

                mEvent =null;

    // 多點手勢監聽器

    publicinterfaceMultiTouchGestureListener {

        // 手指觸碰到螢幕,由一個 ACTION_DOWN觸發

        booleanonDown(MultiMotionEvent e);

        // 确定一個press事件,強調手指按下的一段時間(TAP_TIMEOUT)内,手指未曾移動或擡起

        voidonShowPress(MultiMotionEvent e);

        // 手指點選螢幕後離開,由 ACTION_UP引發,可以簡單的了解為單擊事件,即手指點選時間不長(未構成長按事件),也不曾移動過

        booleanonSingleTapUp(MultiMotionEvent e);

        // 長按,手指點下後一段時間(DOUBLE_TAP_TIMEOUT)内,不曾擡起或移動

        voidonLongPress(MultiMotionEvent e);

        // 拖動,由ACTION_MOVE觸發,手指地按下後,在螢幕上移動

        booleanonScroll(MultiMotionEvent e1, MultiMotionEvent e2,floatdistanceX,floatdistanceY);

        // 滑動,由ACTION_UP觸發,手指按下并移動一段距離後,擡起時觸發

        booleanonFling(MultiMotionEvent e1, MultiMotionEvent e2,floatvelocityX,floatvelocityY);

    // 多點輕按兩下監聽器

    publicinterfaceMultiTouchDoubleTapListener {

        // 單擊事件确認,強調第一個單擊事件發生後,一段時間内,未發生第二次單擊事件,即确定不會觸發輕按兩下事件

        booleanonSingleTapConfirmed(MultiMotionEvent e);

        // 輕按兩下事件, 由ACTION_DOWN觸發,從第一次單擊事件的DOWN事件開始的一段時間(DOUBLE_TAP_TIMEOUT)内結束(即手指),

        // 并且在第一次單擊事件的UP時間開始後的一段時間内(DOUBLE_TAP_TIMEOUT)發生第二次單擊事件,

        // 除此之外兩者坐标間距小于定值(DOUBLE_TAP_SLAP)時,則觸發輕按兩下事件

        booleanonDoubleTap(MultiMotionEvent e);

        // 輕按兩下事件,與onDoubleTap事件不同之處在于,構成輕按兩下的第二次點選的ACTION_DOWN,ACTION_MOVE和ACTION_UP都會觸發該事件

        booleanonDoubleTapEvent(MultiMotionEvent e);

    // 事件資訊隊列,隊列的下标與MotionEvent的pointId對應

    privatestaticList<EventInfo> sEventInfos =newArrayList<EventInfo>(10);

    // 輕按兩下判斷隊列,這個隊列中的元素等待輕按兩下逾時的判斷結果

    privatestaticList<EventInfo> sEventForDoubleTap =newArrayList<EventInfo>(5);

    // 指定大點選區域的大小(這個比較拗口),這個值主要用于幫助判斷輕按兩下是否成立

    privateintmBiggerTouchSlopSquare =20*20;

    // 判斷是否構成onScroll手勢,當手指在這個範圍内移動時,不觸發onScroll手勢

    privateintmTouchSlopSquare;

    // 判斷是否構成輕按兩下,隻有兩次點選的距離小于該值,才能構成輕按兩下手勢

    privateintmDoubleTapSlopSquare;

    // 最小滑動速度

    privateintmMinimumFlingVelocity;

    // 最大滑動速度

    privateintmMaximumFlingVelocity;

    // 長按閥值,當手指按下後,在該閥值的時間内,未移動超過mTouchSlopSquare的距離并未擡起,則長按手勢觸發

    privatestaticfinalintLONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();

    // showPress手勢的觸發閥值,當手指按下後,在該閥值的時間内,未移動超過mTouchSlopSquare的距離并未擡起,則showPress手勢觸發

    privatestaticfinalintTAP_TIMEOUT = ViewConfiguration.getTapTimeout();

    // 輕按兩下逾時閥值,僅在兩次輕按兩下事件的間隔(第一次單擊的UP事件和第二次單擊的DOWN事件)小于此閥值,輕按兩下事件才能成立

    privatestaticfinalintDOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();

    // 輕按兩下區域閥值,僅在兩次輕按兩下事件的距離小于此閥值,輕按兩下事件才能成立

    privatestaticfinalintDOUBLE_TAP_SLAP =64;

    // GestureHandler所處理的Message的what屬性可能為以下 常量:

    // showPress手勢

    privatestaticfinalintSHOW_PRESS =1;

    // 長按手勢

    privatestaticfinalintLONG_PRESS =2;

    // SingleTapConfirmed手勢

    privatestaticfinalintTAP_SINGLE =3;

    // 輕按兩下手勢

    privatestaticfinalintTAP_DOUBLE =4;

    // 手勢處理器

    privatefinalGestureHandler mHandler;

    // 手勢監聽器

    privatefinalMultiTouchGestureListener mListener;

    // 輕按兩下監聽器

    privateMultiTouchDoubleTapListener mDoubleTapListener;

    // 長按允許閥值

    privatebooleanmIsLongpressEnabled;

    // 速度追蹤器

    privateVelocityTracker mVelocityTracker;

    privateclassGestureHandlerextendsHandler {

        GestureHandler() {

            super();

        GestureHandler(Handler handler) {

            super(handler.getLooper());

        publicvoidhandleMessage(Message msg) {

            intidx = (Integer) msg.obj;

            switch(msg.what) {

            caseSHOW_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");

                // 觸發手勢監聽器的onShowPress事件

                mListener.onShowPress(info.mCurrentDownEvent);

            caseLONG_PRESS: {

                // Log.d(MYTAG, CLASS_NAME + ":trigger LONG_PRESS");

msg.what = LONG_PRESS, idx=" + idx + ", while sEventInfos.size()="

//                    Log.e(MYTAG, CLASS_NAME + ":handleMessage, msg.what = LONG_PRESS, idx=" + idx + ", Info = null");

                dispatchLongPress(info, idx);

            caseTAP_SINGLE: {

                // Log.d(MYTAG, CLASS_NAME + ":trriger TAP_SINGLE");

                // If the user's finger is still down, do not count it as a tap

//                    Log.e(MYTAG, CLASS_NAME + ":handleMessage,

msg.what = TAP_SINGLE, idx=" + idx + ", while sEventInfos.size()="

//                    Log.e(MYTAG, CLASS_NAME + ":handleMessage, msg.what = TAP_SINGLE, idx=" + idx + ", Info = null");

                if(mDoubleTapListener !=null&& !info.mStillDown) {//手指在輕按兩下逾時的閥值内未離開螢幕進行第二次單擊事件,則确定單擊事件成立(不再觸發輕按兩下事件)

                    mDoubleTapListener.onSingleTapConfirmed(info.mCurrentDownEvent);

            caseTAP_DOUBLE: {

                if(idx >= sEventForDoubleTap.size()) {

msg.what = TAP_DOUBLE, idx=" + idx + ", while

sEventForDoubleTap.size()="

//                            + sEventForDoubleTap.size());

                EventInfo info = sEventForDoubleTap.get(idx);

//                    Log.w(MYTAG, CLASS_NAME + ":handleMessage, msg.what = TAP_DOUBLE, idx=" + idx + ", Info = null");

                sEventForDoubleTap.set(idx,null);// 這個沒什麼好做的,就是把隊列中對應的元素清除而已

            default:

                thrownewRuntimeException("Unknown message "+ msg);// never

     * 觸發長按事件

     * @param info

     * @param idx

    privatevoiddispatchLongPress(EventInfo info,intidx) {

        mHandler.removeMessages(TAP_SINGLE, idx);//移除單擊事件确認

        info.mInLongPress =true;

        mListener.onLongPress(info.mCurrentDownEvent);

     /**

     * 構造器1

     * @param context

     * @param listener

    publicMultiTouchGestureDetector(Context context, MultiTouchGestureListener listener) {

        this(context, listener,null);

     * 構造器2

     * @param handler

    publicMultiTouchGestureDetector(Context context, MultiTouchGestureListener listener, Handler handler) {

        if(handler !=null) {

            mHandler =newGestureHandler(handler);

        }else{

            mHandler =newGestureHandler();

        mListener = listener;

        if(listenerinstanceofMultiTouchDoubleTapListener) {

            setOnDoubleTapListener((MultiTouchDoubleTapListener) listener);

        init(context);

     * 初始化識别器

    privatevoidinit(Context context) {

        if(mListener ==null) {

            thrownewNullPointerException("OnGestureListener must not be null");

        mIsLongpressEnabled =true;

        inttouchSlop, doubleTapSlop;

        if(context ==null) {

            touchSlop = ViewConfiguration.getTouchSlop();

            doubleTapSlop = DOUBLE_TAP_SLAP;

            mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();

            mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity();

        }else{//允許識别器在App中,使用偏好的設定

            finalViewConfiguration 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

    publicvoidsetOnDoubleTapListener(MultiTouchDoubleTapListener onDoubleTapListener) {

        mDoubleTapListener = onDoubleTapListener;

     * 設定是否允許長按

     * @param isLongpressEnabled

    publicvoidsetIsLongpressEnabled(booleanisLongpressEnabled) {

        mIsLongpressEnabled = isLongpressEnabled;

     * 判斷是否允許長按

     * @return

    publicbooleanisLongpressEnabled() {

        returnmIsLongpressEnabled;

     * 判斷目前事件是否為輕按兩下事件

     * <br/> 通過周遊sEventForDoubleTap來比對是否存在能夠構成輕按兩下事件的單擊事件

     * @param e

    privateEventInfo checkForDoubleTap(MultiMotionEvent e) {

        if(sEventForDoubleTap.isEmpty()) {

//            Log.e(MYTAG, CLASS_NAME + ":checkForDoubleTap(), sEventForDoubleTap is empty !");

            returnnull;

        for(inti =0; i < sEventForDoubleTap.size(); i++) {

            EventInfo info = sEventForDoubleTap.get(i);

            if(info !=null&& isConsideredDoubleTap(info, e)) {

                sEventForDoubleTap.set(i,null);// 這個單擊事件已經被消耗了,是以置為null

                mHandler.removeMessages(TAP_DOUBLE, i);// 移除Handler内的為處理消息

                returninfo;

        returnnull;

     * 判斷目前按下事件是否能和指定的單擊事件構成輕按兩下事件

     *

     * @param secondDown

    privatebooleanisConsideredDoubleTap(EventInfo info, MultiMotionEvent secondDown) {

        if(!info.mAlwaysInBiggerTapRegion) {//如多第一次單擊事件有過較大距離的移動,則無法構成輕按兩下事件

            returnfalse;

        if(secondDown.getEventTime() - info.mPreviousUpEvent.getEventTime() > DOUBLE_TAP_TIMEOUT) {

            //如果第一次單擊的UP時間和第二次單擊的down時間時間間隔大于DOUBLE_TAP_TIMEOUT,也無法構成輕按兩下事件

        intdeltaX = (int) info.mCurrentDownEvent.getX() - (int) secondDown.getX();

        intdeltaY = (int) info.mCurrentDownEvent.getY() - (int) secondDown.getY();

        return(deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare);//最後判斷兩次單擊事件的距離

     * 将事件資訊放入輕按兩下判斷隊列,并傳回序号

    privateintaddIntoTheMinIndex(EventInfo info) {

            if(sEventForDoubleTap.get(i) ==null) {

                sEventForDoubleTap.set(i, info);

                returni;

        sEventForDoubleTap.add(info);

        returnsEventForDoubleTap.size() -1;

     * 從事件資訊隊列中移除指定序号的事件

    privatevoidremoveEventFromList(intid) {

        if(id > sEventInfos.size() || id <0) {

//            Log.e(MYTAG, CLASS_NAME + ".removeEventFromList(), id=" +

id + ", while sEventInfos.size() =" + sEventInfos.size());

            return;

        sEventInfos.set(id,null);

     * 向事件隊列中添加新資訊

    privatevoidaddEventIntoList(EventInfo info) {

        intid = 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);

        }elseif(id == sEventInfos.size()) {

            sEventInfos.add(info);

//            Log.e(MYTAG, CLASS_NAME + ".addEventIntoList, invalidata id !");

    publicbooleanonTouchEvent(MotionEvent ev) {

        if(mVelocityTracker ==null) {

            mVelocityTracker = VelocityTracker.obtain();

        mVelocityTracker.addMovement(ev);//把所有事件都添加到速度追蹤器,為計算速度做準備

        booleanhandled =false;

        finalintaction = ev.getAction();//擷取Action

//        int idx = (action & MotionEvent.ACTION_POINTER_INDEX_MASK)

>> MotionEvent.ACTION_POINTER_INDEX_SHIFT;//擷取觸摸事件的序号

        intidx = ev.getPointerId(ev.getActionIndex());//擷取觸摸事件的id

        switch(action & MotionEvent.ACTION_MASK) {

        caseMotionEvent.ACTION_DOWN:

        caseMotionEvent.ACTION_POINTER_DOWN: {

            EventInfo info =newEventInfo(MotionEvent.obtain(ev));

            this.addEventIntoList(info);//将手勢資訊儲存到隊列中

            if(mDoubleTapListener !=null) {//如果輕按兩下監聽器不為null

                if(mHandler.hasMessages(TAP_DOUBLE)) {

                    MultiMotionEvent e =newMultiMotionEvent(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;

        caseMotionEvent.ACTION_UP:

        caseMotionEvent.ACTION_POINTER_UP: {

            MultiMotionEvent currentUpEvent =newMultiMotionEvent(ev);

            if(idx >= sEventInfos.size()) {

//                Log.e(MYTAG, CLASS_NAME + ":ACTION_POINTER_UP, idx=" +

idx + ", while sEventInfos.size()=" + sEventInfos.size());

            EventInfo info = sEventInfos.get(currentUpEvent.getId());

            if(info ==null) {

//                Log.e(MYTAG, CLASS_NAME + ":ACTION_POINTER_UP, idx=" + idx + ", Info = null");

            info.mStillDown =false;

            if(info.mIsDoubleTapping) {//處于輕按兩下狀态,則觸發onDoubleTapEvent事件

                handled |= mDoubleTapListener.onDoubleTapEvent(currentUpEvent);

            }elseif(info.mInLongPress) {//處于長按狀态

                mHandler.removeMessages(TAP_SINGLE, idx);//可以無視這行代碼

                info.mInLongPress =false;

            }elseif(info.mAlwaysInTapRegion) {//尚未移動過

                if(mHandler.hasMessages(TAP_SINGLE, idx)) {//還在輕按兩下的時間閥值内,是以要為輕按兩下判斷做額外處理

                    mHandler.removeMessages(TAP_SINGLE, idx);

                    info.mPreviousUpEvent =newMultiMotionEvent(MotionEvent.obtain(ev));

                    intindex =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 distance

                finalVelocityTracker velocityTracker = mVelocityTracker;

                velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);//計算1秒鐘内的滑動速度

                //擷取X和Y方向的速度

                finalfloatvelocityX = velocityTracker.getXVelocity(idx);

                finalfloatvelocityY = 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());//手指離開,則從隊列中删除手勢資訊

        caseMotionEvent.ACTION_MOVE:

            for(intrIdx =0; rIdx < ev.getPointerCount(); rIdx++) {//因為無法确定目前發生移動的是哪個手指,是以周遊處理所有手指

                MultiMotionEvent e =newMultiMotionEvent(ev, rIdx);

                if(e.getId() >= sEventInfos.size()) {

//                    Log.e(MYTAG, CLASS_NAME + ":ACTION_MOVE, idx=" +

rIdx + ", while sEventInfos.size()=" + sEventInfos.size());

                EventInfo info = sEventInfos.get(e.getId());

//                    Log.e(MYTAG, CLASS_NAME + ":ACTION_MOVE, idx=" + rIdx + ", Info = null");

                if(info.mInLongPress) {   //長按,則不處理move事件

                //目前坐标

                floatx = e.getX();

                floaty = e.getY();

                //距離上次事件移動的位置

                finalfloatscrollX = x - info.mLastMotionX;

                finalfloatscrollY = y - info.mLastMotionY;

                if(info.mIsDoubleTapping) {//輕按兩下事件

                    handled |= mDoubleTapListener.onDoubleTapEvent(e);

                }elseif(info.mAlwaysInTapRegion) {//該手勢尚未移動過(移動的距離小于mTouchSlopSquare,視為未移動過)

                    // 計算從落下到目前事件,移動的距離

                    finalintdeltaX = (int) (x - info.mCurrentDownEvent.getX());

                    finalintdeltaY = (int) (y - info.mCurrentDownEvent.getY());

                    // Log.d(MYTAG, CLASS_NAME + "deltaX="+deltaX+";deltaY=" +

                    // deltaX +"mTouchSlopSquare=" + mTouchSlopSquare);

                    intdistance = (deltaX * deltaX) + (deltaY * deltaY);

                    if(distance > mTouchSlopSquare) {    // 移動距離超過mTouchSlopSquare

                        handled = 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三種消息

                        intid = e.getId();

                        mHandler.removeMessages(TAP_SINGLE, id);

                        mHandler.removeMessages(SHOW_PRESS, id);

                        mHandler.removeMessages(LONG_PRESS, id);

                    if(distance > mBiggerTouchSlopSquare) {//移動距離大于mBiggerTouchSlopSquare,則無法構成輕按兩下事件

                        info.mAlwaysInBiggerTapRegion =false;

                }elseif((Math.abs(scrollX) >=1) || (Math.abs(scrollY) >=1)) {//之前已經移動過了

                    handled = mListener.onScroll(info.mCurrentDownEvent, e, scrollX, scrollY);

                    info.mLastMotionX = x;

                    info.mLastMotionY = y;

        caseMotionEvent.ACTION_CANCEL:

            cancel();//清理

        returnhandled;

    // 清理所有隊列

    privatevoidcancel() {

        mHandler.removeMessages(SHOW_PRESS);

        mHandler.removeMessages(LONG_PRESS);

        mHandler.removeMessages(TAP_SINGLE);

        mVelocityTracker.recycle();

        mVelocityTracker =null;

        sEventInfos.clear();

        sEventForDoubleTap.clear();

}