Android 手勢(shì)識(shí)別處理轉(zhuǎn)換

最近項(xiàng)目中對(duì)已有的自定義的圖表控件進(jìn)行優(yōu)化. 考慮之前的手勢(shì)處理都是在控件內(nèi)的onTouchEvent中處理的. 通過(guò)監(jiān)聽(tīng)每一次的MotionEvent事件, 然后進(jìn)行判斷不同的時(shí)機(jī)去決定操作事件的觸發(fā).

其實(shí), Android系統(tǒng)早已提供了針對(duì)Touch事件識(shí)別的處理, 那就是GestureDetector和ScaleGestureDetector.在它們的onTouchEvent中已經(jīng)完美識(shí)別了我們需要的不同的操作, 并通過(guò)Listener回調(diào)給使用者.

OnGestureListener, OnDoubleTapListener, OnContextClickListener
回調(diào)接口?參見(jiàn)GestureDetector.java:

OnScaleGestureListener
縮放操作的回調(diào)接口參見(jiàn)ScaleGestureDetector.java:

從以上接口中, 我們可以非常方便的找到系統(tǒng)所支持的所有手勢(shì)操作處理點(diǎn). 并準(zhǔn)確的把握處理時(shí)機(jī). 以便完成我們的處理邏輯.

可是, 它仍有一些弊端:

  1. 語(yǔ)義不夠明確, 有時(shí)我們僅需要告訴使用者什么時(shí)候真的 單擊, 長(zhǎng)按, 縮放了...
  2. 自定義控件時(shí)使用不具靈活,
    長(zhǎng)按時(shí)間不能自定義, 長(zhǎng)按時(shí)間參見(jiàn)系統(tǒng)設(shè)置:Settings.Secure.LONG_PRESS_TIMEOUT 默認(rèn)為500毫秒
    <integer name="def_long_press_timeout_millis">500</integer>
    對(duì)縮放操作自定義處理.
  3. 與父布局的Scroll事件沖突. 本例中當(dāng)View的父控件是可滾動(dòng)時(shí),
    View的移動(dòng)操作會(huì)與父控件的Scroll事件發(fā)生沖突.

當(dāng)然, 這都因需求而定, 目前我的項(xiàng)目主要是針對(duì)股票K線及技術(shù)指標(biāo)圖表的操作, 主要有單擊, 雙擊, 長(zhǎng)按, 移動(dòng), 長(zhǎng)按后的移動(dòng),

為此我有如下封裝:

    /**
     * Created by ?kangqiao on 16/9/22.
     * e-mail: kangqiao610@gmail.com
     */
    public interface GestureOperateListener {

        /**
         * 只要有 ACTION_UP 或 ACTION_CANCEL
         */
        void onUpOrCancel();

        /**
         * 縮放
         * if true zoom in, else zoom out
         *
         * @param detector
         */
        void onZoom(ScaleGestureDetector detector);

        /**
         * 滑動(dòng), 注意: 之前沒(méi)有進(jìn)行長(zhǎng)按.
         *
         * @param downEvent
         * @param event
         * @param distanceX
         * @param distanceY
         */
        void onMove(MotionEvent downEvent, MotionEvent event, float distanceX, float distanceY);

        /**
         * 長(zhǎng)按后滑動(dòng), 回調(diào)多次
         *
         * @param event
         */
        void onMoveAfterLongPress(MotionEvent event);

        /**
         * 長(zhǎng)按 僅回調(diào)一次
         *
         * @param event
         */
        void onLongPress(MotionEvent event);

        /**
         * 快速點(diǎn)擊. 第一次 touch up時(shí), 后續(xù)又沒(méi)有任何操作(滑動(dòng), 不處理長(zhǎng)按中, 縮放)時(shí)回調(diào)
         *
         * @param event
         */
        void onClick(MotionEvent event);

        /**
         * 雙擊. 第二次 touch up時(shí), 回調(diào)
         *
         * @param event
         */
        void onDoubleClick(MotionEvent event);

        /**
         * 快速滑動(dòng)后的最后一個(gè)動(dòng)作
         *
         * @param downEvent 起點(diǎn)
         * @param event     終點(diǎn)
         * @param velocityX 水平方向移動(dòng)的速度搞动,像素/秒
         * @param velocityY 垂直方向移動(dòng)的速度纯露,像素/秒
         */
        void onFling(MotionEvent downEvent, MotionEvent event, float velocityX, float velocityY);

        /**
         * 重繪UI
         */
        void postInvalidate();
    }

對(duì)于TouchEvent事件中那些復(fù)雜的操作判斷仍然交由系統(tǒng)提供的GestureDetector和ScaleGestureDetector來(lái)偵測(cè), 需要作的僅僅是在其回調(diào)中轉(zhuǎn)換為需要的操作.

依然遵循Detector的使用規(guī)則, 我做了如下的封裝:

