Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android開發之手勢識別

Android開發之手勢識別

編輯:關於Android編程

在播放器中,涉及到手勢識別。所以,今天我們來說一下Android的手勢識別。

我們首先需要站在巨人的肩膀上。引用一些別人的案例和說明。

第一篇:

/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:
 * 
    *
  • Create an instance of the {@code GestureDetector} for your {@link View} *
  • 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. *
*/ 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. *

* 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的手勢識別就到這裡。謝謝。



  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved