編輯:關於Android編程
在播放器中,涉及到手勢識別。所以,今天我們來說一下Android的手勢識別。
我們首先需要站在巨人的肩膀上。引用一些別人的案例和說明。
第一篇:
/kf/201110/109480.html
對於觸摸屏,其原生的消息無非按下、抬起、移動這幾種,我們只需要簡單重載onTouch或者設置觸摸偵聽器setOnTouchListener即可進行處理。不過,為了提高我們的APP的用戶體驗,有時候我們需要識別用戶的手勢,Android給我們提供的手勢識別工具GestureDetector就可以幫上大忙了。
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); } }
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); } }); } }
我們可以通過手勢的滑動來調節音量,來控制進度。
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); } }最後我們附上
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: *
* 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開發之listView組件用法。分享給大家供大家參考,具體如下:關於Android ListView組件中android:drawSelect
1、概述 上一篇已經基本給大家介紹了如何自定義ViewGroup,如果你還不了解,請查看:Android 手把手教您自定ViewGroup ,本篇將使用上篇
閒談:從大二暑假的時候開始,一直想進騰訊或者阿裡,在招實習生的時候,最終遺憾落選。暑假的時候為了好好准備校招,我放棄了去步步高實習的機會,繼續在原來的一家公司實習。在暑假
本文詳細分析了Android中Service服務。分享給大家供大家參考,具體如下:一、Service簡介Service是Android中實現程序後台運行的解決方案,適用於