Android GestureDetector 源碼分析

前言

工作這段時(shí)間以來(lái),陸陸續(xù)續(xù)看了一些類的源碼裆熙,但是都沒(méi)有詳細(xì)的記錄下來(lái)端礼,只是在筆記上記錄了一些功能性的代碼,導(dǎo)致一段時(shí)間之后入录,完完全全忘記了蛤奥。所以今天打算回顧梳理系統(tǒng)里的手勢(shì)判斷類 GestureDetector 。由于我們的項(xiàng)目是一個(gè)拍照類美化的 App纷跛,所以在之前的版本需求里喻括,產(chǎn)品提了一個(gè)在預(yù)覽頁(yè)里加上 A/B 對(duì)比圖的需求。這里的 A贫奠、B 指的是原圖和添加了濾鏡之后的圖片唬血,拍照進(jìn)入預(yù)覽頁(yè)顯示原圖A望蜡,但是長(zhǎng)按一小段時(shí)間之后顯示加了濾鏡的圖片B。當(dāng)時(shí)第一時(shí)間想到的就是使用 GestureDetector 拷恨,但是預(yù)覽頁(yè)的自定義 View 已經(jīng)有了自己的事件攔截處理的邏輯脖律,不想加上 GestureDetector 使代碼更為復(fù)雜,所以便萌生了參考 GestureDetector 的想法腕侄。

概述

當(dāng)我們的手指接觸到屏幕的時(shí)候小泉,會(huì)產(chǎn)生許多事件,例如 down,move,up,scroll,fling 等等冕杠,一般情況下微姊, 如果我們需要捕獲這些事件做一些簡(jiǎn)單的出來(lái),只需要重寫 View 的 onTouchEvent(event) 即可分预,但是如果是一些比較復(fù)雜的手勢(shì)兢交,比如單擊,雙擊笼痹,長(zhǎng)按配喳,左右滑動(dòng),快速滑動(dòng)凳干,就需要我們?nèi)プ鲆恍╊~外的判斷邏輯了晴裹,所以系統(tǒng)給我們提供了 GestureDetector ,以此簡(jiǎn)化我們的開(kāi)發(fā)工作救赐。接下來(lái)看一下系統(tǒng)提供的 GestureDetector .OnGestureListener 接口涧团,以下接口方法中,返回 True 代碼事件被消費(fèi)了净响。源碼分析基于android- 25!


  • onDown(MotionEvent e)

    當(dāng)我們觸碰到屏幕 Down 事件發(fā)生時(shí)少欺,會(huì)立即回調(diào)此方法。

  • onShowPress(MotionEvent e)

    用戶已經(jīng)觸發(fā)了 Down 事件馋贤,但是并沒(méi)有觸發(fā) Move 或者 Up 事件赞别。這方法通常是作為一種反饋,讓用戶知道他手指按下屏幕這一動(dòng)作已經(jīng)被識(shí)別了配乓。

  • onSingleTapUp(MotionEvent e)

    當(dāng)一個(gè)以 Up 事件結(jié)束的單擊發(fā)生時(shí)仿滔,回調(diào)此方法。

  • onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)

    當(dāng)一個(gè)滑動(dòng)事件發(fā)生時(shí)回調(diào)此方法犹芹,其中 e1 代表滑動(dòng)開(kāi)始的第一個(gè) Down 事件崎页,e2 代表當(dāng)前手指正在滑動(dòng)的 Move 事件,distanceX 代表在 X 軸上前一次滑動(dòng)和當(dāng)次滑動(dòng)的差值量腰埂,注意飒焦,distanceX 并不是 e1 和 e2 在 X 方向上的差值量。distanceY 同 distanceX 類似,只不過(guò)代表的是 Y 軸牺荠。

  • onLongPress(MotionEvent e)

    長(zhǎng)按事件發(fā)生時(shí)回調(diào)此方法

  • onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)

    快速滑動(dòng)發(fā)生時(shí)回調(diào)此方法翁巍,其中 e1 代表觸發(fā)這個(gè)動(dòng)作的 Down 事件,e2 代表這個(gè)動(dòng)作結(jié)束手指抬起的 Up 事件休雌, velocityX 代表在 X 方向上的滑動(dòng)速度灶壶,velocityY 代表在 Y 方向上的滑動(dòng)速度,數(shù)值單位均為 pixels /s 杈曲,即每秒劃過(guò)多少個(gè)像素驰凛。

    在 GestureDetector 類中,我們還發(fā)現(xiàn)一個(gè)用于確認(rèn)單擊以及雙擊的接口 GestureDetector .OnDoubleTapListener


  • onSingleTapConfirmed(MotionEvent e)

    當(dāng)確認(rèn)一個(gè)單擊事件發(fā)生時(shí)回調(diào)此方法担扑,也就是說(shuō)恰响,手勢(shì)判斷器判斷到這是一次單擊事件,用戶不會(huì)再次點(diǎn)擊屏幕魁亦。

  • onDoubleTap(MotionEvent e)

    當(dāng)雙擊事件發(fā)生時(shí)回調(diào)此方法

  • onDoubleTapEvent(MotionEvent e)

    雙擊事件發(fā)生過(guò)程中都會(huì)回調(diào)此方法渔隶,包括 Down 羔挡、Move 洁奈、Up 事件。