public class KLineChartView extends LinearLayout {
    ......
    private GestureOperateListener supportGestureOperate = new GestureDetectHandler.SimpleGestureOperateListener() {
    private void init(Context context, AttributeSet attrs) {
        initAttrs(attrs);
        mGestureDetectHandler = new GestureDetectHandler(this, supportGestureOperate);
        //設(shè)置長(zhǎng)按效果后延時(shí)取消的時(shí)間.
        mGestureDetectHandler.setDelayedCancelTimeoutAfterLongPress(2000);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return mGestureDetectHandler.onTouchEvent(event);
    }
    ......
}

GestureDetectHandler是針對(duì)TouchEvent事件的綜合處理類.
在構(gòu)造時(shí)接收被偵測(cè)的View, 并給出相應(yīng)的事件回調(diào).
并覆寫View的onTouchEvent方法, 將事件傳遞給處理者.

public class GestureDetectHandler extends GestureDetector.SimpleOnGestureListener implements ScaleGestureDetector.OnScaleGestureListener, View.OnTouchListener {
   public GestureDetectHandler(View detectedView, GestureOperateListener operator) {
       assert detectedView != null : Log.e(TAG, "detectedView is null in constructor!");
       assert operator != null : Log.e(TAG, "GestureOperateListener is null in constructor!");

       mDetectedView = detectedView;
       detectedView.setOnTouchListener(this);
       mHandler = new GestureHandler(detectedView.getContext().getMainLooper());
       mGestureOptListener = operator;

       mGestureDetector = new GestureDetector(mDetectedView.getContext(), this);
       mScaleGestureDetector = new ScaleGestureDetector(mDetectedView.getContext(), this);

       setIsCustomLongPressEnabled(true); //默認(rèn)使用自定義的長(zhǎng)按操作
   }

   public boolean onTouch(View v, MotionEvent event) {
       final int action = event.getAction();
       if (action == MotionEvent.ACTION_CANCEL) {
           loge("onTouch", actionToStr(action));
           //當(dāng)mDetectedView的onTouchEvent事件突然交到父控件處理時(shí), 緊急執(zhí)行清理操作.
           dispatchCancel();
       }
       return false;
   }
   public boolean onTouchEvent(MotionEvent event) {
       mGestureDetector.onTouchEvent(event);
       mScaleGestureDetector.onTouchEvent(event);
       return true;
   }
}

在解決與父控件滾動(dòng)事件沖突時(shí), 可以通過(guò)阻止父層的View截獲touch事件,就是調(diào)用getParent().requestDisallowInterceptTouchEvent(true);方法肢扯。一旦底層View收到touch的action后調(diào)用這個(gè)方法那么父層View就不會(huì)再調(diào)用onInterceptTouchEvent了,也無(wú)法截獲以后的action此再。

//在發(fā)生滾動(dòng)和長(zhǎng)按時(shí)觸發(fā), 標(biāo)示著mDetectedView要完全接收事件. 父控件不在干預(yù).
mDetectedView.getParent().requestDisallowInterceptTouchEvent(true);
//當(dāng)mDetectedView結(jié)束了相應(yīng)的操作后, 要相應(yīng)的給重置為false, 標(biāo)示著允許父控件與子控件交互處理事件.
mDetectedView.getParent().requestDisallowInterceptTouchEvent(false);

GestureDetectHandler對(duì)于mDetectedView事件的處理時(shí)機(jī)的判斷已經(jīng)交由GestureDetector和ScaleGestureDetector來(lái)處理, 并在回調(diào)OnGestureListener, OnDoubleTapListener和OnScaleGestureListener中轉(zhuǎn)換為
GestureOperateListener接口中定義的相應(yīng)的操作, 最后回調(diào)給使用者.

固GestureDetectHandler主要是針對(duì)GestureOperateListener接口的操作做了相應(yīng)的轉(zhuǎn)換操作. 并主要針對(duì)

  1. 長(zhǎng)按
    長(zhǎng)按時(shí)間自定義
    解決與父控件的沖突
    長(zhǎng)按效果延長(zhǎng)時(shí)間的設(shè)置.
  2. 長(zhǎng)按后的移動(dòng)
    解決正常移動(dòng)與自定義長(zhǎng)按后移動(dòng)的沖突.
  3. 單擊事件的重新偵測(cè)
    長(zhǎng)按后不會(huì)觸發(fā)單擊操作.
  4. 縮放
    縮放操作的自定義

完整的代碼如下: ( 注解中已詳盡說(shuō)明)


/**
 * Created by zhaopan on 16/9/24.
 * e-mail: kangqiao610@gmail.com
 * 手勢(shì)識(shí)別轉(zhuǎn)換處理類
 */
public class GestureDetectHandler extends GestureDetector.SimpleOnGestureListener implements ScaleGestureDetector.OnScaleGestureListener, View.OnTouchListener {
    private static final String TAG = "GestureDetectHandler";
    public static final boolean DEBUG = true;

