Android开发之手势识别

来源:互联网 发布:翼龙贷java 待遇 编辑:程序博客网 时间:2024/05/17 20:34

在播放器中,涉及到手势识别。所以,今天我们来说一下Android的手势识别。

我们首先需要站在巨人的肩膀上。引用一些别人的案例和说明。

第一篇:

http://www.2cto.com/kf/201110/109480.html


对于触摸屏,其原生的消息无非按下、抬起、移动这几种,我们只需要简单重载onTouch或者设置触摸侦听器setOnTouchListener即可进行处理。不过,为了提高我们的APP的用户体验,有时候我们需要识别用户的手势,Android给我们提供的手势识别工具GestureDetector就可以帮上大忙了。


基础



GestureDetector的工作原理是,当我们接收到用户触摸消息时,将这个消息交给GestureDetector去加工,我们通过设置侦听器获得GestureDetector处理后的手势。


GestureDetector提供了两个侦听器接口,OnGestureListener处理单击类消息,OnDoubleTapListener处理双击类消息。


OnGestureListener的接口有这几个:


// 单击,触摸屏按下时立刻触发 


abstract boolean onDown(MotionEvent e); 


// 抬起,手指离开触摸屏时触发(长按、滚动、滑动时,不会触发这个手势) 


abstract boolean onSingleTapUp(MotionEvent e); 


// 短按,触摸屏按下后片刻后抬起,会触发这个手势,如果迅速抬起则不会 


abstract void onShowPress(MotionEvent e); 


// 长按,触摸屏按下后既不抬起也不移动,过一段时间后触发 


abstract void onLongPress(MotionEvent e); 


// 滚动,触摸屏按下后移动 


abstract boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY); 


// 滑动,触摸屏按下后快速移动并抬起,会先触发滚动手势,跟着触发一个滑动手势 


abstract boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY); 


OnDoubleTapListener的接口有这几个:


// 双击,手指在触摸屏上迅速点击第二下时触发 


abstract boolean onDoubleTap(MotionEvent e); 


// 双击的按下跟抬起各触发一次 


abstract boolean onDoubleTapEvent(MotionEvent e); 


// 单击确认,即很快的按下并抬起,但并不连续点击第二下 


abstract boolean onSingleTapConfirmed(MotionEvent e); 


有时候我们并不需要处理上面所有手势,方便起见,Android提供了另外一个类SimpleOnGestureListener实现了如上接口,我们只需要继承SimpleOnGestureListener然后重载感兴趣的手势即可。

简单应用

import android.content.Context; import android.view.MotionEvent; import android.view.GestureDetector.SimpleOnGestureListener; import android.widget.Toast;  public class MyGestureListener extends SimpleOnGestureListener {      private Context mContext;          MyGestureListener(Context context) {         mContext = context;     }          @Override     public boolean onDown(MotionEvent e) {         Toast.makeText(mContext, "DOWN " + e.getAction(), Toast.LENGTH_SHORT).show();         return false;     }      @Override     public void onShowPress(MotionEvent e) {         Toast.makeText(mContext, "SHOW " + e.getAction(), Toast.LENGTH_SHORT).show();                }      @Override     public boolean onSingleTapUp(MotionEvent e) {         Toast.makeText(mContext, "SINGLE UP " + e.getAction(), Toast.LENGTH_SHORT).show();         return false;     }      @Override     public boolean onScroll(MotionEvent e1, MotionEvent e2,             float distanceX, float distanceY) {         Toast.makeText(mContext, "SCROLL " + e2.getAction(), Toast.LENGTH_SHORT).show();         return false;     }      @Override     public void onLongPress(MotionEvent e) {         Toast.makeText(mContext, "LONG " + e.getAction(), Toast.LENGTH_SHORT).show();     }      @Override     public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,             float velocityY) {         Toast.makeText(mContext, "FLING " + e2.getAction(), Toast.LENGTH_SHORT).show();         return false;     }      @Override     public boolean onDoubleTap(MotionEvent e) {         Toast.makeText(mContext, "DOUBLE " + e.getAction(), Toast.LENGTH_SHORT).show();         return false;     }      @Override     public boolean onDoubleTapEvent(MotionEvent e) {         Toast.makeText(mContext, "DOUBLE EVENT " + e.getAction(), Toast.LENGTH_SHORT).show();         return false;     }      @Override     public boolean onSingleTapConfirmed(MotionEvent e) {         Toast.makeText(mContext, "SINGLE CONF " + e.getAction(), Toast.LENGTH_SHORT).show();         return false;     } }   
我们可以在Activity里设置手势识别:
import android.app.Activity; import android.os.Bundle; import android.view.GestureDetector; import android.view.MotionEvent;  public class GestureTestActivity extends Activity {     private GestureDetector mGestureDetector;      @Override     public void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.main);          mGestureDetector = new GestureDetector(this, new MyGestureListener(this));     }      @Override     public boolean onTouchEvent(MotionEvent event) {         return mGestureDetector.onTouchEvent(event);     } } 