源碼分析

1.構(gòu)造方法

public GestureDetector(OnGestureListener listener) {
        this(null, listener, null);
    }
public GestureDetector(Context context, OnGestureListener listener) {
        this(context, listener, null);
    }
public GestureDetector(Context context, OnGestureListener listener, Handler handler) {
        if (handler != null) {
            //初始化Handler绞灼,用于處理延時(shí)消息
            mHandler = new GestureHandler(handler);
        } else {
            mHandler = new GestureHandler();
        }
        //設(shè)置回調(diào)監(jiān)聽(tīng)器
        mListener = listener;
        if (listener instanceof OnDoubleTapListener) {
            setOnDoubleTapListener((OnDoubleTapListener) listener);
        }
        if (listener instanceof OnContextClickListener) {
            setContextClickListener((OnContextClickListener) listener);
        }
        init(context);
    }
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);
            //滑動(dòng)的時(shí)候利术,滑動(dòng)數(shù)值大于這個(gè)值才認(rèn)為滑動(dòng)開(kāi)始
            touchSlop = configuration.getScaledTouchSlop();
            doubleTapTouchSlop = configuration.getScaledDoubleTapTouchSlop();
            //雙擊位置之間的最大距離,大于這個(gè)數(shù)值不認(rèn)為是雙擊
            doubleTapSlop = configuration.getScaledDoubleTapSlop();
            //手指拋的最小速度
            mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
           //手指拋的最大速度
            mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity();
        }
        //計(jì)算平方低矮,后面用到
        mTouchSlopSquare = touchSlop * touchSlop;
        mDoubleTapTouchSlopSquare = doubleTapTouchSlop * doubleTapTouchSlop;
        mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop;
    }

從以上可以看出印叁,構(gòu)造 GestureDetector 的過(guò)程中,主要做的就是初始化一些變量军掂,并且獲取一些數(shù)值作為閾值轮蜕。此外,還需要注意到 GestureHandler 蝗锥,手勢(shì)的

private class GestureHandler extends Handler {
      //...忽略構(gòu)造函數(shù)
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case SHOW_PRESS:
                //回調(diào) onShowPress 方法
                mListener.onShowPress(mCurrentDownEvent);
                break;
                
            case LONG_PRESS:
                //處理長(zhǎng)按消息
                dispatchLongPress();
                break;
                
            case TAP:
                // If the user's finger is still down, do not count it as a tap
                if (mDoubleTapListener != null) {
                    if (!mStillDown) {
                        //此時(shí)跃洛,手指已抬起,回調(diào)確認(rèn)單擊的方法
                        mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
                    } else {
                      //未抬起终议,在手指抬起的時(shí)候回調(diào)確認(rèn)單擊的方法
                        mDeferConfirmSingleTap = true;
                    }
                }
                break;

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

2. 處理手勢(shì)信息

接下來(lái)也就到了 GestureDetector 的核心部分汇竭,也就是如何處理手勢(shì)信息,并判斷是具體的哪種手勢(shì)

public boolean onTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 0);
        }

        final int action = ev.getAction();

        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        //初始化速度追蹤器穴张,并計(jì)算速度
        mVelocityTracker.addMovement(ev);
        //下面需要得出 X 细燎、Y 方向上的中心焦點(diǎn),如果是多點(diǎn)觸碰皂甘,需要計(jì)算平均值得出中心焦點(diǎn)
        // 這里判斷這個(gè) action 是不是有非主要的手指抬起來(lái)了玻驻,如果是的話,記錄它的index,下面計(jì)算的時(shí)候
        //忽略這個(gè)觸點(diǎn)
        final boolean pointerUp =
                (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP;
        final int skipIndex = pointerUp ? ev.getActionIndex() : -1;

        // Determine focal point
        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);
        }
      //pointerUp 為 true偿枕,需要排除掉
        final int div = pointerUp ? count - 1 : count;
        final float focusX = sumX / div;
        final float focusY = sumY / div;

        boolean handled = false;
      //根據(jù)不同的 action,做不同的處理
        switch (action & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_POINTER_DOWN:
            //...
            break;

        case MotionEvent.ACTION_POINTER_UP:
            //...
            break;

        case MotionEvent.ACTION_DOWN:
            //...
            break;

        case MotionEvent.ACTION_MOVE:
            //...
            break;

        case MotionEvent.ACTION_UP:
            //...
            break;

        case MotionEvent.ACTION_CANCEL:
          //...
        }

        if (!handled && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 0);
        }
        return handled;
    }