    //最小縮放距離, 單位:像素
    public static final int MIN_SCALE_DISTANCE = 20;
    //最短縮放時(shí)間, 單位:毫秒
    public static final int MIN_SCALE_TIME_MILLI = 10;
    //最小移動(dòng)距離
    public static final int MIN_MOVE_DISTANCE = 5;

    //X軸的坐標(biāo)位移大于FLING_MIN_DISTANCE
    public static final int FLING_MIN_DISTANCE = 100;
    //移動(dòng)速度大于FLING_MIN_VELOCITY個(gè)像素/秒
    public static final int FLING_MIN_VELOCITY = 150;

    public static final long CUSTOM_LONG_PRESS_TIMEOUT = 300;
    public static final long CUSTOM_SCALE_BEGIN_TIMEOUT = 0;
    public static final long CUSTOM_DELAYED_CANCEL_TIMEOUT_AFTER_LONG_PRESS = 100;

    private static final int LONG_PRESS = 1;
    private static final int SCALE_BEGIN = 2;
    private static final int DELAYED_CANCEL = 3;

    private long mLongPressTimeout = CUSTOM_LONG_PRESS_TIMEOUT;
    private long mScaleBeginTimeout = CUSTOM_SCALE_BEGIN_TIMEOUT;
    private long mDelayedCancelTimeoutAfterLongPress = CUSTOM_DELAYED_CANCEL_TIMEOUT_AFTER_LONG_PRESS;
    private boolean mInLongPressProgress = false;
    private boolean mInScaleProgress = false;
    private boolean mInDoubleTapProgress = false;
    private boolean mIsCanceled = true;
    private boolean mIsCustomLongPressEnabled = true;

    private float lastSpan;
    private MotionEvent showPressEvent;
    private View mDetectedView;
    private Handler mHandler;
    private GestureOperateListener mGestureOptListener;
    private GestureDetector mGestureDetector;//單擊和雙擊事件手勢(shì)識(shí)別
    private ScaleGestureDetector mScaleGestureDetector;//縮放事件手勢(shì)識(shí)別

    public GestureDetectHandler(View detectedView, GestureOperateListener operator) {
        assert detectedView != null : Log.e(TAG, "detectedView is null in constructor!");
        assert operator != null : Log.e(TAG, "GestureOperateListener is null in constructor!");

        mDetectedView = detectedView;
        detectedView.setOnTouchListener(this);
        mHandler = new GestureHandler(detectedView.getContext().getMainLooper());
        mGestureOptListener = operator;

        mGestureDetector = new GestureDetector(mDetectedView.getContext(), this);
        mScaleGestureDetector = new ScaleGestureDetector(mDetectedView.getContext(), this);

        setIsCustomLongPressEnabled(true); //默認(rèn)使用自定義的長(zhǎng)按操作
    }

    private class GestureHandler extends Handler {
        GestureHandler() {
            super();
        }

        GestureHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case LONG_PRESS:
                    dispatchLongPress();
                    break;
                case SCALE_BEGIN:
                    dispatchScaleBegin();
                    break;
                case DELAYED_CANCEL:
                    dispatchCancel();
                    break;
                default:
                    throw new RuntimeException("Unknown message " + msg); //never
            }
        }
    }

    /**
     * OnTouchListener 僅僅作為補(bǔ)充操作,
     * 防止長(zhǎng)按后滑動(dòng)和長(zhǎng)按滑動(dòng)后onFling, 這兩個(gè)操作后續(xù)沒(méi)有明確的結(jié)束回調(diào), 即Up或Cancel時(shí)GestureDetector并不回調(diào)操作.
     * 不建議使用, 如是mDetectedView有去設(shè)置OnTouchListener則會(huì)覆蓋此實(shí)現(xiàn). 即會(huì)導(dǎo)致此中功能失效.
     *
     * @param v
     * @param event
     * @return
     */
    @Override
    @Deprecated
    public boolean onTouch(View v, MotionEvent event) {
        final int action = event.getAction();
        if (action == MotionEvent.ACTION_CANCEL) {
            loge("onTouch", actionToStr(action));
            //當(dāng)mDetectedView的onTouchEvent事件突然交到父控件處理時(shí), 緊急執(zhí)行清理操作.
            dispatchCancel();
        }
        //改由GestureDetector的每一步單獨(dú)處理, 減少重復(fù)的繪制操作.
        //mGestureOptListener.postInvalidate();
        return false;
    }

    /**
     * 接收系統(tǒng)的onTouchEvent事件, 并返回true表示處理, 不再傳遞
     *
     * @param event
     * @return
     */
    public boolean onTouchEvent(MotionEvent event) {
        mGestureDetector.onTouchEvent(event);
        mScaleGestureDetector.onTouchEvent(event);
        //return super.onTouchEvent(event);//不管返回值是什么子漩,都能接收down事件幼衰,都能觸發(fā)onDown、onShowPress诊霹、onLongPress
        return true;//但只有返回true才能繼續(xù)接收move,up等事件羞延,也才能響應(yīng)ScaleGestureDetector事件及GestureDetector中與move,up相關(guān)的事件
    }

    private void dispatchLongPress() {
        mDetectedView.getParent().requestDisallowInterceptTouchEvent(true);
        mInLongPressProgress = true;
        onLongPress(showPressEvent);
    }

    private void dispatchScaleBegin() {
        mDetectedView.getParent().requestDisallowInterceptTouchEvent(true);
        mInScaleProgress = true;
        if(mInLongPressProgress) {
            mInLongPressProgress = false;
            mHandler.removeMessages(LONG_PRESS);
            mGestureOptListener.onUpOrCancel();
            mGestureOptListener.postInvalidate();
        }
    }

    //ACTION_CANCEL and ACTION_UP
    private void dispatchCancel() {
        loge("dispatchCancel", "dispatchCancel");
        if (!mIsCanceled) {
            mIsCanceled = true;
            mDetectedView.getParent().requestDisallowInterceptTouchEvent(false);
            mHandler.removeMessages(DELAYED_CANCEL);
            mHandler.removeMessages(LONG_PRESS);
            mHandler.removeMessages(SCALE_BEGIN);
            mInDoubleTapProgress = false;
            mInLongPressProgress = false;
            mInScaleProgress = false;
            mGestureOptListener.onUpOrCancel();
        }
        mGestureOptListener.postInvalidate();
    }

    /**
     * 設(shè)置是否使用自己的長(zhǎng)按處理.
     *
     * @param isCustomLongPressEnabled
     */
    public void setIsCustomLongPressEnabled(boolean isCustomLongPressEnabled) {
        if (isCustomLongPressEnabled) {
            //如果自定義自己的長(zhǎng)按處理, 先禁用系統(tǒng)的長(zhǎng)按觸發(fā).
            mGestureDetector.setIsLongpressEnabled(false);
        } else {
            //如果不自定義自己的長(zhǎng)按處理, 默認(rèn)啟用系統(tǒng)的長(zhǎng)按
            mGestureDetector.setIsLongpressEnabled(true);
        }
        //mGestureDetector.setIsLongpressEnabled(!isCustomLongPressEnabled);
        mIsCustomLongPressEnabled = isCustomLongPressEnabled;
    }

    /**
     * 設(shè)置是否使用系統(tǒng)的長(zhǎng)按處理.
     *
     * @param isSystemLongPressEnabled
     */
    public void setIsLongpressEnabled(boolean isSystemLongPressEnabled) {
        mGestureDetector.setIsLongpressEnabled(isSystemLongPressEnabled);
        mIsCustomLongPressEnabled = false;
    }

    public void setScaleBeginTimeout(long scaleBeginTimeout) {
        if (scaleBeginTimeout >= 0) this.mScaleBeginTimeout = scaleBeginTimeout;
    }

    public void setLongPressTimeout(long longPressTimeout) {
        if (longPressTimeout >= 0) this.mLongPressTimeout = longPressTimeout;
    }

    /**
     * 設(shè)置長(zhǎng)按后的處理操作 需要延時(shí)多長(zhǎng)時(shí)間執(zhí)行清理操作. 默認(rèn)大于等于100毫秒. 且會(huì)自動(dòng)開(kāi)啟自定義的長(zhǎng)按操作處理.
     *
     * @param cancelTimeout
     */
    public void setDelayedCancelTimeoutAfterLongPress(long cancelTimeout) {
        if (cancelTimeout >= CUSTOM_DELAYED_CANCEL_TIMEOUT_AFTER_LONG_PRESS) {
            setIsCustomLongPressEnabled(true);
            this.mDelayedCancelTimeoutAfterLongPress = cancelTimeout;
        }
    }

    private void sendDelayedCancelMessage() {
        mHandler.removeMessages(DELAYED_CANCEL);
        mHandler.sendEmptyMessageDelayed(DELAYED_CANCEL, mDelayedCancelTimeoutAfterLongPress);
    }

    public GestureDetector getGestureDetector() {
        return mGestureDetector;
    }

    public ScaleGestureDetector getScaleGestureDetector() {
        return mScaleGestureDetector;
    }

    /**
     * 注意:
     * 1. onSingleTapConfirmed(單擊)和onSingleTapUp都是在down后既沒(méi)有滑動(dòng)onScroll,又沒(méi)有長(zhǎng)按onLongPress時(shí)脾还, up 時(shí)觸發(fā)的
     * 2. 非嘲槁幔快的點(diǎn)擊一下:onDown->onSingleTapUp->onSingleTapConfirmed
     * 3. 稍微慢點(diǎn)的點(diǎn)擊一下:onDown->onShowPress->onSingleTapUp->onSingleTapConfirmed(最后一個(gè)不一定會(huì)觸發(fā))
     */
    ////////OnGestureListener/////////////////////////////

    /**
     * Touch down時(shí)觸發(fā)
     * 按下(onDown): 剛剛手指接觸到觸摸屏的那一剎那,就是觸的那一下
     *
     * @param e
     * @return
     */
    @Override
    public boolean onDown(MotionEvent e) {
        loge("view-手勢(shì)", "onDown event.getAction=" + actionToStr(e.getAction()) + ", event.getX()=" + e.getX() + ", event.getY()=" + e.getY());
        mInLongPressProgress = false;
        mIsCanceled = false;
        return super.onDown(e);
    }

    /**
     * onScroll一點(diǎn)距離后鄙漏,【拋擲時(shí)】觸發(fā)(若是輕輕的嗤谚、慢慢的停止活動(dòng),而非拋擲怔蚌,則很可能不觸發(fā))
     * 參數(shù)為手指接觸屏幕巩步、離開(kāi)屏幕一瞬間的動(dòng)作事件,及手指水平桦踊、垂直方向移動(dòng)的速度椅野,像素/秒
     * 拋擲(onFling): 手指在觸摸屏上迅速移動(dòng),并松開(kāi)的動(dòng)作,onDown -> onScroll ... -> onFling
     * 長(zhǎng)按后滾動(dòng)(onScroll): 手指在觸摸屏上滑動(dòng)竟闪,onDown -> onShowPress -> onLongPress -> onScroll ... [onFling]
     *
     * @param downEvent
     * @param event
     * @param velocityX
     * @param velocityY
     * @return
     */
    @Override
    public boolean onFling(MotionEvent downEvent, MotionEvent event, float velocityX, float velocityY) {
        loge("view-手勢(shì)", "onFling newAction=" + actionToStr(event.getAction()) + ", downEvent.getRawX()=" + downEvent.getRawX() + ", event.getRawX()=" + event.getRawX() + ", velocityX=" + velocityX + ", velocityY=" + velocityY);
        mGestureOptListener.onFling(downEvent, event, velocityX, velocityY);
        mGestureOptListener.postInvalidate();
        if (mIsCustomLongPressEnabled && mInLongPressProgress) {
            sendDelayedCancelMessage(); //長(zhǎng)按后滑動(dòng), 最后onFling了一下, 執(zhí)行延時(shí)清理操作.
        } else {
            dispatchCancel(); //正常的onFling, 直接執(zhí)行清理操作.
        }
        /*if ((event.getRawX() - downEvent.getRawX()) > FLING_MIN_DISTANCE *//*&& Math.abs(velocityX) > FLING_MIN_VELOCITY*//*) {
            loge("view-手勢(shì)", "onFling-從左往右滑");
            return true;
        } else if (downEvent.getRawX() - event.getRawX() > FLING_MIN_DISTANCE *//*&& Math.abs(velocityX) > FLING_MIN_VELOCITY*//*) {
            loge("view-手勢(shì)", "onFling-從右往左滑");
            return true;
        }*/
        return super.onFling(downEvent, event, velocityX, velocityY);
    }

    /**
     * Touch了滑動(dòng)時(shí)觸發(fā)声离,e1代表觸摸時(shí)的事件,是不變的瘫怜,e2代表滑動(dòng)過(guò)程中的事件术徊,是時(shí)刻變化的
     * distance是當(dāng)前event2與上次回調(diào)時(shí)的event2之間的距離,代表上次回調(diào)之后到這次回調(diào)之前移動(dòng)的距離
     * 縮放(onScroll) onDown -> onScaleBegin -> (onScroll ... -> onScale) ... -> onScaleEnd
     * 長(zhǎng)按后滾動(dòng)(onScroll): 手指在觸摸屏上滑動(dòng)鲸湃,onDown -> onShowPress -> onLongPress -> onScroll ... [onFling]
     * 滾動(dòng)(onScroll): 手指在觸摸屏上滑動(dòng)赠涮,onDown -> onScroll ...
     *
     * @param downEvent
     * @param event
     * @param distanceX
     * @param distanceY
     * @return
     */
    @Override
    public boolean onScroll(MotionEvent downEvent, MotionEvent event, float distanceX, float distanceY) {
        if (mInScaleProgress) {
            // TODO: 16/9/27 縮放中的同步滑動(dòng)
            loge("view-手勢(shì)", "onScroll 縮放中>>> " + actionToStr(event.getAction()) + ", -X-" + (int) downEvent.getX() + "/" + (int) event.getX() + "/" + (int) distanceX
                    + ", >>> -Y-" + (int) downEvent.getY() + "/" + (int) event.getY() + "/" + (int) distanceY);
        } else if (mInLongPressProgress) { //長(zhǎng)按后的滑動(dòng).
            loge("view-手勢(shì)", "onScroll 長(zhǎng)按中>>> " + actionToStr(event.getAction()) + ", -X-" + (int) downEvent.getX() + "/" + (int) event.getX() + "/" + (int) distanceX
                    + ", >>> -Y-" + (int) downEvent.getY() + "/" + (int) event.getY() + "/" + (int) distanceY);
            mGestureOptListener.onMoveAfterLongPress(event);
            mGestureOptListener.postInvalidate(); //后續(xù)仍有移動(dòng), 執(zhí)行刷新UI操作
            if (mIsCustomLongPressEnabled) {
                sendDelayedCancelMessage(); //長(zhǎng)按后的滑動(dòng)操作, 執(zhí)行延時(shí)清理操作
            } else {
                // TODO: 16/9/27 注: 沒(méi)有自定義長(zhǎng)按操作, 如果沒(méi)有后續(xù)操作是不會(huì)有Up或Cancel動(dòng)作, 即沒(méi)有執(zhí)行清理操作的機(jī)會(huì)了.
            }
        } else { //普通滑動(dòng)
            loge("view-手勢(shì)", "onScroll 滑動(dòng)中>>> " + actionToStr(event.getAction()) + ", -X-" + (int) downEvent.getX() + "/" + (int) event.getX() + "/" + (int) distanceX
                    + ", >>> -Y-" + (int) downEvent.getY() + "/" + (int) event.getY() + "/" + (int) distanceY);
            mGestureOptListener.onMove(downEvent, event, distanceX, distanceY);
            mGestureOptListener.postInvalidate(); //后續(xù)仍有移動(dòng), 執(zhí)行刷新UI操作
            // TODO: 16/9/27 注: 普通的滑動(dòng)操作, 如果沒(méi)有后續(xù)操作是不會(huì)有Up或Cancel動(dòng)作, 即沒(méi)有執(zhí)行清理操作的機(jī)會(huì)了.
        }
        return super.onScroll(downEvent, event, distanceX, distanceY);
    }

    /**
     * ouch了不移動(dòng)一直Touch down時(shí)觸發(fā)
     * 長(zhǎng)按(onLongPress): 手指按在持續(xù)一段時(shí)間,并且沒(méi)有松開(kāi)
     * 1. onDown -> onShowPress -> onLongPress -> onSingleTapUp -> onSingleTapConfirmed
     * 2. onDown -> onShowPress -> onLongPress -> onScroll ...
     * 3. onDown -> onShowPress -> onLongPress -> onScroll ... -> onFling
     *
     * @param event
     */
    @Override
    public void onLongPress(MotionEvent event) {
        loge("view-手勢(shì)", "onLongPress event.getAction=" + actionToStr(event.getAction()) + ", event.getX()=" + event.getX() + ", event.getY()=" + event.getY());
        mGestureOptListener.onLongPress(event);
        mGestureOptListener.postInvalidate();
        super.onLongPress(event);
    }

    /**
     * Touch了還沒(méi)有滑動(dòng)時(shí)觸發(fā)
     * 按装堤簟(onShowPress): 手指按在觸摸屏上笋除,在按下起效,在長(zhǎng)按前失效炸裆,
     * onDown -> onShowPress -> onLongPress
     *
     * @param e
     */
    @Override
    public void onShowPress(MotionEvent e) {
        loge("view-手勢(shì)", "onShowPress event.getAction=" + actionToStr(e.getAction()) + ",  event.getX()=" + e.getX() + ", event.getY()=" + e.getY());
        if (mIsCustomLongPressEnabled && !mInDoubleTapProgress) { //如果自定義長(zhǎng)按啟用, 并且不是在雙擊的第二下長(zhǎng)按的, 即執(zhí)行長(zhǎng)按操作.
            mHandler.sendEmptyMessageAtTime(LONG_PRESS, e.getDownTime() + mLongPressTimeout);
            showPressEvent = e;
        } else { //沒(méi)有設(shè)置自定義長(zhǎng)按操作
            mDetectedView.getParent().requestDisallowInterceptTouchEvent(true); //參考dispatchLongPress();
            mInLongPressProgress = true;
        }
        super.onShowPress(e);
    }

    /**
     * 在touch down后又沒(méi)有滑動(dòng)(onScroll)垃它,又沒(méi)有長(zhǎng)按(onLongPress),然后Touchup時(shí)觸發(fā)烹看。
     * 抬起(onSingleTapUp):手指離開(kāi)觸摸屏的那一剎那
     *
     * @param e
     * @return
     */
    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        loge("view-手勢(shì)", "onSingleTapUp event.getAction=" + actionToStr(e.getAction()) + ", event.getX()=" + e.getX() + ", event.getY()=" + e.getY());
        return super.onSingleTapUp(e);
    }

    ////////OnDoubleTapListener/////////////////////////////

    /**
     * 在touch down后又沒(méi)有滑動(dòng)(onScroll)国拇,又沒(méi)有長(zhǎng)按(onLongPress),然后Touchup時(shí)觸發(fā)惯殊。
     * 單擊確認(rèn)酱吝,即很快的按下并抬起,但并不連續(xù)點(diǎn)擊第二下
     *
     * @param event
     * @return
     */
    @Override
    public boolean onSingleTapConfirmed(MotionEvent event) {
        loge("view-手勢(shì)", "onSingleTapConfirmed event.getAction=" + actionToStr(event.getAction()) + ", event.getX()=" + event.getX() + ", event.getY()=" + event.getY());
        if (mInLongPressProgress) {
            //長(zhǎng)按進(jìn)行中... 如果設(shè)置了自定義長(zhǎng)按操作, 執(zhí)行延時(shí)清理操作, 否則直接清理.
            if (mIsCustomLongPressEnabled) {
                sendDelayedCancelMessage();
            } else {
                dispatchCancel();
            }
        } else {
            //執(zhí)行單擊操作.
            mGestureOptListener.onClick(event);
            dispatchCancel(); //后續(xù)沒(méi)有操作并已經(jīng)Up或Cancel了, 所以執(zhí)行清理操作.
        }

        return super.onSingleTapConfirmed(event);
    }

    /**
     * 雙擊的【第二下】Touch down時(shí)觸發(fā)(只執(zhí)行一次)
     *
     * @param e
     * @return
     */
    @Override
    public boolean onDoubleTap(MotionEvent e) {
        loge("view-手勢(shì)", "onDoubleTap event.getAction=" + actionToStr(e.getAction()) + ", event.getX()=" + e.getX() + ", event.getY()=" + e.getY());
        mInDoubleTapProgress = true;
        return super.onDoubleTap(e);
    }

    /**
     * 雙擊的【第二下】Touch down和up都會(huì)觸發(fā)(執(zhí)行次數(shù)不確定)土思。
     *
     * @param event
     * @return
     */
    @Override
    public boolean onDoubleTapEvent(MotionEvent event) {
        loge("view-手勢(shì)", "onDoubleTapEvent event.getAction=" + actionToStr(event.getAction()) + ", event.getX()=" + event.getX() + ", event.getY()=" + event.getY());
        if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) {
            mGestureOptListener.onDoubleClick(event);
            dispatchCancel(); //雙擊操作完成并Up或Cancel, 所以執(zhí)行清理操作
        }
        return super.onDoubleTapEvent(event);
    }

    ////////OnContextClickListener/////////////////////////////
    @Override
    public boolean onContextClick(MotionEvent e) {
        loge("view-手勢(shì)", "onContextClick event.getX()=" + e.getX() + ", event.getY()=" + e.getY());
        return super.onContextClick(e);
    }

    ////////OnScaleGestureListener/////////////////////////////
    /**
     * http://www.cnblogs.com/baiqiantao/p/5630506.html
     * public float getCurrentSpan () 返回手勢(shì)過(guò)程中务热,組成該手勢(shì)的兩個(gè)觸點(diǎn)的當(dāng)前距離。
     * public long getEventTime () 返回事件被捕捉時(shí)的時(shí)間己儒。
     * public float getFocusX () 返回當(dāng)前手勢(shì)焦點(diǎn)的 X 坐標(biāo)崎岂。 如果手勢(shì)正在進(jìn)行中,焦點(diǎn)位于組成手勢(shì)的兩個(gè)觸點(diǎn)之間闪湾。 如果手勢(shì)正在結(jié)束冲甘,焦點(diǎn)為仍留在屏幕上的觸點(diǎn)的位置。若 isInProgress() 返回 false响谓,該方法的返回值未定義损合。
     * public float getFocusY ()  返回當(dāng)前手勢(shì)焦點(diǎn)的 Y 坐標(biāo)。
     * public float getPreviousSpan () 返回手勢(shì)過(guò)程中娘纷,組成該手勢(shì)的兩個(gè)觸點(diǎn)的前一次距離。
     * public float getScaleFactor () 返回從前一個(gè)伸縮事件至當(dāng)前伸縮事件的伸縮比率跋炕。該值定義為  getCurrentSpan() / getPreviousSpan()赖晶。
     * public long getTimeDelta () 返回前一次接收到的伸縮事件距當(dāng)前伸縮事件的時(shí)間差,以毫秒為單位。
     * public boolean isInProgress () 如果手勢(shì)處于進(jìn)行過(guò)程中遏插,返回 true捂贿。否則返回 false。
     */

    /**
     * 開(kāi)始縮放
     *
     * @param detector
     * @return
     */
    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        if (mInScaleProgress) { //縮放中...
            float distance = detector.getCurrentSpan() - lastSpan;
            if (Math.abs(distance) > MIN_SCALE_DISTANCE) {
                lastSpan = detector.getCurrentSpan();
                if (detector.getScaleFactor() < 1) { //縮小
                    Log.e("view-縮放", "onScale胳嘲,縮小" + detector.getScaleFactor() + ", curSpan=" + detector.getCurrentSpan() + ", preSpan=" + detector.getPreviousSpan() + ", curSpan-preSpan=>" + distance + ", timeDelta=" + detector.getTimeDelta());
                } else { //放大
                    Log.e("view-縮放", "onScale厂僧,放大" + detector.getScaleFactor() + ", curSpan=" + detector.getCurrentSpan() + ", preSpan=" + detector.getPreviousSpan() + ",  curSpan-preSpan=>" + distance + ", timeDelta=" + detector.getTimeDelta());
                }
                mGestureOptListener.onZoom(detector);
                mGestureOptListener.postInvalidate(); //并沒(méi)Up或Cancel且后續(xù)仍在Zoom, 執(zhí)行刷新UI操作
            }
        }
        return false;
    }

    /**
     * 縮放開(kāi)始, 一次縮放僅執(zhí)行一次
     *
     * @param detector
     * @return
     */
    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        loge("view-縮放", "onScaleBegin");
        mHandler.sendEmptyMessageAtTime(SCALE_BEGIN, detector.getEventTime() + mScaleBeginTimeout);
        return true;
    }

    /**
     * 縮放結(jié)束, 一次縮放僅執(zhí)行一次
     *
     * @param detector
     */
    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {
        loge("view-縮放", "onScaleEnd");
        dispatchCancel(); //已經(jīng)停止縮放操作并Up或Cancel, 執(zhí)行清理操作.
    }

    /**
     * @see ScaleGestureDetector#getCurrentSpanX()
     */
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public static float getCurrentSpanX(ScaleGestureDetector scaleGestureDetector) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            return scaleGestureDetector.getCurrentSpanX();
        } else {
            return scaleGestureDetector.getCurrentSpan();
        }
    }

    /**
     * @see ScaleGestureDetector#getCurrentSpanY()
     */
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public static float getCurrentSpanY(ScaleGestureDetector scaleGestureDetector) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            return scaleGestureDetector.getCurrentSpanY();
        } else {
            return scaleGestureDetector.getCurrentSpan();
        }
    }

    private static void loge(String TAG, String msg) {
        if (DEBUG) Log.e("zp>>>" + TAG, msg);
    }

    private static String actionToStr(int action) {
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                return "DOWN_" + action;
            case MotionEvent.ACTION_UP:
                return "UP_" + action;
            case MotionEvent.ACTION_MOVE:
                return "MOVE_" + action;
            case MotionEvent.ACTION_CANCEL:
                return "CANCEL_" + action;
        }
        return "<" + action + ">";
    }

   //省略 GestureOperateListener與SimpleGestureOperateListener的定義.
}

