• Android手势识别的发展


    在播放器。与手势识别。所以,看看今天的我们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的手势识别就到这里。谢谢。



    版权声明:本文博客原创文章,博客,未经同意,不得转载。

  • 相关阅读:
    enca工具,检测文件编码
    ubuntu 支持gbk
    LinkedList线程安全问题
    php防止form重复提交的方法
    Linux 内存泄露调试工具
    从B树、B+树、B*树谈到R 树
    Ubuntu 语言设置
    wwwauthenticate
    Lua脚本语法说明
    jQuery学习总结之元素的相对定位和选择器持续更新中
  • 原文地址:https://www.cnblogs.com/gcczhongduan/p/4621109.html
Copyright © 2020-2023  润新知