從以上可以看出璧瞬,onTouchEvent() 方法主要統(tǒng)一對(duì)不同的輸入事件作統(tǒng)一的處理佛析。

2.1 Down 事件處理

    case MotionEvent.ACTION_DOWN:
            if (mDoubleTapListener != null) {
                //如果設(shè)置了雙擊監(jiān)聽(tīng)器,進(jìn)入此結(jié)構(gòu)體
                boolean hadTapMessage = mHandler.hasMessages(TAP);
                //第一次點(diǎn)擊的時(shí)候發(fā)送了TAG類型的消息彪蓬,取消掉寸莫,防止出錯(cuò)
                if (hadTapMessage) mHandler.removeMessages(TAP);
                if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null) && hadTapMessage &&
                        isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) {
                    // This is a second tap
                    //判斷到這是第二次點(diǎn)擊,設(shè)置變量值档冬,回調(diào)相關(guān)方法
                    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
                    // 這是一個(gè)第一次的單擊膘茎,先發(fā)送延時(shí)消息,如果在這段時(shí)間之內(nèi)沒(méi)有再次點(diǎn)擊酷誓,GestureHandler 里就會(huì)處理 這個(gè) TAG 類型的消息
                    mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);
                }
            }
          //設(shè)置相關(guān)變量
            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) {
              //允許長(zhǎng)按披坏,發(fā)送延時(shí)的長(zhǎng)按消息,在  TAP_TIMEOUT + LONGPRESS_TIMEOUT 這段時(shí)間之內(nèi)盐数,
              //如果沒(méi)有其他手勢(shì)操作(移動(dòng)手指棒拂、抬起手指),會(huì)認(rèn)為是長(zhǎng)按手勢(shì)玫氢,并且回調(diào)LongPress 方法帚屉。
                mHandler.removeMessages(LONG_PRESS);
                mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime()
                        + TAP_TIMEOUT + LONGPRESS_TIMEOUT);
            }
            //一小段時(shí)間之后,回調(diào) onShowPress(MotionEvent e) 方法漾峡,反饋給用戶
            mHandler.sendEmptyMessageAtTime(SHOW_PRESS, mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);
            handled |= mListener.onDown(ev);
            break;
    //判斷第二次點(diǎn)擊是否有效
     private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp,
            MotionEvent secondDown) {
        //第一次點(diǎn)擊后攻旦,超過(guò)了限定范圍,認(rèn)為無(wú)效
        if (!mAlwaysInBiggerTapRegion) {
            return false;
        }

        final long deltaTime = secondDown.getEventTime() - firstUp.getEventTime();
        //第一次點(diǎn)擊的手指抬起和第二次點(diǎn)擊的手指落下生逸,中間時(shí)間間隔不符合要求牢屋,無(wú)效
        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();
        //范圍沒(méi)超過(guò)mDoubleTapSlopSquare ,認(rèn)為有效
        return (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare);
    }

Down 事件總結(jié):

  • 單擊判斷:如果接受到一次單擊事件槽袄,會(huì)先發(fā)送 TAG 類型的延遲消息烙无,在這段時(shí)間內(nèi),如果消息沒(méi)被取消遍尺,則會(huì)進(jìn)入 GestureHandler 的 TAG 確認(rèn)操作截酷。
  • 雙擊判斷:已經(jīng)有了第一次的單擊事件,這時(shí)候需要判斷第二次點(diǎn)擊和第一次單擊的距離狮鸭、時(shí)間合搅,條件符合則認(rèn)為是雙擊,回調(diào)相關(guān)方法歧蕉。
  • 長(zhǎng)按判斷:點(diǎn)擊的時(shí)候就發(fā)送延遲的長(zhǎng)按消息灾部,要是這段時(shí)間內(nèi)沒(méi)被取消,就回調(diào)長(zhǎng)按的方法惯退。

