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

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

Android手势识别的发展的更多相关文章

  1. 札记:android手势识别,MotionEvent

    摘要 本文是手势识别输入事件处理的完整学习记录.内容包括输入事件InputEvent响应方式,触摸事件MotionEvent的概念和使用,触摸事件的动作分类.多点触摸.根据案例和API分析了触摸手势T ...

  2. Android手势识别(单击 双击 抬起 短按 长按 滚动 滑动)

    对于触摸屏,其原生的消息无非按下.抬起.移动这几种,我们只需要简单重载onTouch或者设置触摸侦听器setOnTouchListener即可进行处理.不过,为了提高我们的APP的用户体验,有时候我们 ...

  3. Android NDK进入发展

    使用互联网有很多javah命令生成一个头文件来完成JNI写,但事实上ADT集成NDK后.点点鼠标就可以了,网上的介绍是非常小懒的方法,在这里,我们主要谈论的懒惰JNI发展. 为ADT组态NDK.请个人 ...

  4. Android手势识别总结

    一:首先,在Android系统中,每一次手势交互都会依照以下顺序执行. 1. 接触接触屏一刹那,触发一个MotionEvent事件. 2. 该事件被OnTouchListener监听,在其onTouc ...

  5. android手势识别ViewFlipper触摸动画

    使用ViewFlipper来将您要来回拖动的View装在一起,然 后与GestureDetector手势识别类来联动,确定要显示哪个View,加上一点点动画效果即可.比如当手指向左快速滑动时跳转到上一 ...

  6. Android 手势识别—缩放

    上一篇讲解了手势识别中的点击和双击事件的识别,用到的是GestureDetector类和GestureDetectorCompat类,用于监听用户触摸屏幕中的简单动作. 缩放 基本用法如下,可以通过缩 ...

  7. Android 手势识别——单击/双击

    为什么需要手势识别? 手势对于我们的app有很多的地方都在使用,比如右滑关闭界面等.手势控制分为触发动作(Touch Mechanics,用户手指在屏幕上如何动作)和触发行为(Touch Activi ...

  8. Android+手势识别详解

      今天就来把以前的学习文章与经验简单总结中出来吧,在这里我就直接把代码贴下来了,希望能给初学者做最佳的学习参考,也希望有更多的开发人员来加入 ANDROID开发团队,参与更多的创新方式的开发,好了, ...

  9. 【转】android 手势识别和VelocityTracker

    参考地址: http://jcodecraeer.com/a/anzhuokaifa/androidkaifa/2012/1020/448.html http://www.jcodecraeer.co ...

随机推荐

  1. swift学习第二天:swift中的基本数据类型

    一:swift基本数据类型 Swift中的数据类型也有:整型/浮点型/对象类型/结构体类型等等 先了解整型和浮点型 整型 有符号 Int8 : 有符号8位整型 Int16 : 有符号16位整型 Int ...

  2. js进阶js中支持正则的四个常用字符串函数(search march replace split)

    js进阶js中支持正则的四个常用字符串函数(search march replace split) 一.总结 代码中详细四个函数的用法 search march replace split 二.js进 ...

  3. 【C++竞赛 F】yyy的三角形

    时间限制:2s 内存限制:32MB 问题描述 yyy对三角形非常感兴趣,他有n个木棍,他正在用这些木棍组成三角形.这时xxx拿了两根木棍过来,xxx希望yyy能给他一根木棍,使得xxx可以组成一个三角 ...

  4. 体验ArcGIS9.2的历史库功能

    转自原文 体验ArcGIS9.2的历史库功能 ESRI公司于2006年11月9日全球同步发布了历史上重要的软件版本ArcGIS9.2,在该版本中,主要新增了以下四大功能(ESRI田昌莲): 第一大新功 ...

  5. SpringSecurity3.2.5自己定义角色及权限的教程

    近期阴差阳错的搞上了SpringSecurity3.由于是自己做的小系统.中间遇到了非常多坑,基本每一个坑都踩过了,网上也查了不少资料,发现有不少错误的.更是让我绕了一圈又一圈,如今把一些主要的东西总 ...

  6. 【codeforces 752F】Santa Clauses and a Soccer Championship

    time limit per test2 seconds memory limit per test256 megabytes inputstandard input outputstandard o ...

  7. Method and apparatus for loading a segment register in a microprocessor capable of operating in multiple modes

    A microprocessor contains an address generation unit, including a segment block, for loading descrip ...

  8. js进阶 9-10 html中如何遍历下拉列表

    js进阶 9-10  html中单选框和多选框如何遍历下拉列表 一.总结 一句话总结: 1.select元素的options.length可以获取选择长度,然后用options[i]精确定位到选项,用 ...

  9. poj1995 Raising Modulo Numbers【高速幂】

    Raising Modulo Numbers Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 5500   Accepted: ...

  10. HDU 5317 RGCDQ(素数个数 多校2015啊)

    题目链接:pid=5317" target="_blank">http://acm.hdu.edu.cn/showproblem.php?pid=5317 Prob ...