自定View中使用手势识别

import android.content.Context; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View;  public class MyView extends View {      private GestureDetector mGestureDetector;      public MyView(Context context, AttributeSet attrs) {         super(context, attrs);          mGestureDetector = new GestureDetector(context, new MyGestureListener(context));          setLongClickable(true);          this.setOnTouchListener(new OnTouchListener() {              public boolean onTouch(View v, MotionEvent event) {                 return mGestureDetector.onTouchEvent(event);             }          });     } }  

需要注意的问题:

对于自定义View,使用手势识别有两处陷阱可能会浪费你的不少时间。

1:View必须设置longClickable为true,否则手势识别无法正确工作,只会返回Down, Show, Long三种手势

2:必须在View的onTouchListener中调用手势识别,而不能像Activity一样重载onTouchEvent,否则同样手势识别无法正确工作


测试结果


下面是各种操作返回的手势序列,数值0表示触摸屏按下,1表示抬起

单击:down 0, single up 1, single conf 0 


短按:down 0, show 0, single up 1 


长按:down 0, show 0, long 0 


双击:down 0, single up 1, double 0, double event 0, down 0, double event 1 


滚动:down 0, (show 0), scrool 2... 


滑动:down 0, (show 0), scrool 2..., fling 1   


手势滑动在播放器中的应用

我们可以通过手势的滑动来调节音量,来控制进度。