自此, GestureDetectHandler已經(jīng)完成對(duì)于TouchEvent事件的轉(zhuǎn)換, 并符合項(xiàng)目所支持的所有操作.
對(duì)于后續(xù)操作的處理. 接口定義方法中的參數(shù)已經(jīng)足夠給使用者完成處理.

以后如遇到自定義View的事件處理時(shí), 可簡(jiǎn)單的使用它便能很好的完成事件操作的處理.
且可自定義操作.

歡迎指出缺陷及不足, 一起成長(zhǎng).

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市了牛,隨后出現(xiàn)的幾起案子颜屠,更是在濱河造成了極大的恐慌,老刑警劉巖鹰祸,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件甫窟,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡蛙婴,警方通過(guò)查閱死者的電腦和手機(jī)粗井,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)街图,“玉大人浇衬,你說(shuō)我怎么就攤上這事〔图茫” “怎么了径玖?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)颤介。 經(jīng)常有香客問(wèn)我梳星,道長(zhǎng),這世上最難降的妖魔是什么滚朵? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任冤灾,我火速辦了婚禮,結(jié)果婚禮上辕近,老公的妹妹穿的比我還像新娘韵吨。我一直安慰自己,他們只是感情好移宅,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布归粉。 她就那樣靜靜地躺著,像睡著了一般漏峰。 火紅的嫁衣襯著肌膚如雪糠悼。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,737評(píng)論 1 305
  • 那天浅乔,我揣著相機(jī)與錄音倔喂,去河邊找鬼铝条。 笑死,一個(gè)胖子當(dāng)著我的面吹牛席噩,可吹牛的內(nèi)容都是我干的班缰。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼悼枢,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼埠忘!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起馒索,我...
    開(kāi)封第一講書(shū)人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤莹妒,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后双揪,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體动羽,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年渔期,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了运吓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡疯趟,死狀恐怖拘哨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情信峻,我是刑警寧澤倦青,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站盹舞,受9級(jí)特大地震影響产镐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜踢步,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一癣亚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧获印,春花似錦述雾、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至鳍征,卻和暖如春黍翎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蟆技。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工玩敏, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留斗忌,地道東北人质礼。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓旺聚,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親眶蕉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子砰粹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

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