2.2 Move 事件處理

case MotionEvent.ACTION_MOVE:
          //已經(jīng)觸發(fā)長(zhǎng)按操作赌髓,一系列事件可以認(rèn)為是長(zhǎng)按,接下來(lái)不做其他處理
            if (mInLongPress || mInContextClick) {
                break;
            }
            final float scrollX = mLastFocusX - focusX;
            final float scrollY = mLastFocusY - focusY;
            if (mIsDoubleTapping) {
                // Give the move events of the double-tap
                //上面說(shuō)過(guò),雙擊過(guò)程中 onDoubleTapEvent(ev) 方法會(huì)被回調(diào)锁蠕,這個(gè)過(guò)程還沒(méi)結(jié)束
                //回調(diào)這個(gè)方法
                handled |= mDoubleTapListener.onDoubleTapEvent(ev);
            } else if (mAlwaysInTapRegion) {
                //第一次 Down 事件里夷野,mAlwaysInTapRegion 為 true
                final int deltaX = (int) (focusX - mDownFocusX);
                final int deltaY = (int) (focusY - mDownFocusY);
                int distance = (deltaX * deltaX) + (deltaY * deltaY);
                if (distance > mTouchSlopSquare) {
                  // 距離超過(guò)一個(gè)數(shù)值,可以認(rèn)為是滑動(dòng)荣倾,進(jìn)入onScroll 模式悯搔,回調(diào)相關(guān)函數(shù)
                    handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
                    mLastFocusX = focusX;
                    mLastFocusY = focusY;
                    mAlwaysInTapRegion = false;
                  //移除之前發(fā)送的延遲消息
                    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)) {
                //最后的條件判斷,繼續(xù) onScroll 回調(diào)
                handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);
                mLastFocusX = focusX;
                mLastFocusY = focusY;
            }
            break;

Move 事件總結(jié):

  • 如果已經(jīng)觸發(fā)了長(zhǎng)按舌仍,并且回調(diào)了長(zhǎng)按的相關(guān)函數(shù)妒貌,就會(huì)認(rèn)為接下來(lái)的 Move 事件都會(huì)直接返回。
  • 如果是雙擊铸豁,因?yàn)殡p擊過(guò)程還未結(jié)束灌曙,所以 Move 事件會(huì)一直回調(diào) onDoubleTapEvent(ev) 方法。
  • 如果滑動(dòng)的距離超過(guò)一定的數(shù)值平方节芥,會(huì)進(jìn)入 onScroll 模式在刺,接下來(lái)的滑動(dòng),只要超過(guò) 1px头镊,就繼續(xù)回調(diào) onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) 方法蚣驼。

2.3 Up 事件處理