package com.kankan.anime.player;import android.app.Activity;import android.support.v4.app.Fragment;import android.view.LayoutInflater;import android.view.MotionEvent;import android.view.View;import android.view.ViewGroup;import com.kankan.anime.R;import com.kankan.anime.player.GestureDetector.SimpleOnGestureListener;import com.kankan.anime.player.local.LocalPlayerActivity;import com.kankan.anime.util.NetworkHelper;import com.kankan.anime.util.UIHelper;import com.kankan.anime.widget.MediaController;import com.kankan.anime.widget.MediaController.MediaPlayerControl;import com.kankan.anime.widget.VideoGestureSeekWidget;import com.kankan.anime.widget.VoiceLightWidget;import com.kankan.logging.Logger;public class GestureDelegator {    private static final Logger LOG = Logger.getLogger(GestureDelegator.class);    private static final double RADIUS_SLOP = Math.PI * 5 / 24;    private static final int GESTURE_NONE = 0;    private static final int GESTURE_VOICE = GESTURE_NONE + 1;    private static final int GESTURE_LIGHT = GESTURE_VOICE + 1;    private static final int GESTURE_PROGRESS = GESTURE_LIGHT + 1;    private static final int MAX_SEEK_TIME = (int) (1.5 * 60);// 屏幕滑动快进,滑动一屏幕是180s    private VoiceLightWidget mVoiceLightWidget;    private VideoGestureSeekWidget mSeekWidget;    private int mCurrentGesture;    private final MediaController mMediaController;    private final GestureDetector mGestureDetector;    private final MediaController.MediaPlayerControl mPlayerController;    private final Activity mContext;    private Fragment mFragment;    private int mDragPos;    private int mCurrentDeltaScroll;    private int mScrolledPixPerVideoSecend;    private int mDeltaAll = 0;    private boolean mNeedResume;    public GestureDelegator(Fragment fragment, MediaController mediaController,            MediaPlayerControl mediaPlayerControl) {        mMediaController = mediaController;        mFragment = fragment;        mContext = fragment.getActivity();        mPlayerController = mediaPlayerControl;        mScrolledPixPerVideoSecend = (int) (UIHelper.getScreenWidth(mContext) * 0.7) / MAX_SEEK_TIME;        mGestureDetector = new GestureDetector(mContext, mGestureListener);        attachVoiceControllerToActivity();    }    private void clearDragPos() {        mDragPos = 0;        mDeltaAll = 0;    }    public boolean onTouchEvent(MotionEvent ev) {        if (mMediaController.isShowing() && (mMediaController.isActionInPannel(ev) && !mMediaController.isLocked())) {            mMediaController.show();            return true;        }        final int action = ev.getAction();        if (action == MotionEvent.ACTION_UP                || action == MotionEvent.ACTION_CANCEL) {            if (mCurrentGesture == GESTURE_PROGRESS) {                if (mNeedResume) {                    mPlayerController.start();                    mNeedResume = !mNeedResume;                }                mPlayerController.seekTo(mDragPos);                clearDragPos();            }            if (mCurrentGesture == GESTURE_NONE) {                if (mMediaController.isLocked()) {                    if (!mMediaController.isShowing()) {                        mMediaController.show();                    } else {                        mMediaController.hide();                    }                } else {                    if (!mMediaController.isShowing()) {                        mMediaController.show();                        mMediaController.showSystemUI();                    } else {                        mMediaController.hide();                        mMediaController.hideSystemUI();                    }                }            }            if (mMediaController.isShowing() && mCurrentGesture != GESTURE_NONE) {                mMediaController.fadeOut(1000);            }            mCurrentGesture = GESTURE_NONE;        }        if (action == MotionEvent.ACTION_MOVE) {            if (mMediaController.isShowing()) {                mMediaController.show();            }        }        if (!mMediaController.isLocked()) {            mGestureDetector.onTouchEvent(ev);        }        return true;    }    private void attachVoiceControllerToActivity() {        ViewGroup outFrame = (ViewGroup) mFragment.getView();        View layer = LayoutInflater.from(mContext).inflate(R.layout.gesture_widget_layer, null);        mVoiceLightWidget = (VoiceLightWidget) layer.findViewById(R.id.voice_controller);        mSeekWidget = (VideoGestureSeekWidget) layer.findViewById(R.id.video_seek_controller);        if (outFrame != null) {            outFrame.addView(layer);        }    }    private SimpleOnGestureListener mGestureListener = new SimpleOnGestureListener() {        public boolean onDoubleTap(MotionEvent e) {            if (mPlayerController.isPlaying()) {                mPlayerController.pause();            } else {                if (mContext instanceof LocalPlayerActivity) {                    mPlayerController.start();                } else {                    NetworkHelper.getInstance().accessNetwork(mContext, new Runnable() {                        @Override                        public void run() {                            mPlayerController.start();                        }                    });                }            }            return true;        };        public boolean onScroll(MotionEvent e1, MotionEvent e2,                float distanceX, float distanceY) {            if (e1 == null || e2 == null) {                return false;            }            float oldX = e1.getX();            final double distance = Math.sqrt(Math.pow(distanceX, 2)                    + Math.pow(distanceY, 2));            int windowWidth = UIHelper.getScreenWidth(mContext);            final double radius = distanceY / distance;            if (Math.abs(radius) > RADIUS_SLOP) {                if (mCurrentGesture != GESTURE_PROGRESS                        && !mSeekWidget.isVisiable()) {                    if (oldX > windowWidth / 2) {// TODO右半屏幕处理声音的逻辑                        mCurrentGesture = GESTURE_VOICE;                        onVoiceChange(distanceY, distance);                    } else {// TODO左半屏幕处理亮度的逻辑                        mCurrentGesture = GESTURE_LIGHT;                        onLightChange(distanceY, distance);                    }                }            } else {// TODO 处理视频进度                if (mCurrentGesture != GESTURE_VOICE                        && mCurrentGesture != GESTURE_LIGHT                        && !mVoiceLightWidget.isVisible()) {                    onVideoTouchSeek(distanceX, distance);                }            }            return super.onScroll(e1, e2, distanceX, distanceY);        }    };    private void onVoiceChange(float delta, double distance) {        mSeekWidget.setVisibility(View.GONE);        mVoiceLightWidget.onVoiceChange(delta, (int) distance);    }    private void onLightChange(float delta, double distance) {        mSeekWidget.setVisibility(View.GONE);        mVoiceLightWidget.onLightChange(delta, (int) distance,                mContext.getWindow());    }    private void onVideoTouchSeek(float distanceX, double distane) {        mVoiceLightWidget.setVisibility(View.GONE);        if (mDragPos == 0 && mCurrentGesture != GESTURE_PROGRESS) {            mDragPos = mPlayerController.getCurrentPosition();        }        if (mPlayerController.isPlaying()) {            mPlayerController.pause();            mNeedResume = true;        }        mCurrentGesture = GESTURE_PROGRESS;        mCurrentDeltaScroll += distanceX;                if (Math.abs(mCurrentDeltaScroll) >= mScrolledPixPerVideoSecend) {            int deltaTime = mCurrentDeltaScroll / mScrolledPixPerVideoSecend;            mDeltaAll += deltaTime;            mDragPos = mDragPos - deltaTime * 1000;            if (mDragPos > mPlayerController.getDuration()) {                mDragPos = mPlayerController.getDuration();            }            if (mDragPos < 0) {                mDragPos = 0;                mDeltaAll = 0;            }            mCurrentDeltaScroll = 0;        }        mSeekWidget.onSeek(mDragPos, mPlayerController.getDuration(), mDeltaAll);    }}
最后我们附上

GestureDetector.java文件

package com.kankan.anime.player;/* * Copyright (C) 2008 The Android Open Source Project *  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at *  * http://www.apache.org/licenses/LICENSE-2.0 *  * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */import android.content.Context;import android.os.Handler;import android.os.Message;import android.view.MotionEvent;import android.view.VelocityTracker;import android.view.View;import android.view.ViewConfiguration;import com.kankan.logging.Logger;/** * Detects various gestures and events using the supplied {@link MotionEvent}s. The {@link OnGestureListener} callback * will notify users when a particular motion event has occurred. This class should only be used with * {@link MotionEvent}s reported via touch (don't use for trackball events). *  * To use this class: * <ul> * <li>Create an instance of the {@code GestureDetector} for your {@link View} * <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call {@link #onTouchEvent(MotionEvent)}. The * methods defined in your callback will be executed when the events occur. * </ul> */public class GestureDetector {    @SuppressWarnings("unused")    private static final Logger LOG = Logger.getLogger(GestureDetector.class);    /**     * The listener that is used to notify when gestures occur. If you want to listen for all the different gestures     * then implement this interface. If you only want to listen for a subset it might be easier to extend     * {@link SimpleOnGestureListener}.     */    public interface OnGestureListener {        /**         * Notified when a tap occurs with the down {@link MotionEvent} that triggered it. This will be triggered         * immediately for every down event. All other events should be preceded by this.         *          * @param e         *            The down motion event.         */        boolean onDown(MotionEvent e);        /**         * The user has performed a down {@link MotionEvent} and not performed a move or up yet. This event is commonly         * used to provide visual feedback to the user to let them know that their action has been recognized i.e.         * highlight an element.         *          * @param e         *            The down motion event         */        void onShowPress(MotionEvent e);        /**         * Notified when a tap occurs with the up {@link MotionEvent} that triggered it.         *          * @param e         *            The up motion event that completed the first tap         * @return true if the event is consumed, else false         */        boolean onSingleTapUp(MotionEvent e);        /**         * Notified when a scroll occurs with the initial on down {@link MotionEvent} and the current move         * {@link MotionEvent}. The distance in x and y is also supplied for convenience.         *          * @param e1         *            The first down motion event that started the scrolling.         * @param e2         *            The move motion event that triggered the current onScroll.         * @param distanceX         *            The distance along the X axis that has been scrolled since the last call to onScroll. This is NOT         *            the distance between {@code e1} and {@code e2}.         * @param distanceY         *            The distance along the Y axis that has been scrolled since the last call to onScroll. This is NOT         *            the distance between {@code e1} and {@code e2}.         * @return true if the event is consumed, else false         */        boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);        /**         * Notified when a long press occurs with the initial on down {@link MotionEvent} that trigged it.         *          * @param e         *            The initial on down motion event that started the longpress.         */        void onLongPress(MotionEvent e);        /**         * Notified of a fling event when it occurs with the initial on down {@link MotionEvent} and the matching up         * {@link MotionEvent}. The calculated velocity is supplied along the x and y axis in pixels per second.         *          * @param e1         *            The first down motion event that started the fling.         * @param e2         *            The move motion event that triggered the current onFling.         * @param velocityX         *            The velocity of this fling measured in pixels per second along the x axis.         * @param velocityY         *            The velocity of this fling measured in pixels per second along the y axis.         * @return true if the event is consumed, else false         */        boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);    }    /**     * The listener that is used to notify when a double-tap or a confirmed single-tap occur.     */    public interface OnDoubleTapListener {        /**         * Notified when a single-tap occurs.         * <p>         * Unlike {@link OnGestureListener#onSingleTapUp(MotionEvent)}, this will only be called after the detector is         * confident that the user's first tap is not followed by a second tap leading to a double-tap gesture.         *          * @param e         *            The down motion event of the single-tap.         * @return true if the event is consumed, else false         */        boolean onSingleTapConfirmed(MotionEvent e);        /**         * Notified when a double-tap occurs.         *          * @param e         *            The down motion event of the first tap of the double-tap.         * @return true if the event is consumed, else false         */        boolean onDoubleTap(MotionEvent e);        /**         * Notified when an event within a double-tap gesture occurs, including the down, move, and up events.         *          * @param e         *            The motion event that occurred during the double-tap gesture.         * @return true if the event is consumed, else false         */        boolean onDoubleTapEvent(MotionEvent e);    }    /**     * A convenience class to extend when you only want to listen for a subset of all the gestures. This implements all     * methods in the {@link OnGestureListener} and {@link OnDoubleTapListener} but does nothing and return     * {@code false} for all applicable methods.     */    public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener {        public boolean onSingleTapUp(MotionEvent e) {            return false;        }        public void onLongPress(MotionEvent e) {        }        public boolean onScroll(MotionEvent e1, MotionEvent e2,                float distanceX, float distanceY) {            return false;        }        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,                float velocityY) {            return false;        }        public void onShowPress(MotionEvent e) {        }        public boolean onDown(MotionEvent e) {            return false;        }        public boolean onDoubleTap(MotionEvent e) {            return false;        }        public boolean onDoubleTapEvent(MotionEvent e) {            return false;        }        public boolean onSingleTapConfirmed(MotionEvent e) {            return false;        }    }    private int mTouchSlopSquare;    private int mDoubleTapTouchSlopSquare;    private int mDoubleTapSlopSquare;    private int mMinimumFlingVelocity;    private int mMaximumFlingVelocity;    /**     * 解决huawei meit手动隐藏navigationBar导致 doubleTab失效问题     */    private float mDoubleTapSlopSquareFactor = 1.3f;    private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();    private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout();    private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();    // constants for Message.what used by GestureHandler below    private static final int SHOW_PRESS = 1;    private static final int LONG_PRESS = 2;    private static final int TAP = 3;    private final Handler mHandler;    private final OnGestureListener mListener;    private OnDoubleTapListener mDoubleTapListener;    private boolean mStillDown;    private boolean mInLongPress;    private boolean mAlwaysInTapRegion;    private boolean mAlwaysInBiggerTapRegion;    private MotionEvent mCurrentDownEvent;    private MotionEvent mPreviousUpEvent;    /**     * True when the user is still touching for the second tap (down, move, and up events). Can only be true if there is     * a double tap listener attached.     */    private boolean mIsDoubleTapping;    private float mLastFocusX;    private float mLastFocusY;    private float mDownFocusX;    private float mDownFocusY;    private boolean mIsLongpressEnabled;    /**     * Determines speed during touch scrolling     */    private VelocityTracker mVelocityTracker;    private class GestureHandler extends Handler {        GestureHandler() {            super();        }        GestureHandler(Handler handler) {            super(handler.getLooper());        }        @Override        public void handleMessage(Message msg) {            switch (msg.what) {            case SHOW_PRESS:                mListener.onShowPress(mCurrentDownEvent);                break;            case LONG_PRESS:                dispatchLongPress();                break;            case TAP:                // If the user's finger is still down, do not count it as a tap                if (mDoubleTapListener != null && !mStillDown) {                    mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);                }                break;            default:                throw new RuntimeException("Unknown message " + msg); // never            }        }    }    /**     * Creates a GestureDetector with the supplied listener. This variant of the constructor should be used from a     * non-UI thread (as it allows specifying the Handler).     *      * @param listener     *            the listener invoked for all the callbacks, this must not be null.     * @param handler     *            the handler to use     *      * @throws NullPointerException     *             if either {@code listener} or {@code handler} is null.     *      * @deprecated Use     *             {@link #GestureDetector(android.content.Context, android.view.GestureDetector.OnGestureListener, android.os.Handler)}     *             instead.     */    @Deprecated    public GestureDetector(OnGestureListener listener, Handler handler) {        this(null, listener, handler);    }    /**     * Creates a GestureDetector with the supplied listener. You may only use this constructor from a UI thread (this is     * the usual situation).     *      * @see android.os.Handler#Handler()     *      * @param listener     *            the listener invoked for all the callbacks, this must not be null.     *      * @throws NullPointerException     *             if {@code listener} is null.     *      * @deprecated Use {@link #GestureDetector(android.content.Context, android.view.GestureDetector.OnGestureListener)}     *             instead.     */    @Deprecated    public GestureDetector(OnGestureListener listener) {        this(null, listener, null);    }    /**     * Creates a GestureDetector with the supplied listener. You may only use this constructor from a UI thread (this is     * the usual situation).     *      * @see android.os.Handler#Handler()     *      * @param context     *            the application's context     * @param listener     *            the listener invoked for all the callbacks, this must not be null.     *      * @throws NullPointerException     *             if {@code listener} is null.     */    public GestureDetector(Context context, OnGestureListener listener) {        this(context, listener, null);    }    /**     * Creates a GestureDetector with the supplied listener. You may only use this constructor from a UI thread (this is     * the usual situation).     *      * @see android.os.Handler#Handler()     *      * @param context     *            the application's context     * @param listener     *            the listener invoked for all the callbacks, this must not be null.     * @param handler     *            the handler to use     *      * @throws NullPointerException     *             if {@code listener} is null.     */    public GestureDetector(Context context, OnGestureListener listener, Handler handler) {        if (handler != null) {            mHandler = new GestureHandler(handler);        } else {            mHandler = new GestureHandler();        }        mListener = listener;        if (listener instanceof OnDoubleTapListener) {            setOnDoubleTapListener((OnDoubleTapListener) listener);        }        init(context);    }    /**     * Creates a GestureDetector with the supplied listener. You may only use this constructor from a UI thread (this is     * the usual situation).     *      * @see android.os.Handler#Handler()     *      * @param context     *            the application's context     * @param listener     *            the listener invoked for all the callbacks, this must not be null.     * @param handler     *            the handler to use     *      * @throws NullPointerException     *             if {@code listener} is null.     */    public GestureDetector(Context context, OnGestureListener listener, Handler handler,            boolean unused) {        this(context, listener, handler);    }    private void init(Context context) {        if (mListener == null) {            throw new NullPointerException("OnGestureListener must not be null");        }        mIsLongpressEnabled = true;        // Fallback to support pre-donuts releases        int touchSlop, doubleTapSlop, doubleTapTouchSlop;        final ViewConfiguration configuration = ViewConfiguration.get(context);        touchSlop = configuration.getScaledTouchSlop();        // doubleTapTouchSlop = configuration.getScaledDoubleTapTouchSlop();        doubleTapTouchSlop = configuration.getScaledTouchSlop();        doubleTapSlop = configuration.getScaledDoubleTapSlop();        mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();        mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity();        mTouchSlopSquare = touchSlop * touchSlop;        mDoubleTapTouchSlopSquare = doubleTapTouchSlop * doubleTapTouchSlop;        // mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop;        mDoubleTapSlopSquare = (int) (doubleTapSlop * doubleTapSlop * mDoubleTapSlopSquareFactor);    }    /**     * Sets the listener which will be called for double-tap and related gestures.     *      * @param onDoubleTapListener     *            the listener invoked for all the callbacks, or null to stop listening for double-tap gestures.     */    public void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener) {        mDoubleTapListener = onDoubleTapListener;    }    /**     * Set whether longpress is enabled, if this is enabled when a user presses and holds down you get a longpress event     * and nothing further. If it's disabled the user can press and hold down and then later moved their finger and you     * will get scroll events. By default longpress is enabled.     *      * @param isLongpressEnabled     *            whether longpress should be enabled.     */    public void setIsLongpressEnabled(boolean isLongpressEnabled) {        mIsLongpressEnabled = isLongpressEnabled;    }    /**     * @return true if longpress is enabled, else false.     */    public boolean isLongpressEnabled() {        return mIsLongpressEnabled;    }    /**     * Analyzes the given motion event and if applicable triggers the appropriate callbacks on the     * {@link OnGestureListener} supplied.     *      * @param ev     *            The current motion event.     * @return true if the {@link OnGestureListener} consumed the event, else false.     */    public boolean onTouchEvent(MotionEvent ev) {        final int action = ev.getAction();        if (mVelocityTracker == null) {            mVelocityTracker = VelocityTracker.obtain();        }        mVelocityTracker.addMovement(ev);        final boolean pointerUp =                (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP;        final int skipIndex = pointerUp ? ev.getActionIndex() : -1;        // 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);        }        final int div = pointerUp ? count - 1 : count;        final float focusX = sumX / div;        final float focusY = sumY / div;        boolean handled = false;        switch (action & MotionEvent.ACTION_MASK) {        case MotionEvent.ACTION_POINTER_DOWN:            mDownFocusX = mLastFocusX = focusX;            mDownFocusY = mLastFocusY = focusY;            // Cancel long press and taps            cancelTaps();            break;        case MotionEvent.ACTION_POINTER_UP:            mDownFocusX = mLastFocusX = focusX;            mDownFocusY = mLastFocusY = focusY;            // Check the dot product of current velocities.            // If the pointer that left was opposing another velocity vector, clear.            mVelocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);            final int upIndex = ev.getActionIndex();            final int id1 = ev.getPointerId(upIndex);            final float x1 = mVelocityTracker.getXVelocity(id1);            final float y1 = mVelocityTracker.getYVelocity(id1);            for (int i = 0; i < count; i++) {                if (i == upIndex)                    continue;                final int id2 = ev.getPointerId(i);                final float x = x1 * mVelocityTracker.getXVelocity(id2);                final float y = y1 * mVelocityTracker.getYVelocity(id2);                final float dot = x + y;                if (dot < 0) {                    mVelocityTracker.clear();                    break;                }            }            break;        case MotionEvent.ACTION_DOWN:            if (mDoubleTapListener != null) {                boolean hadTapMessage = mHandler.hasMessages(TAP);                if (hadTapMessage)                    mHandler.removeMessages(TAP);                if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null) && hadTapMessage &&                        isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) {                    // This is a second tap                    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                    mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);                }            }            mDownFocusX = mLastFocusX = focusX;            mDownFocusY = mLastFocusY = focusY;            if (mCurrentDownEvent != null) {                mCurrentDownEvent.recycle();            }            mCurrentDownEvent = MotionEvent.obtain(ev);            mAlwaysInTapRegion = true;            mAlwaysInBiggerTapRegion = true;            mStillDown = true;            mInLongPress = false;            if (mIsLongpressEnabled) {                mHandler.removeMessages(LONG_PRESS);                mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime()                        + TAP_TIMEOUT + LONGPRESS_TIMEOUT);            }            mHandler.sendEmptyMessageAtTime(SHOW_PRESS, mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);            handled |= mListener.onDown(ev);            break;        case MotionEvent.ACTION_MOVE:            if (mInLongPress) {                break;            }            final float scrollX = mLastFocusX - focusX;            final float scrollY = mLastFocusY - focusY;            if (mIsDoubleTapping) {                // Give the move events of the double-tap                handled |= mDoubleTapListener.onDoubleTapEvent(ev);            } else if (mAlwaysInTapRegion) {                final int deltaX = (int) (focusX - mDownFocusX);                final int deltaY = (int) (focusY - mDownFocusY);                int distance = (deltaX * deltaX) + (deltaY * deltaY);                if (distance > mTouchSlopSquare) {                    handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);                    mLastFocusX = focusX;                    mLastFocusY = focusY;                    mAlwaysInTapRegion = false;                    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)) {                handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY);                mLastFocusX = focusX;                mLastFocusY = focusY;            }            break;        case MotionEvent.ACTION_UP:            mStillDown = false;            MotionEvent currentUpEvent = MotionEvent.obtain(ev);            if (mIsDoubleTapping) {                // Finally, give the up event of the double-tap                handled |= mDoubleTapListener.onDoubleTapEvent(ev);            } else if (mInLongPress) {                mHandler.removeMessages(TAP);                mInLongPress = false;            } else if (mAlwaysInTapRegion) {                handled = mListener.onSingleTapUp(ev);            } else {                // 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)) {                    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;            mHandler.removeMessages(SHOW_PRESS);            mHandler.removeMessages(LONG_PRESS);            break;        case MotionEvent.ACTION_CANCEL:            cancel();            break;        }        return handled;    }    private void cancel() {        mHandler.removeMessages(SHOW_PRESS);        mHandler.removeMessages(LONG_PRESS);        mHandler.removeMessages(TAP);        mVelocityTracker.recycle();        mVelocityTracker = null;        mIsDoubleTapping = false;        mStillDown = false;        mAlwaysInTapRegion = false;        mAlwaysInBiggerTapRegion = false;        if (mInLongPress) {            mInLongPress = false;        }    }    private void cancelTaps() {        mHandler.removeMessages(SHOW_PRESS);        mHandler.removeMessages(LONG_PRESS);        mHandler.removeMessages(TAP);        mIsDoubleTapping = false;        mAlwaysInTapRegion = false;        mAlwaysInBiggerTapRegion = false;        if (mInLongPress) {            mInLongPress = false;        }    }    private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp,            MotionEvent secondDown) {        if (!mAlwaysInBiggerTapRegion) {            return false;        }        if (secondDown.getEventTime() - firstUp.getEventTime() > DOUBLE_TAP_TIMEOUT) {            return false;        }        int deltaX = (int) firstDown.getX() - (int) secondDown.getX();        int deltaY = (int) firstDown.getY() - (int) secondDown.getY();        return (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare);    }    private void dispatchLongPress() {        mHandler.removeMessages(TAP);        mInLongPress = true;        mListener.onLongPress(mCurrentDownEvent);    }}


好,Android的手势识别就到这里。谢谢。



2 0
原创粉丝点击