case MotionEvent.ACTION_UP:
            mStillDown = false;
            MotionEvent currentUpEvent = MotionEvent.obtain(ev);
            if (mIsDoubleTapping) {
                // Finally, give the up event of the double-tap
                //是雙擊的過(guò)程,回調(diào)onDoubleTapEvent(ev) 方法
                handled |= mDoubleTapListener.onDoubleTapEvent(ev);
            } else if (mInLongPress) {
              //長(zhǎng)按的過(guò)程拧晕,移除可能存在的 TAP 消息 
                mHandler.removeMessages(TAP);
                mInLongPress = false;
            } else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) {
              //如果點(diǎn)擊之后沒(méi)有移動(dòng)隙姿,或者移動(dòng)的距離沒(méi)有超過(guò)一定的數(shù)值,可以認(rèn)為只是單擊而已
                handled = mListener.onSingleTapUp(ev);
                if (mDeferConfirmSingleTap && mDoubleTapListener != null) {
              //GestureHandler 里 mDeferConfirmSingleTap 被設(shè)置為 true厂捞,這里在手指抬起時(shí)進(jìn)行回調(diào)
                    mDoubleTapListener.onSingleTapConfirmed(ev);
                }
            } else if (!mIgnoreNextUpEvent) {
                //進(jìn)入這個(gè)結(jié)構(gòu)體的時(shí)候,說(shuō)明前面沒(méi)有觸發(fā)雙擊或者長(zhǎng)按操作队丝,并且滑動(dòng)的距離也超過(guò)了最小值靡馁,              
                //mAlwaysInTapRegion 被設(shè)置為 false
                // 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)){
                  //速度超過(guò)了最小值,可以認(rèn)為是一個(gè)拋的動(dòng)作
                    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;

Up 事件總結(jié):

  • 判斷是雙擊的過(guò)程臭墨,則繼續(xù)回調(diào) onDoubleTapEvent(ev) 方法,結(jié)束這個(gè)過(guò)程.
  • 如果是長(zhǎng)按的膘盖,也結(jié)束這個(gè)過(guò)程胧弛。
  • 說(shuō)明前面沒(méi)有觸發(fā)雙擊或者長(zhǎng)按操作,并且滑動(dòng)的距離也超過(guò)了最小值侠畔,最后手指抬起時(shí)结缚,速度超過(guò)最小值,可以認(rèn)為是拋的動(dòng)作软棺,回調(diào) onFling() 方法红竭。
  • 如果點(diǎn)擊之后沒(méi)有移動(dòng),或者移動(dòng)的距離沒(méi)有超過(guò)一定的數(shù)值,認(rèn)為只是簡(jiǎn)單的點(diǎn)擊茵宪,結(jié)束這個(gè)過(guò)程最冰。之前 點(diǎn)擊的時(shí)候 發(fā)生了延遲的 TAG消息,但是需要手指抬起才能回調(diào) onSingleTapConfirmed() 方法稀火,如果處理延遲消息的時(shí)候暖哨,手指還未抬起,則先設(shè)置變量標(biāo)記(處理邏輯如下圖)凰狞,等手指抬起再回調(diào)鹿蜀。
case TAP:
                // If the user's finger is still down, do not count it as a tap
                if (mDoubleTapListener != null) {
                    if (!mStillDown) {
                        //此時(shí),手指已抬起服球,回調(diào)確認(rèn)單擊的方法
                        mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
                    } else {
                      //未抬起茴恰,在手指抬起的時(shí)候回調(diào)確認(rèn)單擊的方法
                        mDeferConfirmSingleTap = true;
                    }
                }

2.4 多點(diǎn)觸控處理

case MotionEvent.ACTION_POINTER_DOWN:
            mDownFocusX = mLastFocusX = focusX;
            mDownFocusY = mLastFocusY = focusY;
            // Cancel long press and taps
            //有其他手指落下,移除所有消息斩熊,重置標(biāo)記變量
            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);
                //如果剩下的手指速度方向是和抬起那根手指的速度相反方向的往枣,就說(shuō)明不是fling,清空速度監(jiān)聽(tīng)
                final float dot = x + y;
                if (dot < 0) {
                    mVelocityTracker.clear();
                    break;
                }
            }
            break;
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;
    }

在 onTouchEvent(MotionEvent ev) 方法的前面粉渠,我們對(duì)于多點(diǎn)觸控的處理都是取平均值的分冈,對(duì)多個(gè)手指的UP和DOWN事件處理,其實(shí)就是做一些取消操作而讓多點(diǎn)觸摸不影響單點(diǎn)觸摸的應(yīng)用霸株,例如在多個(gè)手指落下的時(shí)候取消點(diǎn)擊信息等雕沉。

后記

第一次分析源碼,如果有錯(cuò)誤或者需要補(bǔ)充的去件,歡迎留言討論坡椒。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市尤溜,隨后出現(xiàn)的幾起案子倔叼,更是在濱河造成了極大的恐慌,老刑警劉巖宫莱,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件丈攒,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡授霸,警方通過(guò)查閱死者的電腦和手機(jī)巡验,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)碘耳,“玉大人显设,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)桦踊。 經(jīng)常有香客問(wèn)我绞蹦,道長(zhǎng)力奋,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任幽七,我火速辦了婚禮景殷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘澡屡。我一直安慰自己猿挚,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布驶鹉。 她就那樣靜靜地躺著绩蜻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪室埋。 梳的紋絲不亂的頭發(fā)上办绝,一...
    開(kāi)封第一講書人閱讀 52,246評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音姚淆,去河邊找鬼孕蝉。 笑死,一個(gè)胖子當(dāng)著我的面吹牛腌逢,可吹牛的內(nèi)容都是我干的降淮。 我是一名探鬼主播,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼搏讶,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼佳鳖!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起窍蓝,我...
    開(kāi)封第一講書人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤腋颠,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后吓笙,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡巾腕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年面睛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片尊搬。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡叁鉴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出佛寿,到底是詐尸還是另有隱情幌墓,我是刑警寧澤但壮,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站常侣,受9級(jí)特大地震影響蜡饵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜胳施,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一溯祸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧舞肆,春花似錦焦辅、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至哩盲,卻和暖如春前方,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背种冬。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工镣丑, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人娱两。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓莺匠,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親十兢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子趣竣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容